模仿实现一个直播的点赞动画

一个人想着一个人 提交于 2020-07-26 10:35:32

前言

在阅读了H5 直播的疯狂点赞动画是如何实现的?(附完整源码)这篇文章后感觉这个点赞效果很不错,并且也跟着作者实现了一下这个动画效果。其中css的实现方式很容易理解,但是我在读完作者canvas实现方法之后有不同的实现思路,因此又按照自己的思路实现了一下。实现效果如下:

实现思路

我看到这个动画效果首先想到的,声明一个包含多个小图标对象的数组,通过在不同的位置绘制这些小图标实现这个点赞动画。那么首先我们实现一个小图标的动画。

动画流程分解

要做动画我们首先把整个动画流程分解一下,这个动画包含如下几个:

  • 放大
    图片刚出现时有一个放大动画
  • y轴平移
  • x轴平移
    即图标左右摆动的动画
  • 透明度
    图标消失时的透明度动画

单个图标动画

我们这里首先将问题先简单化处理,制作单个图标的动画。这里为了之后对图标动画的操作,我写了一个Ani类表示图标动画,它包含了图标的位置属性、大小、image对象和动画轨迹相关的属性,还有一个用来绘制的draw方法。定义如下:

        //图标动画类
         class Ani{
             constructor(img,x,y,w,h){
                this.x=x;
                this.y=y;
                this.width=w;
                this.height=h;
                this.image=img;
                //随机生成正弦曲线的波动幅度
                this.step=getRandom(10,60);
                //随机生成y轴的移动速度
                this.spite=getRandom(2,6);
                //正弦函数的摆动频率
                this.frequency=getRandom(50,100);
                //小图标透明度
                this.op=0;
                //随机曲线类型
                Math.random()>=0.5?this.type=1:this.type=2;
                //y轴偏移量,作为正弦函数的参数
                this.dy=0;
             }
             //这里不用全局的context是为了方便复用
             draw(context){ 
                 //y轴平移动画
                 this.y-=this.spite;
                 //x轴摆动动画
                 this.dy+=this.spite;
                 let dx=0;
                 //不同的波动方向
                 this.type==1?dx=Math.sin(this.dy/this.frequency):dx=Math.sin(-this.dy/this.frequency);
                 let x=this.x+dx*this.step;  
                 //放大动画
                 if(this.width<50){
                    this.width+=0.5;
                    this.height+=0.5;
                 }
                 context.drawImage(this.image,x,this.y,this.width,this.height);
                 //消失时的透明度动画
                 if(this.y<50){
                    this.op+=0.05
                    context.fillStyle = `rgba(255,255,255,${this.op})`;
                    context.fillRect(x,this.y,this.width,this.height);
                 }
             }
         }
复制代码

稍微解释一下上面的代码,构造函数中的frequency属性是用来控制正弦函数的摆动频率的,大家可以修改它的值来查看效果,getRandom()方法是用来获得某个区间之间的随机数的函数,线面的源码中给出了源码。

这里设置图标的透明度是通过在指定位置添加透明背景色实现的,在网上查了好多也没又找到直接修改image对象透明度的方法,因此使用了这个方法。

Anidraw()方法通过改变Ani对象的yxwidthheightop等属性分别实现各个动画过程。这里着重说一下x轴的摆动动画的实现,x轴的运动动画是借助正弦函数实现的,js有现成Math.sin()函数供我们使用,正弦函数的0->1->0->-1的曲线完美符合我们需要的运动曲线。这里我们把y轴偏移量dy作为正弦函数的参数,生成0~-1之间的值dx,再使用dx*step获得x轴的偏移量。

Ani类完成之后我们还需要一个启动动画的函数:

    const canvas = document.getElementById('thumsCanvas');
    let context = canvas.getContext('2d');
    let image=new Image("./img/star.png");
    let ani=new Ani(image,250/2-50/2,500-50,20,20);
       //渲染函数
    function render(){
        //context.clearRect(0,0,250,500); // clear canvas
        //添加透明背景色填充,实现拖尾效果
        context.fillStyle = 'rgba(255,255,255,0.5)';
        context.fillRect(0,0,250,500);
        ani.draw(context);
        window.requestAnimationFrame(render)
    }    
    render();
复制代码

多个图标动画

实现了单个图标的动画,那么多个图标的动画就很容易实现了。我们只需要创建一个数组持有多个图标对象,在每次render函数执行时遍历绘制它们。

因为我们需要多个图像的图标对象,因此我们需要实现一个loadImge函数把要加载的图像先缓存到一个imageList数组中,此方法参考上面的文章H5 直播的疯狂点赞动画是如何实现的?(附完整源码)。 代码如下:

        //加载图像
        function loadImage(){
            const images=[
                '../img/red.png',
                '../img/dog.png',
                '../img/cat.png',
                '../img/star.png',
                '../img/zan.png',
            ];
            const promiseList=[];
            images.forEach(element => {
                let p=new Promise((resolve)=>{
                    const img=new Image();
                    img.onload=resolve.bind(null,img)
                    img.src=element;
                })
                promiseList.push(p)
            });
            return new Promise(resolve=>{
                Promise.all(promiseList).then(imgs=>{                        
                        this.imageList=imgs;
                        resolve();
                })
            })
        }
