如何在Canvas中实现自定义路径动画

在最近的项目中笔者须要作一个新需求:在canvas中实现自定义的路径动画。这里所谓的自定义路径不仅仅包括一条直线,也许是多条直线的运动组合,甚至还包含了贝塞尔曲线,所以,这个动画也许是下面这个样子的:javascript

自定义路径动画

那么如何才能在canvas中实现这种动画效果呢?其实很简单,对于路径的处理svg很是在行,所以在canvas中实现自定义路径动画,咱们须要借助svg的力量。java

建立Path

制做动画前,先要拿到动画的路径,对此咱们能够直接使用svg的path定义规则,好比咱们定义了一条较为复杂的路径(它到底长什么样你们能够本身试试,这里就不展现了),而后,咱们须要将定义好的路径导入进一个新生成的path元素中(咱们只是借助svg的api,所以并不须要将其插到页面内)canvas

const path = 'M0,0 C8,33.90861 25.90861,16 48,16 C70.09139,16 88,33.90861 88,56 C88,78.09139 105.90861,92 128,92 C150.09139,92 160,72 160,56 C160,40 148,24 128,24 C108,24 96,40 96,56 C96,72 105.90861,92 128,92 C154,93 168,78 168,56 C168,33.90861 185.90861,16 208,16 C230.09139,16 248,33.90861 248,56 C248,78.09139 230.09139,96 208,96 L48,96 C25.90861,96 8,78.09139 8,56 Z';

const pathElement = document.createElementNS('http://www.w3.org/2000/svg',"path"); 
pathElement.setAttributeNS(null, 'd', path);

getTotalLength与getPointAtLength

SVGPathElement提供的这两个api很关键,能够说它是实现路径动画的最为核心的地方(在svg内实现自定义路径动画通常也是经过这两个api去解决)详情请戳:SVGPathElement MDNapi

getTotalLength方法能够获取SVGPathElement的总长度性能优化

getPointAtLength方法,传入一个长度x,将返回距离SVGPathElement起点的长度为x的终点坐标。svg

利用这两个api,经过循环的方式不断去更新canvas内所绘制的图形坐标,便可实现路径动画:函数

const length = pathElement.getTotalLength();
const duration = 1000; // 动画总时长
const interval = length / duration;
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
let time = 0, step = 0; 

const timer = setInterval(function() {
  if (time <= duration) {
    const x = parseInt(pathElement.getPointAtLength(step).x);
    const y = parseInt(pathElement.getPointAtLength(step).y);
    move(x, y);  // 更新canvas所绘制图形的坐标
    step++;
  } else {
    clearInterval(timer)
  }
}, interval);

function move(x, y) {
   context.clearRect(0, 0, canvas.width, canvas.height);
   context.beginPath();
   context.arc(x, y, 25, 0, Math.PI*2, true);
   context.fillStyle = '#f0f';
   context.fill();
   context.closePath();
}

最后,咱们把它封装一下,便可实现一个在canvas中实现自定义动画的简易函数啦:性能

function customizePath(path, func) {
    const pathElement = document.createElementNS('http://www.w3.org/2000/svg',"path"); 
    pathElement.setAttributeNS(null, 'd', path);
      const length = pathElement.getTotalLength();
    const duration = 1000; 
    const interval = length / duration;
    let time = 0, step = 0; 
  
      const timer = setInterval(function() {
        if (time <= duration) {
              const x = parseInt(pathElement.getPointAtLength(step).x);
              const y = parseInt(pathElement.getPointAtLength(step).y);
              func(x, y);
              step++;
        } else {
              clearInterval(timer)
        }
     }, interval);
}

const path = 'M0,0 C8,33.90861 25.90861,16 48,16 C70.09139,16 88,33.90861 88,56 C88,78.09139 105.90861,92 128,92 C150.09139,92 160,72 160,56 C160,40 148,24 128,24 C108,24 96,40 96,56 C96,72 105.90861,92 128,92 C154,93 168,78 168,56 C168,33.90861 185.90861,16 208,16 C230.09139,16 248,33.90861 248,56 C248,78.09139 230.09139,96 208,96 L48,96 C25.90861,96 8,78.09139 8,56 Z';
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
function move(x, y) {
      context.clearRect(0, 0, canvas.width, canvas.height);
    context.beginPath();
      context.arc(x, y, 25, 0, Math.PI*2, true);
      context.fillStyle = '#f0f';
      context.fill();
      context.closePath();
}
customizePath(path, move);

实现思路大体如上所述,然而这并非最终成果。当咱们决定要在canvas制做自定义路径动画时,咱们不只要考虑如何实现,更要考虑性能优化,好比在这个实现思路中,咱们是否能够减小没必要要的渲染次数?帧率如何控制达到最优?等等。优化

虽然它们并不在这篇文章的讨论范围中,当也应当值得咱们思考。动画

相关文章
相关标签/搜索