使用HTML5 Canvas创建动态粒子网格动画

525700djm 7年前
   <p>最近几年,辨识度最高的虚拟图案之一也许就是上面你所看到的这种动画形式了;我不确定这种动画形式是否有学名,这里我姑且称之为 动态粒子网格动画(dynamic point mesh animation) 。本例中的动画是基于这种动画形式的变种,灵感来自于 Daniel Mayovskiy 之前的作品。</p>    <p>通常,这类动画被放置于其他内容之后,所以在该例中,将 <canvas> 的大小设置为与视口相同的大小:</p>    <pre>  <code class="language-javascript"><canvas id="canvas"></canvas></code></pre>    <p>样式如下:</p>    <pre>  <code class="language-javascript">body {      background: #222;      margin: 0rem;      min-height: 100vh;  }  #canvas {      position: absolute;      display: block;      top: 0;      left: 0;      z-index: -1;  }</code></pre>    <p>这段代码的Codepen版本 在动画的顶部放置了一些文本,并为文本添加了些许结构和样式。</p>    <p>为了确保 <canvas> 能够铺满视口,页面末尾的第一部分 JavaScript 代码是一个缩放函数:</p>    <pre>  <code class="language-javascript">let resizeReset = function() {      w = canvasBody.width = window.innerWidth;      h = canvasBody.height = window.innerHeight;  }</code></pre>    <h2><strong>创建点集</strong></h2>    <p>opts 是一个 对象 ,包含一系列属性,默认属性如下:</p>    <pre>  <code class="language-javascript">const opts = {           particleColor: "rgb(200,200,200)",          lineColor: "rgb(200,200,200)",          particleAmount: 40,          defaultSpeed: 1,          variantSpeed: 1,          defaultRadius: 2,          variantRadius: 2,          linkRadius: 200,  }</code></pre>    <p>变量 variantSpeed 和 variantRadiut 用来增加点的尺寸大小和移动速度的 随机性 , linkRadius 表示点与点要连成线必须要靠近的距离范围。</p>    <p><canvas> 元素可以被缩放,这样就会导致粒子会碰触到浏览器窗口的边缘。脚本一加载, resizeReset() 函数就会被调用,但是需要延迟或“去抖”,以便在页面其余部分的操作期间不会减慢脚本的运行:</p>    <pre>  <code class="language-javascript">let delay = 200, tid;    window.addEventListener("resize", function(){          deBouncer();  });    let deBouncer = function() {      clearTimeout(tid);      tid = setTimeout(function() {          resizeReset();      }, delay);  };</code></pre>    <p>生成每个点(each of the dots)的 粒子 对象是一个比较庞大的函数:</p>    <pre>  <code class="language-javascript">Particle = function(xPos, yPos){       this.x = Math.random() * w;       this.y = Math.random() * h;      this.speed = opts.defaultSpeed + Math.random() * opts.variantSpeed;       this.directionAngle = Math.floor(Math.random() * 360);       this.color = opts.particleColor;      this.radius = opts.defaultRadius + Math.random() * opts. variantRadius;       this.vector = {          x: Math.cos(this.directionAngle) * this.speed,          y: Math.sin(this.directionAngle) * this.speed      };      this.update = function(){           this.border();           this.x += this.vector.x;           this.y += this.vector.y;       };      this.border = function(){           if (this.x >= w || this.x <= 0) {               this.vector.x *= -1;          }          if (this.y >= h || this.y <= 0) {              this.vector.y *= -1;          }          if (this.x > w) this.x = w;          if (this.y > h) this.y = h;          if (this.x < 0) this.x = 0;          if (this.y < 0) this.y = 0;       };      this.draw = function(){           drawArea.beginPath();          drawArea.arc(this.x, this.y, this.radius, 0, Math.PI*2);          drawArea.closePath();          drawArea.fillStyle = this.color;          drawArea.fill();      };  };</code></pre>    <p>在脚本的上下文中, this 指代的是每个创建出来的 粒子 :</p>    <ul>     <li> <p>每个粒子的初始速度和角度是随机生成的,粒子的颜色通过相关的设置选项来确定。</p> </li>     <li> <p>this.vector 用来存储粒子的 <em>移动方向</em> :如果 this.vector.x 为 1 ,则粒子向 右 运动;如果是 -1 ,则粒子向 左 移动。同样,如果 this.vector.y 为 负 ,则粒子向 上 移动,如果为 正 ,则粒子向 下 移动。</p> </li>     <li> <p>this.update 用来更新每个粒子 下一个位置 的坐标。首先,进行边缘检测;如果粒子的移动超出了canvas的尺寸,则将方向向量乘以 -1 产生 <em>反向</em> 的运动方向。</p> </li>     <li> <p>窗口缩放可能会引起粒子超出边界,如此一来 边缘检测 函数就捕捉不到了,所以就需要一系列的 if 语句来检测这种情况,将粒子的位置重置为当前canvas的边界。</p> </li>     <li> <p>最后一步,将这些点绘制到画布上。</p> </li>    </ul>    <p>我们需要进行下面的操作来使粒子运动起来:</p>    <pre>  <code class="language-javascript">function setup(){       particles = [];      for (let i = 0; i < opts.particleAmount; i++){          particles.push( new Particle() );      }      window.requestAnimationFrame(loop);  }</code></pre>    <p>在调用 loop 函数之前, setup 函数会创建一个由一系列粒子组成的 粒子 数组,这里使用 requestionAnimationFrame 来创建循环动画。</p>    <p>loop 函数看起来是这个样子的:</p>    <pre>  <code class="language-javascript">function loop(){       window.requestAnimationFrame(loop);      drawArea.clearRect(0,0,w,h);      for (let i = 0; i < particles.length; i++){          particles[i].update();          particles[i].draw();      }  }</code></pre>    <p>loop 函数会清除 canvas 画布,更新每个粒子的位置,然后再绘制粒子;这里使用 requestAnimationFrame() 来刷新、创建动画。</p>    <p>设置了相关常量、变量,并初始化了 resizeReset 函数后,调用 setup 函数开始运行动画:</p>    <pre>  <code class="language-javascript">const canvasBody = document.getElementById("canvas"),  drawArea = canvasBody.getContext("2d");  let delay = 200, tid;  resizeReset();  setup();</code></pre>    <p>到了这一步,动画看起来就是一系列点在 canvas 画布里面来回移动:</p>    <p>动画效果可以去 CodePen 上查看Dudley Storey ( @dudleystorey )的 《使用HTML5的canvas创建动态点动画》 。</p>    <p>为了创建网状结构,我们需要增加一点代码。</p>    <h2><strong>创建线条</strong></h2>    <p>为了绘制线条, loop() 函数需要添加些代码,变成下面这个样子:</p>    <pre>  <code class="language-javascript">function loop(){       window.requestAnimationFrame(loop);      drawArea.clearRect(0,0,w,h);      for (let i = 0; i < particles.length; i++){          particles[i].update();          particles[i].draw();      }      for (let i = 0; i < particles.length; i++){          linkPoints(particles[i], particles);      }  }</code></pre>    <p>每个粒子都会调用 linkPoints 函数,这个函数会调用 checkDistance 函数:</p>    <pre>  <code class="language-javascript">let checkDistance = function(x1, y1, x2, y2){       return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));  };</code></pre>    <p>checkDistance 函数决定了每个点与点之间的距离;如果距离小于 checkDistance ,那么线条的 透明度 就会被设置成大于0,并且线条会在匹配的点之间重新绘制。</p>    <p>在继续之前,先分解下 rgb 颜色 :</p>    <pre>  <code class="language-javascript">`let rgb = opts.lineColor.match(/\d+/g);``</code></pre>    <p>linkPoints 函数用来检测每一个点相对于剩下其他点的距离(等同于函数的"hubs"参数),然后使用 模板字面量 按照相应的 透明度 来绘制线条:</p>    <pre>  <code class="language-javascript">let linkPoints = function(point1, hubs){       for (let i = 0; i < hubs.length; i++) {          let distance = checkDistance(point1.x, point1.y, hubs[i].x, hubs[i].y);          let opacity = 1 - distance / opts.linkRadius;          if (opacity > 0) {               drawArea.lineWidth = 0.5;              drawArea.strokeStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${opacity})`;              drawArea.beginPath();              drawArea.moveTo(point1.x, point1.y);              drawArea.lineTo(hubs[i].x, hubs[i].y);              drawArea.closePath();              drawArea.stroke();          }      }  }</code></pre>    <h2><strong>结论</strong></h2>    <p>使用ES6是非常高效的,我也鼓励你在脚本上去尝试各个设置选项。</p>    <p>需要指出的是:如果添加过多的点和/或过多的连接距离(连接距离会创建过多的线条),动画也会扛不住。当视口变窄时最好降低粒子的运动速度:粒子的尺寸越小,在愈加狭窄空间内的移动速度貌似会越快。</p>    <p> </p>    <p>来自:http://www.zcfy.cc/article/the-new-code-create-a-dynamic-point-mesh-animation-with-html5-canvas-1780.html</p>    <p> </p>