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

前言

在阅读了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 直播的疯狂点赞动画是如何实现的?(附完整源码)之后按照自己的思路实现的一个直播点赞效果,对作者表示感谢。

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一拳小和尚LXY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值