• js canvas 粒子动画 电子表


     前言

    从我接触canvas的第一天就觉得canvas很有趣,想搞点事情,这几天终于忍不住了,于是他来了。 

    先看效果

                         

    这里我做了四个大家有兴趣可以看完文章,做一个自己喜欢的动画。

    思路

    开始做之前,我们先分析一下这种粒子动画实现的原理,绘制的内容是由许多个带有颜色像素点构成,每个像素点在画布上都有自己的坐标。首先获取到要绘制的内容的像素点信息的数组(目标数组)例如 

    [
        {x:10, y:20, color: 'rgba(255, 122, 122)'},
        {x:11, y:20, color: 'rgba(255, 122, 122)'},
        {x:12, y:20, color: 'rgba(255, 122, 122)'},
    ]

     然后我们就可以让这些像素点从某些特定的位置,以某种特定的方式,移动到目标位置,动画就完成了。

    实现

    1.获取目标数组

    我们先说一下 canvas 的 getImageData() ,该方法返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据。

    对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:

    • R - 红色 (0-255)
    • G - 绿色 (0-255)
    • B - 蓝色 (0-255)
    • A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)

    真实样子是这个样子的

    0,1,2,3 4,5,6,7 8,9,10,11
    12,13,14,15 16,17,18,19 20,21,22,23

    每四个值为一组,用来表示一个像素点的信息,每一个单元格代表一个像素。

    先在一个canvas中绘制想要的内容,通过getImageData()获得像素信息,我们发现ImageData 对象的信息和我们想象中的目标数组不大一样,我们要将ImageData对象处理一下,我们将其每四个划分为一组,重新定义索引,例如我们在一个12px宽的画布中,经过分析不难发现坐标与索引之间的关系,分两种情况 n<12(画布的宽度) 时坐标为((n+1)%12, n+1),n>12时坐标为((n+1)%12, parseInt((n+1)/ 12))

    0(0,0) 1(0,1) .. n((n+1)%12, n+1) 11(0,11)
    .. ..       ..          ..           ..            
         

    n((n+1)%12, parseInt((n+1)/ 12))

     

    到这里功能是实现了,但是如果操作的内容很大,像素点很多,后期操作的像素点越多性能就越差,有没有什么办法可以稀释一下这些像素呢,当然有!我们可以隔一个像素取一个像素,这样像素点瞬间就减少了一倍,同理我们隔两个隔三个隔n个,这样我们就可以定义一个参数用来控制像素的稀释度

    下面的事情就简单了,用代码实来现这一步

    /*
    * @ ImageDataFormat
    * @ param { pixels 需要格式化的ImageData对象, n 稀释度 }
    * @ return { Array }
    */ 
    
     function ImageDataFormat(pixels, n){
        n = n*4
    var arr = [], //目标数组     temPixel = {}, //目标数组中存放像素信息的对象     x = 0, //像素的x坐标     y = 0 //像素的y坐标 for (var i=0;i<pixels.data.length;i+=n){       //过滤纯色背景提高性能,如背景色不可去掉可省略判断 if(pixels.data[i] !== 0 || pixels.data[i+1] !== 0 || pixels.data[i+2] !== 0 ){ var index = (i+1) / 4 //每四个划分为一组,重新定义索引 if(index > timeDom.width){ x = index % timeDom.width y = parseInt(index / timeDom.width) }else{ x = index y = 0 } temPixel = { R: pixels.data[i], G: pixels.data[i+1], B: pixels.data[i+2], A: pixels.data[i+3], I:i, X:x, Y:y } arr.push(temPixel) } }
        return arr }

    2.将目标数组绘制到画布上

    2.1在画布的指定位置画一个圆(一个像素点)

      /**
     * @ drawArc
     * @ param{ ctx 画布,,x x坐标,y y坐标,color 颜色}
     */
    function drawArc(ctx, x, y, color){
      x = x 
      y = y 
      ctx.beginPath();
      ctx.fillStyle = color
      ctx.strokeStyle = color
      ctx.arc(x,y,0.5,0,2*Math.PI);
      ctx.closePath()
      ctx.fill()
    }

    2.1将点连成线,线构成面

      /**
     * 画路径
     * @param { points 格式化好的目标数组, crx 画布}
     */
    function draw_path(points, ctx){

      for(var i=0;i < points.length-1;i++){

        var color = 'rgba(' + points[i].R + ',' + points[i].G + ',' + points[i].B + ')', x, y drawArc(ctx,points[i].X,points[i].Y, color) } }

    到此我们就画出了动画的其中一帧,下面我们就要让这一帧动起来

    2.2动起来

    我们的动画进行其实很简单

    1.画第一帧

    2.清空画布

    3.画下一帧

    4.在清空

    ....

    但是想让这个动画流畅的进行起来我们还要在了解一下tween(缓动动画), window.requestAnimationFrame()

    tween 我们值列举一种其他 形式感兴趣的可以自己查一下

    /*
    * @ 参数描述
    * @ t 动画执行到当前帧所经过的时间
    * @ b 起始值
    * @ c 总位移值
    * @ d 持续时间
    */
    function easeInOutExpon(t,b,c,d){
        if (t==0) return b;
        if (t==d) return b+c;
        if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
        return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
    }

     window.requestAnimationFrame()

    准备工作做好了可以开工了

    我么只需要将前面的函数稍微改动一下他就动起来了

      function ShowTimeInit(pixels, n){
            n = 4*n
          var arr = [], temPixel = {}, x = 0, y = 0
          for (var i=0;i<pixels.data.length;i+=n){
            if(pixels.data[i] !== 0 ||  pixels.data[i+1] !== 0 || pixels.data[i+2] !== 0 ){
              var index = parseInt ((i+1) / 4)
              if(index > timeDom.width){
                x = index % timeDom.width
                y = parseInt(index / timeDom.width) 
              }else{
                x = index
                y = 0
              }
              temPixel = {
                R: pixels.data[i], 
                G: pixels.data[i+1], 
                B: pixels.data[i+2], 
                A: pixels.data[i+3], 
                I:i,
                X:x,
                Y:y
              }
              
              arr.push(temPixel)
            }
    
          }
          var step = requestAnimationFrame(function(){draw_path(arr, ShowTime, step)})
          
      }
      /**
     * 画路径
     * @param path 路径
     */
    function draw_path(points, ctx, step){
        ShowTime.clearRect(0,0,ShowTimeDom.width,ShowTimeDom.height);
        var pointX, pointY, randomX, randomY
        for(var i=0;i < points.length-1;i++){
            switch (mode){
                case 'left':
                    pointX = randomNum(0,0)
                    pointY = randomNum(0,100)
                    randomX = 0
                    randomY = Math.random() + Math.random()*3000
                    break;
                case 'center':
                    pointX = 80
                    pointY = 50
                    randomX = Math.random() + Math.random()*3000
                    randomY = Math.random() + Math.random()*3000
                    break;
                case 'random':
                    pointX = 0
                    pointY = 0
                    randomX = Math.random() + Math.random()*3000
                    randomY = Math.random() + Math.random()*3000
                    break;        
                case 'flow':
                    pointX = 0
                    pointY = 0
                    randomX = i
                    randomY = i
                    break;
            }
    
          var color = 'rgba(' + points[i].R + ',' +  points[i].G + ','  + points[i].B + ')', x, y
           x = easeInOutExpon(nowDuration + randomX, pointX, points[i].X-pointX, duration)
           y = easeInOutExpon(nowDuration + randomY, pointY, points[i].Y-pointY, duration)
          drawArc(ctx,x, y, color)
          
        }
        nowDuration += 1000/60
            if(duration <= nowDuration){
            window.cancelAnimationFrame(step);
        }else{
            requestAnimationFrame(function(){draw_path(points, ctx, step)})
        }
        
    }

    附上完整代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <style>
      
      </style>
      <title>电子时钟</title>
    </head>
    <body>
      <canvas id="HidenTime" width="300" height="100" style="display: none"> </canvas>
      <canvas id="ShowTime" width="300" height="100"> </canvas>
    </body>
    <script>
      function GetTime(){
        this._Hours = ''
        this._Minutes = ''    
        this._Seconds = ''
      }
      GetTime.prototype = {
        constructor: GetTime,
        get Hours(){
          this._Hours = new Date().getHours()
          if(this._Hours > 9){
            return this._Hours
          }else{
            return "0" + this._Hours
          }
        },
        get Minutes(){
          this._Minutes = new Date().getMinutes()
          if(this._Minutes > 9){
            return this._Minutes
          }else{
            return "0" + this._Minutes
          }
        },
        get Seconds(){
          this._Seconds = new Date().getSeconds()
          if(this._Seconds > 9){
            return this._Seconds
          }else{
            return "0" + this._Seconds
          }
        },
        formTime:function(){
            return this.Hours + ':' + this.Minutes + ':' + this.Seconds
        }
      }
      var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
                                window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
      var cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
      var duration = 3000, nowDuration = 0
      var timeDom = document.getElementById("HidenTime")
      var time = timeDom.getContext('2d')
      var ShowTimeDom = document.getElementById("ShowTime")
      var ShowTime = ShowTimeDom.getContext('2d')
      time.clearRect(0,0,timeDom.width,timeDom.height);
      var nowTime = new GetTime()
      var showTime = nowTime.formTime()
      var modes = ['left', 'random', 'center', 'flow']
      var mode = modes[0]
      time.font="50px Verdana";
      // 创建渐变
      var gradient=time.createLinearGradient(0,0,timeDom.width,0);
      gradient.addColorStop("0","magenta");
      gradient.addColorStop("0.5","blue");
      gradient.addColorStop("1.0","red");
      // 用渐变填色
      time.fillStyle=gradient;
      time.fillText(showTime,10,60);
      var pixels = time.getImageData(0,0,300,100)
      ShowTimeInit(pixels, 2)
      setInterval(function(){
        
        mode = modes[randomNum(0,3)]
        //mode = modes[3]
        time.clearRect(0,0,timeDom.width,timeDom.height);
        nowDuration = 0
        showTime = nowTime.formTime()
        time.fillText(showTime,10,60);
        pixels = time.getImageData(0,0,300,100)
        ShowTimeInit(pixels, 2)
      }, 5000)
      function ShowTimeInit(pixels, n){
            n = 4*n
          var arr = [], temPixel = {}, x = 0, y = 0
          for (var i=0;i<pixels.data.length;i+=n){
            if(pixels.data[i] !== 0 ||  pixels.data[i+1] !== 0 || pixels.data[i+2] !== 0 ){
              var index = parseInt ((i+1) / 4)
              if(index > timeDom.width){
                x = index % timeDom.width
                y = parseInt(index / timeDom.width) 
              }else{
                x = index
                y = 0
              }
              temPixel = {
                R: pixels.data[i], 
                G: pixels.data[i+1], 
                B: pixels.data[i+2], 
                A: pixels.data[i+3], 
                I:i,
                X:x,
                Y:y
              }
              
              arr.push(temPixel)
            }
    
          }
          var step = requestAnimationFrame(function(){draw_path(arr, ShowTime, step)})
          
      }
      /**
     * 画路径
     * @param path 路径
     */
    function draw_path(points, ctx, step){
        ShowTime.clearRect(0,0,ShowTimeDom.width,ShowTimeDom.height);
        var pointX, pointY, randomX, randomY
        for(var i=0;i < points.length-1;i++){
            switch (mode){
                case 'left':
                    pointX = randomNum(0,0)
                    pointY = randomNum(0,100)
                    randomX = 0
                    randomY = Math.random() + Math.random()*3000
                    break;
                case 'center':
                    pointX = 80
                    pointY = 50
                    randomX = Math.random() + Math.random()*3000
                    randomY = Math.random() + Math.random()*3000
                    break;
                case 'random':
                    pointX = 0
                    pointY = 0
                    randomX = Math.random() + Math.random()*3000
                    randomY = Math.random() + Math.random()*3000
                    break;        
                case 'flow':
                    pointX = 0
                    pointY = 0
                    randomX = i
                    randomY = i
                    break;
            }
    
          var color = 'rgba(' + points[i].R + ',' +  points[i].G + ','  + points[i].B + ')', x, y
           x = easeInOutExpon(nowDuration + randomX, pointX, points[i].X-pointX, duration)
           y = easeInOutExpon(nowDuration + randomY, pointY, points[i].Y-pointY, duration)
          drawArc(ctx,x, y, color)
          
        }
        nowDuration += 1000/60
            if(duration <= nowDuration){
            window.cancelAnimationFrame(step);
        }else{
            requestAnimationFrame(function(){draw_path(points, ctx, step)})
        }
        
    }
      /**
     * 画圆
     */
    function drawArc(ctx, x, y, color){
      x = x 
      y = y 
      ctx.beginPath();
      ctx.fillStyle = color
      ctx.strokeStyle = color
      ctx.arc(x,y,0.5,0,2*Math.PI);
      ctx.closePath()
      ctx.fill()
    }
    
    
    
    
    
    
    /*
    * 参数描述
    * t 动画执行到当前帧所经过的时间
    * b 起始值
    * c 总位移值
    * d 持续时间
    */
    function easeInOutExpon(t,b,c,d){
        if (t==0) return b;
        if (t==d) return b+c;
        if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
        return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
    }
    
    //生成从minNum到maxNum的随机数
    function randomNum(minNum, maxNum) {
      switch (arguments.length) {
        case 1:
          return parseInt(Math.random() * minNum + 1, 10);
          break;
        case 2:
          return parseInt(Math.random() * ( maxNum - minNum + 1 ) + minNum, 10);
          break;
        default:
          return 0;
          break;
      }
    }
    </script>
    </html>
    View Code

     总结

    当一个新想法出现时,先去github和博客上找一找,看看有没有大佬做过,大佬们的的思路是什么,有什么自己没想到的细节,感觉差不多了在动手去做。

  • 相关阅读:
    vue /deep/ ::v-deep >>> 深度选择器
    雪碧图优缺点
    自适应高度文本框 react contenteditable
    textarea 高度自适应
    UMI.js开发知识总结
    flex布局下img图片变形的解决方法
    umi model 注册
    HEVC标准介绍
    《推荐系统》阅读笔记
    互联网名词诙谐解释
  • 原文地址:https://www.cnblogs.com/Qqqing/p/11468644.html
Copyright © 2020-2023  润新知