js网页特效使用教程:新手完成第一张作品的方法

js网页特效使用教程:新手完成第一张作品的方法

js网页特效使用教程:新手完成第一张作品的方法

网页特效不一定要从复杂的动画库或成套框架开始。一个按钮、一块画布,再加上一段能响应点击的 JavaScript,就足以做出一张可交互的作品。本文会带你完成一个“点击按钮,画布中央出现扩散波纹”的小效果,并说明如何运行、修改和检查它。

示例只使用浏览器内置的 HTML、CSS 和 JavaScript,不需要安装依赖或联网加载资源。你可以把代码保存成一个文件,在浏览器中打开。跟着完成后,你也能把同样的思路用于卡片反馈、按钮动效、提示状态和简单的视觉实验。

一、什么场景适合用 JavaScript 特效

JavaScript 的价值在于根据用户行为或页面状态改变内容。适合使用它的场景包括:

  • 操作反馈:点击后显示提示、展开面板、播放轻量的确认动画。
  • 数据可视化:根据数据绘制图表、标记或简单的动态示意。
  • 互动展示:让用户通过点击、拖动或键盘操作体验页面内容。
  • 页面状态变化:根据选择项切换内容,或者在加载、成功、失败等状态间更新界面。

如果只需颜色变化、悬停反馈或简单的淡入,优先考虑 CSS。CSS 转场和动画通常更直接,也不必用 JavaScript 持续更新样式。需要响应用户输入、计算坐标、组合多个状态或绘制图形时,再引入 JavaScript 更合适。特效不应掩盖主要内容,也不应该让关键操作只能通过动画表达。

二、先把第一张作品拆成几件事

本文的作品由三个部分组成:页面里的画布负责绘图;按钮提供明确的操作入口;JavaScript 监听点击并绘制波纹。点击时记录动画开始时间,之后用浏览器的 requestAnimationFrame 按帧更新波纹半径,动画结束后停止更新。

这份示例不需要让动画一直运行。只有点击后才申请动画帧,效果结束就停下来。这样比在页面加载时永久循环更节制,也更适合初学者理解“事件触发动画、动画结束清理”的基本结构。

三、可直接运行的完整代码

复制下面的代码,保存为 first-effect.html,再用现代浏览器打开。它是完整的单文件示例,没有外部库或外部图片。按钮可以用鼠标点击,也可以通过键盘聚焦后按回车或空格触发。

