前言
在阅读了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对象透明度的方法,因此使用了这个方法。
Ani
类draw()
方法通过改变Ani对象的y
、x
、width
、height
、op
等属性分别实现各个动画过程。这里着重说一下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 直播的疯狂点赞动画是如何实现的?(附完整源码)之后按照自己的思路实现的一个直播点赞效果,对作者表示感谢。
如果有好的修改建议欢迎大家提出,感觉还可以的话,欢迎点赞~来源:oschina
链接:https://my.oschina.net/u/4274358/blog/4273407