复制代码

然后我们需要把一个方法添加图标对象:

        //添加图标对象到数组
        function tapAdd(){
            let image=this.imageList[this.getRandom(0,5)];
            let ani=new Ani(image,250/2-50/2,500-50,20,20)
            aniList.push(ani)
        }
复制代码

最后我们同样需要一个渲染函数:

       //渲染函数
        function render(){
            //context.clearRect(0,0,250,500); // clear canvas
            context.fillStyle = 'rgba(255,255,255,0.5)';
            context.fillRect(0,0,250,500);
            aniList.forEach((ani,index)=>{
                if(ani.y<-50){
                    ani=null;
                }else{
                    ani.draw(context);
                }
            })
            window.requestAnimationFrame(render)
        }        
        loadImage().then(()=>{
            console.log('图像加载完成')
            render();
            setInterval(tapAdd,100);
        });
复制代码

这里两句context.fillStyle = 'rgba(255,255,255,0.5)';context.fillRect(0,0,250,500);是通过添加半透明填充色实现拖尾效果,如果不需要可以直接换成context.clearRect(0,0,250,500);

完整源码:

 <!DOCTYPE html>
 <html>
     <head>
        <title>cavans点赞动画效果</title>
     </head>
     <body>
        <canvas onclick="tapAdd()" id="thumsCanvas" width="250" height="500" style="width:250px;height:500px;background-color: #f4f4f4;"></canvas>
     </body>
     <script>
         //图标动画类
         class Ani{
             constructor(img,x,y,w,h){
                this.x=x;
                this.y=y;
                this.width=w;
                this.height=h;
                this.image=img;
                //随机生成正弦曲线的波动幅度
                this.step=getRandom(10,60);
                //随机生成y轴的移动速度
                this.spite=getRandom(2,6);
                this.frequency=getRandom(50,100);
                //小图标透明度
                this.op=0;
                //随机曲线类型
                Math.random()>=0.5?this.type=1:this.type=2;
                this.dy=0;
             }
             draw(context){ 
                //y轴动画效果
                 this.y-=this.spite;
                //x轴动画效果 
                 this.dy+=this.spite;
                 let dx=0;
                 //不同的波动方向
                 this.type==1?dx=Math.sin(this.dy/this.frequency):dx=Math.sin(-this.dy/this.frequency);
                 let x=this.x+dx*this.step;  
                 //图像放大动画
                 if(this.width<50){
                    this.width+=0.5;
                    this.height+=0.5;
                 }
                 context.drawImage(this.image,x,this.y,this.width,this.height);
                 //图像消失动画,透明度从0-1
                 if(this.y<50){
                    this.op+=0.05
                    context.fillStyle = `rgba(255,255,255,${this.op})`;
                    context.fillRect(x,this.y,this.width,this.height);
                 }
             }
         }
        //小图标动画对象数组
        let aniList=[];
        const canvas = document.getElementById('thumsCanvas');
        let imageList=[];
        let context = canvas.getContext('2d');
        //加载图像
        function loadImage(){
            const images=[
                '../img/red.png',
                '../img/dog.png',
                '../img/cat.png',
                '../img/start.png',
                '../img/zan1.png',
            ];
            const promiseList=[];
            images.forEach(element => {
                let p=new Promise((resolve)=>{
                    const img=new Image();
                    img.onload=resolve.bind(null,img)
                    img.src=element;
                })
                promiseList.push(p)
            });
            return new Promise(resolve=>{
                Promise.all(promiseList).then(imgs=>{                        
                        this.imageList=imgs;
                        resolve();
                })
            })
        }
        //获取随机数
        function getRandom(min,max){
            return Math.floor(Math.random()*(max-min))+min
        }
        //添加图标
        function tapAdd(){
            let image=this.imageList[this.getRandom(0,5)];
            let ani=new Ani(image,250/2-50/2,500-50,20,20)
            aniList.push(ani)
        }
        //渲染函数
        function render(){
            //context.clearRect(0,0,250,500); // clear canvas
            context.fillStyle = 'rgba(255,255,255,0.5)';
            context.fillRect(0,0,250,500);
            aniList.forEach((ani,index)=>{
                if(ani.y<-50){
                    ani=null;
                }else{
                    ani.draw(context);
                }
            })
            window.requestAnimationFrame(render)
        }        
        loadImage().then(()=>{
            console.log('图像加载完成')
            render();
            setInterval(tapAdd,100);
        });
     </script>
 </html>
复制代码

图片素材

zan.png

cat.png dog.png heart.png star.png

最后

以上就是自己在阅读了H5 直播的疯狂点赞动画是如何实现的?(附完整源码)之后按照自己的思路实现的一个直播点赞效果,对作者表示感谢。

如果有好的修改建议欢迎大家提出,感觉还可以的话,欢迎点赞~

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!