Canvas + CSS3实现可视化项目开场动画

一、背景:

在最近的可视化项目中,视觉设计采用钢铁的写实风格,整体页面布局为左中右,动效需要实现在刚进入页面时,左右板块撞向中间板块,产生撞击光效和火花飞散的动画效果。

二、实现思路:

需要考虑以下几个方面:

  1. 整体页面的布局,左中右三个板块需要独立,分别绝对定位,不要耦合在一起;
  2. 使用css3实现撞击动效;
  3. 使用canvas绘制撞击后的火光和火花,光效可以径向渐变,火花飞舞的运动轨迹分别是左右方向的匀减速运动,上下方向的匀减速和匀加速,最后到飞舞消散的过程,粒子大小随机

三、实现过程:

  1. 撞击效果的实现

  主要使用到CSS3 的animation和translate3d

 (1)animation的属性解析

          name :要绑定到选择器的关键帧的名称

          duration 动画指定需要多少秒或毫秒完成

          timing-function 设置动画将如何完成一个周期

          delay 设置动画在启动前的延迟间隔

          iteration-count 定义动画的播放次数

          direction 指定是否应该轮流反向播放动画

          fill-mode 规定当动画不播放时,要应用到元素的样式

          play-state: 指定动画是否正在运行或已暂停

 (2)translate3d() css函数在三维空间中重新定位一个元素。

          语法:translate3d(tx,ty,tz) 

           tx——表示平移矢量的横坐标的值。

           ty——表示平移向量的纵坐标的值。

           tz——表示平移矢量的z分量的值。

