canvas实现飞线流动效果

这篇文章主要介绍如果用canvas实现固定坐标数组的飞线动画(折线),或者可以说是电流效果吧。

之前在网上有看到过流星托尾的效果,那种效果的主要方式是通过不断添加“蒙板”层的方式逐渐淡化前几秒的位置,而绘制的线其实就是一个小球,一直在移动。看起来是线,代码层面上其实只画了一个点。主要的代码为

ctx.fillStyle = 'rgba(0,0,0,0.1)';//这里颜色要与背景图的颜色一样,但是透明度要小
ctx.rect(0,0,800,600);//整个绘制区域的大小

但是,该方法有一个很严重的bug,托尾的轨迹无法完全清楚,虽然一开始颜色较淡,但是久而久之就会越来越深。具体的实现代码这里不赘述,可自行百度。

而今天要讲述的方法,是不断绘制每一帧的“线”。没错,是正儿八经的线。由于会涉及到拐角转弯,所以会有一定的计算和比较,这是难点。托尾的效果自然可以通过对线设置渐变颜色来实现。

1、参数介绍

constructor(color, length, width, speed, route, step = 0) {
    this.startx = route[0].x;//设置起始点坐标
    this.starty = route[0].y;
    this.route = route;//轨迹点,数组格式
    this.speed = speed;//速度
    this.width = width;//线的宽度
    this.color = color;//飞线的颜色
    this.length = length;//飞线的总体长度
    this.step = step;//表示以第几个轨迹点作为起始点绘制飞线动画,默认是第一个
    this.drawTimes = 0;

    this.colorStep = [0, 0.6, 1];//渐变比例
}

2、绘制当前帧的飞线

参数的设置可以根据实际情况做调整,下面主要讲一下绘制的方法:

draw(ctx) {
    this.drawTimes++;//帧数自增
    ctx.beginPath();
    let { route, startx, starty, color, speed } = this;
    ctx.moveTo(this.startx, this.starty);//首先要绘制起始点的位置

    let length = this.drawTimes * speed <= this.length ? this.drawTimes * speed : this.length;//计算每一帧需要绘制的长度

    let endx = startx;
    let endy = starty;

    for (let i = this.step + 1, len = route.length; i < len; i++) {
      let next_x = route[i].x;//获取第二个轨迹点的坐标
      let next_y = route[i].y;

      let tempLength = Math.sqrt(Math.pow((next_x - startx), 2) + Math.pow((next_y - starty), 2));//这一步是关键,主要是计算当前起始点到下一个轨迹点的直线长度,计算方法就是勾股定理

      if (tempLength > length) {
        // 当上述长度大于这一帧的线长时,说明飞线在这一帧还未运动至下一个轨迹点,因此这一帧飞线为直线的状态,不存在拐角
        let dx = length * (next_x - startx) / tempLength;//按照线段比例计算出x的增量
        let dy = length * (next_y - starty) / tempLength;

        endx = startx + dx;//得出这一帧的终点坐标
        endy = starty + dy;
        
        ctx.lineTo(endx, endy);//连线完成这一帧的飞线,并退出循环
        break;
      } else {
        // 如果存在拐角,则飞线必定经过下一个轨迹点
        ctx.lineTo(next_x, next_y);//连接下一个轨迹点
        startx = next_x;//将下一个轨迹点的坐标设置为起始坐标
        starty = next_y;

        endx = next_x;
        endy = next_y;

        length = length - tempLength;//计算剩余未绘制的飞线长度,开始下一轮的计算
      }
    }

    let lg = ctx.createLinearGradient(this.startx, this.starty, endx, endy);
    lg.addColorStop(this.colorStep[0], `rgba(${color[0]},${color[1]},${color[2]})`);
    lg.addColorStop(this.colorStep[1], `rgba(${color[0]},${color[1]},${color[2]})`);
    lg.addColorStop(this.colorStep[2], `rgba(${color[0]},${color[1]},${color[2]})`);
    ctx.strokeStyle = lg;
    ctx.lineWidth = this.width;
    ctx.stroke();
  }

3、计算下一帧起始点的坐标

这一步的主要作用是计算下一帧时,飞线起始点的坐标,绘制下一帧的飞线,将飞线动画串联。由于每一帧的长度间隔为一个speed的大小,下一帧的起始点有可能还在当前直线上运动,也有可能已经拐弯。

next() {
    let _this = this;//在绘制下一帧飞线前,线获取当前帧的状态
    let endStep = this.step;//获取起始轨迹点的index,默认为轨迹数组的第一个点
    let { route, startx, starty } = this;
    let length = this.speed;//与上一个方法不同,这个方法中length的值表示每一帧运动的长度,及一个speed的长度

    for (let i = endStep + 1, len = route.length; i < len; i++) {
      let next_x = route[i].x;//获取下一个轨迹点的坐标
      let next_y = route[i].y;

      let tempLength = Math.sqrt(Math.pow((next_x - _this.startx), 2) + Math.pow((route[i].y - _this.starty), 2));//与上一个方法相同,通过一个勾股定理计算每一帧的运动是否经过一个拐点

      let dx = length * (next_x - _this.startx) / tempLength;
      let dy = length * (next_y - _this.starty) / tempLength;

      if (tempLength > length) {
        //如果下一帧运动完后,起始点不经过拐点,则计算下一帧的起始点并赋值,结束循环
        this.startx += dx;
        this.starty += dy;
        break;
      } else {
        // 如果经过拐点,则将起始点设置为拐点的坐标,并做下一轮的循环
        _this.startx = next_x;
        _this.starty = next_y;
        _this.step++;

        length = length - tempLength;//计算剩余的长度
      }
    }
    if (this.step >= route.length - 1) {
      // 如果已经运行到最后一个轨迹点,则表示一次飞线动画完成
      return 'finish';
    } else {
      return 'go';
    }
}

 

 

你可能感兴趣的:(JavaScript,实例)