美文网首页Web前端之路
Canvas 写的酷炫动画代码分析

Canvas 写的酷炫动画代码分析

作者: 虚拟J | 来源:发表于2020-12-12 00:59 被阅读0次
    霓虹灯线形成的自发六边形,随机性生成火花

    在看这篇文章时,里面有个动画的示例(如上图),然后感觉有点很酷炫,就打算了解一下怎么写的。( 先上代码示例链接

    前景提要

    需要先确保你还记得三角函数的知识。
    对 Canvas 的 API 有点了解,且稍微了解其中的 globalCompositeOperation API (不了解的话,可以看下这个文章,基本可以有点感觉)。

    代码分析

    尽我所能,我尽量在代码里关键地方都增加了注释。(可能有些描述表达不够好,请见谅~)

       var w = (c.width = window.innerWidth),
          h = (c.height = window.innerHeight),
          ctx = c.getContext("2d"),
          //一些配置项
          opts = {
            len: 20, //线长
            count: 50, //线总数
            baseTime: 10, //线停留基础时间
            addedTime: 10, //线额外停留时间
            dieChance: 0.05, //线重置的概率
            spawnChance: 1, //线生成的概率
            sparkChance: 0.1, //火花生成的概率
            sparkDist: 10, //火花距离线的距离
            sparkSize: 2, //火花大小
    
            color: "hsl(hue,100%,light%)", //hsl() 函数使用色相、饱和度、亮度来定义颜色。
            baseLight: 50, //基础的颜色亮度
            addedLight: 10, // [50-10,50+10]
            shadowToTimePropMult: 6, //阴影的模糊级别
            baseLightInputMultiplier: 0.01, //基础亮度
            addedLightInputMultiplier: 0.02, //额外亮度
    
            cx: w / 2,
            cy: h / 2,
            repaintAlpha: 0.04,
            hueChange: 0.1,
          },
          tick = 0, //控制颜色色相
          lines = [],
          dieX = w / 2 / opts.len,
          dieY = h / 2 / opts.len,
          baseRad = (Math.PI * 2) / 6;
    
        ctx.fillStyle = "black";
        ctx.fillRect(0, 0, w, h);
    
        function loop() {
          //浏览器下次重绘前调用该方法
          window.requestAnimationFrame(loop);
          //循环过程中更改生成的霓虹灯颜色色相
          ++tick;
    
          /*  目标图像 = 已经放置在画布上的绘图。
      源图像 = 打算放置到画布上的绘图。 */
          ctx.globalCompositeOperation = "source-over"; //目标图像上显示源图像
          ctx.shadowBlur = 0;
          ctx.fillStyle = "rgba(0,0,0,alp)".replace("alp", opts.repaintAlpha);
          ctx.fillRect(0, 0, w, h);
          ctx.globalCompositeOperation = "lighter"; //显示源图像 + 目标图像(重叠图形的颜色是通过颜色值相加来确定)
    
          //保持生成的霓虹灯线共有 count 个
          if (lines.length < opts.count && Math.random() < opts.spawnChance)
            lines.push(new Line());
    
          lines.map(function (line) {
            line.step();
          });
        }
        function Line() {
          //生成霓虹灯线时进行初始化
          this.reset();
        }
        //初始化,重置
        Line.prototype.reset = function () {
          this.x = 0;
          this.y = 0;
          this.addedX = 0;
          this.addedY = 0;
    
          this.rad = 0;
          //亮度
          this.lightInputMultiplier =
            opts.baseLightInputMultiplier +
            opts.addedLightInputMultiplier * Math.random();
    
          this.color = opts.color.replace("hue", tick * opts.hueChange);
          this.cumulativeTime = 0; //累计的时间
    
          this.beginPhase();
        };
        //霓虹灯线每一步的开始前规划阶段
        Line.prototype.beginPhase = function () {
          this.x += this.addedX;
          this.y += this.addedY;
    
          this.time = 0;
          //霓虹灯线每一步的停留时间
          this.targetTime = (opts.baseTime + opts.addedTime * Math.random()) | 0;
          //随机六边形路线方向
          this.rad += baseRad * (Math.random() < 0.5 ? 1 : -1);
          this.addedX = Math.cos(this.rad);
          this.addedY = Math.sin(this.rad);
          //霓虹灯线消失重置的条件
          if (
            Math.random() < opts.dieChance ||
            this.x > dieX ||
            this.x < -dieX ||
            this.y > dieY ||
            this.y < -dieY
          )
            this.reset();
        };
        //行走一步
        Line.prototype.step = function () {
          ++this.time;
          ++this.cumulativeTime;
          //超过行走时间,规划下一步
          if (this.time >= this.targetTime) this.beginPhase();
    
          var prop = this.time / this.targetTime,
            wave = Math.sin((prop * Math.PI) / 2), //sin90°=1
            x = this.addedX * wave, //cos(R)=b/c
            y = this.addedY * wave; //sin(R)=a/c
    
          ctx.shadowBlur = prop * opts.shadowToTimePropMult; //阴影的模糊级别
          //模糊和填充的颜色
          ctx.fillStyle = ctx.shadowColor = this.color.replace(
            "light",
            opts.baseLight +
              opts.addedLight *
                Math.sin(this.cumulativeTime * this.lightInputMultiplier)
          );
    
          //绘制霓虹灯线
          ctx.fillRect(
            opts.cx + (this.x + x) * opts.len,
            opts.cy + (this.y + y) * opts.len,
            2,
            2
          );
          //随机生成火花
          if (Math.random() < opts.sparkChance)
            ctx.fillRect(
              opts.cx +
                (this.x + x) * opts.len +
                Math.random() * opts.sparkDist * (Math.random() < 0.5 ? 1 : -1) -
                opts.sparkSize / 2,
              opts.cy +
                (this.y + y) * opts.len +
                Math.random() * opts.sparkDist * (Math.random() < 0.5 ? 1 : -1) -
                opts.sparkSize / 2,
              opts.sparkSize,
              opts.sparkSize
            );
        };
        loop();
    
        //监听浏览器窗口调整,重置
        window.addEventListener("resize", function () {
          w = c.width = window.innerWidth;
          h = c.height = window.innerHeight;
          ctx.fillStyle = "black";
          ctx.fillRect(0, 0, w, h);
    
          opts.cx = w / 2;
          opts.cy = h / 2;
    
          dieX = w / 2 / opts.len;
          dieY = h / 2 / opts.len;
        });
    

    简单来描述下,上面的主要代码:

    • 每一次浏览器重绘前都调用 loop() 函数。
    • 在 loop() 函数里,保持共有 count 个实例化的 Line 。
    • 在实例化时,调用 reset() 函数进行一些属性的初始化。
    • 在初始化完成后,调用 beginPhase() 函数进行下一步绘制的路线规划。(其中霓虹灯线触发重置条件时,调用 reset() 函数,进行属性的数值化)
    • 回到 loop() 函数,遍历每一个示例 Line ,调用 step() 函数,进行绘制霓虹灯线和线周围的火花。(其中超过每一步规定的停留时间后,调用 beginPhase() 函数,规划下一步。)
    问题

    这个动画,让我一开始感觉到厉害的地方是,霓虹灯线行走的尾部,有个渐渐的变暗淡的过程。所以,让人感觉这个动画,就很酷炫。
    而这个是怎么做的呢?我上面描述刻意没有讲到。可以看下代码,思考下,思路感觉挺微妙的。(我是重新看了下代码才明白的)

    答案

    关键点就在于 loop() 函数里的这两行代码。

       ctx.fillStyle = "rgba(0,0,0,alp)".replace("alp", opts.repaintAlpha);
       ctx.fillRect(0, 0, w, h);
    

    通过每一次的层层叠加上有一定透明度的黑色,从而达到了后面尾巴逐渐消灭的效果。(如果你开始一眼就发现了,打扰了,献丑了)

    最后

    酷炫的 Canvas 从来没有写过,也没接触过。这次试着分析这个酷炫动画代码,算是对如何用 Canvas 画动画有了点感觉了吧。
    另外,虽然看懂了代码,但似乎不是知道 Canvas 怎么画动画就能写出这个效果的,总感觉里面似乎蕴涵了一些数学功底~

    相关文章

      网友评论

        本文标题:Canvas 写的酷炫动画代码分析

        本文链接:https://www.haomeiwen.com/subject/vwssgktx.html