.animated {
  animation: 0.8s cubic-bezier(0.94,0,1,1);
  animation-fill-mode: both;
  animation-delay:0.3s;
}
@keyframes bounceInLeft {
  0% {
    opacity: 0;
    transform: translate3d(-100%, 0, 0);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.bounceInLeft {
  animation-name: bounceInLeft;
}
@keyframes bounceInRight {
  0% {
    opacity: 0;
    transform: translate3d(100%, 0, 0);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.bounceInRight {
  animation-name: bounceInRight;
} 

2. 撞击火光和火花的实现

(1)画光,光效是两个对称的圆,然后做径向渐变

  Grad.addColorStop定义了两个渐变的色阶

function drawLight(x,y,direction) {
  var grad=cxt.createRadialGradient(x,y,0,x,y,1000);
  grad.addColorStop(0,"rgba(255,180,0,0.4)");       
  grad.addColorStop(1,"rgba(255,180,0,0)");
  cxt.fillStyle=grad;     
 
  cxt.beginPath();
  cxt.arc(x,y,1000,0,Math.PI*2,direction);
  cxt.closePath();
  cxt.fill();
}

(2)画粒子,声明粒子类

                 class Spark {
                    constructor() {
                        this.startX = 600; //初始位置
                        this.startY = 100;
                        this.radius = 5;
                        this.radiusY = 1;
                        this.radiusOffset = 0;
                        this.radiusA = 0.1;
                        this.v0x = 2; //初始速度
                        this.v0y = 10;
                        this.speedAY = 4; //加速度
                        this.speedAX = -0.5;
                        this.speedX = 2; //实时速度
                        this.speedY = 10;
                        this.offsetX = 0;
                        this.offsetY = 0;
                        this.t = 0;
                        this.color = "#0000FF";
                        this.tY = 0;
                        this.time = 0;
                        this.dirDown = 0;
                        this.dirUp = 1;
                        this.dir = 0; //上下方向状态
                        this.cycleYTimes = 0;
                        this.waiteTime = 0;
                        this.startTime = 0;
                        this.trail = [];
                        this.dirLeft = 0;
                        this.dirRight = 1;
                        this.dirX = 1; //水平方向状态
                    }
                    calc(){
                        if (this.dirX == this.dirLeft) {
                            this.trail.push({
                              x: (this.startX - this.offsetX), 
                              y: (this.startY + this.offsetY)});
                        } else {
                            this.trail.push({
                              x: (this.startX + this.offsetX), 
                              y: (this.startY + this.offsetY)});
                        }
                        if(this.trail.length >2){
                            this.trail.splice(0,1);
                        }
                    }
                }

3)批量生产多个粒子

               for (var i = 0; i < 200;i++) {
                    var spark = new Spark();
                    spark.startY = 100 + Math.random() * 1800;
                    spark.v0x = 10 + Math.random() * 8;
                    spark.speedX = spark.v0x;
                    spark.radius = 2 + Math.random() * 15;
                    spark.radiusY = 1 + Math.random() * 2;
                    let randomDirX = Math.random() * 2;
                    if (randomDirX < 1) {
                        //向左面飞的
                        spark.startX = this.startLeft;
                        spark.dirX = 0;
                        spark.dir = 0;
                    } else {
                        //向右面飞的
                        spark.startX = this.startRight;
                        spark.dirX = 1;
                        spark.dir = 1;
                        spark.speedY = spark.v0x - 1;
                    }
                    
                    spark.startTime = Math.random() * 10;
                    spark.color = "#fff";
                    sparks.push(spark);
                }

(4)画粒子,粒子是根据随机数计算出的半径绘制的小椭圆,然后加上外发光的阴影

       ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)

      参数:(起点x,  起点y, 半径x,  半径y,  旋转的角度,起始角,结果角,方向)

               function drawBall(spark) {
                    cxt.shadowBlur = 20;
                    cxt.shadowColor="#fff"; 
                    cxt.fillStyle=spark.color;
                    cxt.beginPath();
                    if (spark.dirX == spark.dirLeft) {
                        cxt.ellipse(
                            spark.startX - spark.offsetX,
                            spark.startY + spark.offsetY,
                            spark.radius + spark.radiusOffset,
                            spark.radiusY + spark.radiusOffset,
                            0,
                            0,
                            Math.PI*2,
                            true
                        );
                    } else {
                        cxt.ellipse(
                            spark.startX + spark.offsetX,
                            spark.startY + spark.offsetY,
                            spark.radius + spark.radiusOffset,
                            spark.radiusY + spark.radiusOffset,
                            0,
                            0,
                            Math.PI*2,
                            true);
                    }
                    cxt.closePath();
                    cxt.fill();
                }

(5)画轨迹,

         核心公式:v0t+1/2at*t

         水平方向,向左和向右飞的粒子作匀减速运动

         垂直方向,向左和向右飞的粒子在固定幅度内上下运动

function update(spark){
  if (spark.waiteTime >spark.startTime) {
    if (spark.speedX >0) {
      if (spark.cycleYTimes <3){
        if (spark.dir == spark.dirDown) {
          if (spark.speedY >-spark.v0y) {
            spark.offsetY = spark.v0y * spark.tY - 0.5 * spark.tY * spark.tY * 
            spark.speedAY;
            spark.speedY -= spark.speedAY;
          } else {
            spark.offsetY = spark.v0y * spark.tY - 0.5 * spark.tY * spark.tY * 
            spark.speedAY;
 
            spark.dir = spark.dirUp;
            spark.tY = 0;
            spark.speedY += spark.speedAY;
            spark.cycleYTimes++;
          }
         } else if (spark.dir == spark.dirUp) {
            if (spark.speedY 

(6)执行动画,每隔30毫秒清理一次画布

               function play(){
                    cxt.clearRect(0,0,5760,1890);
 
                    for (let i = 0;i < sparks.length;i++) {
                        update(sparks[i]);
                    }
 
                }
                drawLight(this.startLeft,950,true)
                drawLight(this.startRight,950,false)
                setInterval(play,30);

四、完成效果

 

 

 

五、注意事项

五、注意事项

1.为了保证动画时间点的准确性和连续性,不要设置setTimeOut定时器去卡动画时间,这样可能不准,应当监听css动画的结束状态,然后开始绘制canvas动画;

2.canvas画布的层级需要提到最高,但是注意,动画结束后应及时清理掉canvas画布,否则画布会一直停留在页面上,阻挡页面元素的点击等事件;

3.每次切换浏览器的标签页选中状态,应将动画状态重置,重新加载动画,但是不要重新刷新页面,这样会出现等待白屏,影响用户体验,用到的方法是监听浏览器标签页的可视状态(visibilitychange)。

 document.addEventListener('visibilitychange', function() {})

你可能感兴趣的:(可视化,动效,canvas,大数据)