首页 > 解决方案 > HTML 画布和动画

问题描述

我知道如何用 lineTo() 画一条线:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(300, 150);
ctx.stroke(); 

有没有办法将动画(例如使用anime.js)应用于生产线?

标签: javascriptanimationhtml5-canvas

解决方案


一次一帧

要制作动画,您需要以高于人眼可以看到不同帧的速度(大约每秒 20 帧)绘制图形。

要在画布上执行此操作,您需要先清除显示,然后绘制场景。

例如,让一条线动画为围绕画布中心在两个圆圈中移动的点之间的线。变量time用于确定画线的位置。如果我们添加到time该线是向前动画,如果从时间减去该线在相反的方向移动。变量时间的变化越大,它移动线的速度就越快。

 // time determines where the line is drawn
 function drawLine(time) {
     // get size of the canvas
     const w = ctx.canvas.width;
     const h = ctx.canvas.height;

     // get the radius of circle that fits canvas
     const radius = Math.min(w, h) / 2;

     // get the center of the canvas
     const cx = w / 2;
     const cy = h / 2;

     // start of line outer radius
     const x1 = Math.cos(time) * radius * 0.9;
     const y1 = Math.sin(time) * radius * 0.9;

     // end of line, offset time 
     const x2 = Math.cos(time * 0.707) * radius * 0.4;
     const y2 = Math.sin(time * 0.707) * radius * 0.4;

     ctx.moveTo(x1 + cx, y1 + cy); // Add to path
     ctx.lineTo(x2 + cx, y2 + cy);

 }

渲染函数

现在您有了一个可以在不同时间绘制一条线的函数,您需要以看起来很生动的速度重复它。现代浏览器通过 DOM 提供了一个回调函数,旨在通过 DOM 呈现动画内容。它每秒调用 60 次(根据请求,每个请求),这是 DOM 呈现动画内容的最快速度。设置回调函数使用requestAnimationFrame(见示例)

我们一般使用一个更新函数来处理清除和绘制框架的整个过程。下面是一个更新函数的例子,它可以为上面的 line 函数设置动画。

  var time = 0;      // position of line as abstracted time
  const rate = 0.01; // Movement per frame in radians
  requestAnimationFrame(updateFrame); // request the first frame

  function updateFrame() {
       ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // clear the canvas
       time += rate;
       // set the line with color and end cap
       ctx.lineWidth = 10;
       ctx.lineCap = "round";
       ctx.strokeStyle = "black";


       // create a path
       ctx.beginPath();
       drawLine(time);

       // now draw it
       ctx.stroke();  
       requestAnimationFrame(updateFrame);
  }

运行示例

综上所述,我们得到以下结果。

我添加了一些东西让它更有趣。单击画布以添加行并重新启动时间变量。添加线条以了解在设备无法以愚弄眼睛的速度绘制线条之前可以绘制多少线条。对于将有很多线路的普通设备。(有一些方法可以大大提高该速率,但更先进一些)

添加大量线条还会显示一些有趣的锯齿效果,这些效果可以创建不存在的运动模式,但这是由于 DOM 渲染到画布的方式以及眼睛如何推断运动的微小缺陷造成的。

还检查画布与页面相比的大小,并在需要时调整画布大小。最好以这种方式调整大小而不是使用调整大小事件,因为调整大小事件与显示不同步。

// time determines where the line is drawn
function drawLine(time) {
  // get size of the canvas
  const w = ctx.canvas.width;
  const h = ctx.canvas.height;

  // get the radius of circle that fits canvas
  const radius = Math.min(w, h) / 2;

  // get the center of the canvas
  const cx = w / 2;
  const cy = h / 2;

  // start of line outer radius
  const x1 = Math.cos(time) * radius * 0.95;
  const y1 = Math.sin(time*1.04) * radius * 0.95;

  // end of line, offset time 
  const x2 = Math.cos(time * 0.9) * radius * 0.82;
  const y2 = Math.sin(time * 0.902) * radius * 0.82;

  // Offset inner circle  a little for interesting FX

  const ox = Math.cos(time * 0.7) * radius * 0.12;
  const oy = Math.sin(time * 0.703) * radius * 0.12;


  ctx.moveTo(x1 + cx, y1 + cy);
  ctx.lineTo(x2 + cx + ox, y2 + cy + oy);

}
const ctx = canvas.getContext("2d");

canvas.addEventListener("click",() => {
    timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
    timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
    timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
    timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
    timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
    timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
    timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
    timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
    time = 2;
});
const timeOffsets = [1];
var time = 0; // position of line as abstracted time
const rate = 0.01; // Movement per frame in radians
requestAnimationFrame(updateFrame); // request the first frame

function updateFrame() {
  // check if the canvas size needs to change to fit the page
  if (innerWidth !== ctx.canvas.width || innerHeight !== ctx.canvas.height) {
    // changing the canvas resolution also clears the canvas so dont need to clear
    ctx.canvas.width = innerWidth;
    ctx.canvas.height = innerHeight;
    timeOffsets.length = 1;
  } else {
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // clear the canvas
  }
  time += rate;
  var separate = 0;
  const angularSep = 8 / Math.min(ctx.canvas.width, ctx.canvas.height);
  // set the line with color and end cap
  ctx.lineWidth = 2;
  ctx.lineCap = "round";
  ctx.strokeStyle = "black";
  ctx.beginPath();

  for(const t of timeOffsets) {
      drawLine(time * t + separate);
      separate += angularSep;
  }

  ctx.stroke();
  requestAnimationFrame(updateFrame);
}
body: {
  padding: 0px;
  margin: 0px;
}

canvas {
  position: absolute;
  top: 0px;
  left: 0px;
}
<canvas id="canvas"></canvas>


推荐阅读