<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>我的第一张网页特效</title>
  <style>
    * {
      box-sizing: border-box;
    }

    body {
      min-height: 100vh;
      margin: 0;
      padding: 24px;
      display: grid;
      place-items: center;
      color: #f3f6ff;
      background: #101522;
      font-family: system-ui, sans-serif;
    }

    .work {
      width: min(100%, 680px);
    }

    h1 {
      margin: 0 0 8px;
      font-size: clamp(1.5rem, 4vw, 2.3rem);
    }

    p {
      color: #bac4d8;
      line-height: 1.7;
    }

    .stage {
      position: relative;
      overflow: hidden;
      min-height: 300px;
      margin-top: 24px;
      border: 1px solid #3a4560;
      border-radius: 20px;
      background:
        radial-gradient(circle at 50% 45%, #293c64 0, #19243d 45%, #141b2b 100%);
    }

    canvas {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      pointer-events: none;
    }

    .stage-content {
      position: relative;
      z-index: 1;
      min-height: 300px;
      display: grid;
      place-content: center;
      justify-items: center;
      padding: 24px;
      text-align: center;
    }

    button {
      padding: 12px 20px;
      border: 0;
      border-radius: 999px;
      color: #101522;
      background: #a7e8ff;
      font: inherit;
      font-weight: 700;
      cursor: pointer;
    }

    button:hover {
      background: #d0f4ff;
    }

    button:focus-visible {
      outline: 3px solid #fff;
      outline-offset: 4px;
    }

    .status {
      min-height: 1.5em;
      margin: 14px 0 0;
      font-size: 0.95rem;
    }
  </style>
</head>
<body>
  <main class="work">
    <h1>涟漪实验室</h1>
    <p>点击按钮,让画布中央出现一圈扩散的光波。</p>

    <section class="stage" aria-label="波纹特效展示区域">
      <canvas id="effect" aria-hidden="true"></canvas>
      <div class="stage-content">
        <button id="play" type="button">生成一圈波纹</button>
        <p class="status" id="status" aria-live="polite">等待操作</p>
      </div>
    </section>
  </main>

  <script>
    const canvas = document.querySelector("#effect");
    const button = document.querySelector("#play");
    const status = document.querySelector("#status");
    const context = canvas.getContext("2d");

    const duration = 900;
    const maxRadius = 150;
    const motionPreference = window.matchMedia(
      "(prefers-reduced-motion: reduce)"
    );

    let width = 0;
    let height = 0;
    let scale = 1;
    let animationId = 0;
    let startTime = 0;

    function resizeCanvas() {
      const rect = canvas.getBoundingClientRect();

      width = rect.width;
      height = rect.height;

      // 限制画布分辨率,避免高像素比设备创建过大的绘图缓冲区。
      scale = Math.min(window.devicePixelRatio || 1, 2);

      canvas.width = Math.round(width * scale);
      canvas.height = Math.round(height * scale);

      // 后续绘图继续使用 CSS 像素作为坐标单位。
      context.setTransform(scale, 0, 0, scale, 0, 0);
    }

    function drawRipple(progress) {
      context.clearRect(0, 0, width, height);

      const radius = maxRadius * progress;
      const opacity = 1 - progress;

      context.beginPath();
      context.arc(width / 2, height / 2, radius, 0, Math.PI * 2);
      context.strokeStyle = `rgba(167, 232, 255, ${opacity})`;
      context.lineWidth = 3;
      context.stroke();

      context.beginPath();
      context.arc(width / 2, height / 2, radius * 0.62, 0, Math.PI * 2);
      context.strokeStyle = `rgba(255, 255, 255, ${opacity * 0.45})`;
      context.lineWidth = 1;
      context.stroke();
    }

    function animate(now) {
      const progress = Math.min((now - startTime) / duration, 1);

      drawRipple(progress);

      if (progress < 1) {
        animationId = requestAnimationFrame(animate);
      } else {
        animationId = 0;
        context.clearRect(0, 0, width, height);
      }
    }

    function playEffect() {
      if (animationId) {
        cancelAnimationFrame(animationId);
        animationId = 0;
      }

      if (motionPreference.matches) {
        // 用户偏好减少动态效果时,呈现静态结果,不播放扩散动画。
        drawRipple(0.55);
        status.textContent = "已生成静态波纹";
        return;
      }

      startTime = performance.now();
      status.textContent = "波纹正在扩散";
      animationId = requestAnimationFrame(animate);
    }

    button.addEventListener("click", playEffect);
    window.addEventListener("resize", resizeCanvas);

    resizeCanvas();
  </script>
</body>
</html>

四、代码是怎样工作的

1. 画布尺寸和坐标

画布的 CSS 尺寸由容器控制,JavaScript 则读取它在页面上的实际尺寸,并设置画布内部绘图缓冲区。两者不是同一个概念:只用 CSS 放大画布,可能让图形显得模糊;直接把绘图缓冲区设得很大,又会增加内存和绘制成本。

示例将 devicePixelRatio 限制在不高于 2,再用 setTransform 把绘图坐标映射回 CSS 像素。这个上限是示例中的性能取舍,不是浏览器规定的唯一正确值,也不能保证所有设备都同样清晰或流畅。若作品要适配更多设备,应在目标机型上实际测试,权衡画质与资源占用。

2. 用进度计算动画

duration 表示动画时长,单位是毫秒;maxRadius 是波纹扩散到的最大半径,单位是画布坐标里的 CSS 像素。动画每一帧以当前时间减去起始时间,除以时长得到进度,再用 Math.min 将进度限制在 1 以内。

进度从 0 变到 1,半径随之增加,透明度随之减小。这里使用直线变化,便于看懂公式;如果希望波纹开始时慢、随后加速,可以对进度做缓动计算,但应保证用于计算半径和透明度的结果仍处于预期范围。CSS 动画中的缓动曲线不能直接自动套用到 Canvas 绘图,JavaScript 需要自己计算。

3. 为什么要清理动画

按钮可能在动画未结束时再次被点击。代码会先取消尚未完成的动画帧,再开始新一轮效果,避免多个循环同时绘制。动画完成后,它将画布清空,不再继续申请帧。若把这段作品扩展成持续播放的动画,还应提供暂停入口,并在页面隐藏时考虑暂停不必要的计算。

4. 减少动态效果偏好

prefers-reduced-motion 是浏览器提供的媒体偏好,用于读取用户系统是否设置了减少动态效果。示例在此偏好启用时显示静态波纹,不播放扩散过程。它不是对所有动画行为的自动禁令,也不会替开发者完成无障碍设计;应由页面根据效果特点决定如何降动效,并确保信息和功能仍然可用。

五、可以怎样修改这张作品

  • 调整颜色:修改 strokeStyle 中的 RGBA 数值。前三个数字表示红、绿、蓝,最后一个数字表示透明度,通常取 0 到 1 之间的值。
  • 调整速度:把 duration 从 900 改成更大的数,动画会持续更久;减小数值则会更快。浏览器每秒能绘制多少帧并不由这个参数保证。
  • 调整扩散范围:修改 maxRadius。半径以 CSS 像素为单位,实际视觉效果还取决于容器尺寸。
  • 改变画布外观:调整 .stage 的背景、边框和最小高度。绘图区域随容器尺寸变化,因此不同屏幕上可见效果可能不同。
  • 增加触发方式:可以监听其他按钮或特定页面事件,但要避免无意中让动画连续触发。新增控件时保留清楚的焦点样式与键盘操作。

建议每次只改一个参数,保存并刷新后观察差异。若同时改动动画时长、半径、画布尺寸和样式,出现问题时就不容易判断原因。

六、常见错误与排查方法

打开页面后按钮没有反应

先检查代码是否完整复制,尤其是脚本是否放在对应 HTML 元素之后,以及选择器中的 id 是否和页面一致。然后打开浏览器开发者工具的控制台查看报错。复制代码时如果漏掉括号、引号或结束标签,脚本可能会停止执行。

画布是空白或尺寸不对

检查画布是否有可见尺寸。画布的 CSS 宽高和 canvas.width、canvas.height 分工不同:前者控制页面布局,后两者控制绘图缓冲区。只设置其中一组,可能出现拉伸、模糊或坐标不匹配。容器尺寸变化后也要重新计算画布尺寸。

画面发虚或耗费资源

高像素比屏幕会让绘图缓冲区面积快速增大,而绘制工作量大致会随宽高乘积增长。示例将像素比上限设为 2,目的是控制这个成本;如果容器特别大、特效数量很多或目标设备性能较弱,还需要进一步降低分辨率、减少绘图对象或缩短持续时间。

快速连点后出现多重动画

确认每次开始动画前都处理上一轮的动画帧。若还有定时器、事件监听器或其他动画循环,也要分别管理和清理。不要仅通过不断创建新的循环来重置效果。

页面缩放或调整窗口后坐标错位

绘制坐标应以当前画布尺寸为准。示例在窗口尺寸变化时重新设置缓冲区。更复杂的页面可以用 ResizeObserver 观察具体容器,而不是只监听窗口;不过要避免在尺寸回调中无条件触发大量同步布局计算。

七、关键边界:参数、性能与浏览器行为

示例参数是为了展示思路,不是适用于所有项目的固定限制。duration、maxRadius 和像素比上限都可以调整,但值越大不代表效果必然更好。尺寸过大的画布会增加内存和绘制成本;过长或频繁的动画会影响耗电、流畅度和页面可操作性。

requestAnimationFrame 用于请求浏览器在合适的绘制时机运行回调,通常比用固定间隔的定时器模拟逐帧绘图更适合此类效果。但它不承诺固定帧率,也不保证动画在所有设备上以相同速度呈现。后台标签页、低性能设备和系统负载都可能影响实际绘制节奏,所以要避免用“每帧增加固定像素”的方式假定帧率恒定。示例依据时间计算进度,就是为了让时长更少依赖帧数。

Canvas 绘制的图形本身不是普通 DOM 文本,通常不能像 HTML 元素那样直接被辅助技术读取或选择。此示例把画布设为装饰性内容,并用真实按钮触发、用状态文字反馈结果。若画布承载重要数据,应提供相应的文本说明、表格或可访问的 DOM 表达,不能把关键含义只画在画布上。

八、安全用途与需要避免的做法

这份作品在画布上绘制固定的圆形,不解析用户输入,也不调用网络服务,因此没有必要引入 HTML 字符串拼接。不要为了让页面“更动态”,把不可信输入直接交给 innerHTML:它会把字符串按 HTML 解析,可能造成跨站脚本风险。显示普通文本时,优先使用 textContent;如果业务确实需要展示用户提供的富文本,应采用经过审查的消毒方案,并按具体上下文处理。

不要把动画参数校验误当成安全防护。限制半径或时长有助于控制体验与资源消耗,却不构成权限控制、身份验证或数据保护。客户端代码可以被用户查看和修改,不能把秘密密钥、服务端权限判断或可信业务规则只放在浏览器里。涉及敏感数据和访问权限时,应在服务端执行必要验证。

同样,不要把 JavaScript 特效当作安全工具来使用。Canvas 波纹、遮罩或视觉提示不会保护页面内容,也不能阻止复制、截屏或绕过前端逻辑。安全边界应由正确的服务端校验、权限控制和合适的安全策略建立,而不是由动画效果建立。

九、从练习走向实际项目

完成后,可以尝试把波纹改成状态提示、给画布增加不同的颜色主题,或在点击位置绘制图形。扩展时保持几个原则:操作有明确入口;效果有结束条件;信息不只靠动画传达;画布尺寸和绘图坐标保持一致;不需要的循环及时停止。

若项目已经使用组件框架,应把事件监听、动画帧和画布资源的创建与清理放在组件生命周期内,避免组件卸载后回调仍在运行。若页面里有多个相同特效,应避免为每个区域创建不受管理的永久循环。需要更复杂的图形、物理模拟或大量对象时,再评估 Canvas、SVG、CSS 动画或专业图形库各自的取舍,而不是一开始就引入多余工具。

结语

第一张 JavaScript 网页特效不必复杂:先用 HTML 提供结构,用 CSS 建立视觉区域,再让 JavaScript 响应一次真实操作。本文的点击波纹展示了事件监听、画布尺寸、时间驱动动画、重绘和资源清理这些基础方法。先让单文件示例稳定运行,再逐项修改参数、检查键盘操作和设备表现,你就拥有了一条可复用的创作起点。

© 版权声明

相关文章