‘jfs/t1/102954/26/9241/5069/5e0aea05E7dde8bda/720fcec8bc5be9d4.png’
];
const promiseAll = [] as Array<Promise>;
images.forEach((src) => {
const p = new Promise(function (resolve) {
const img = new Image;
img.onerror = img.onload = resolve.bind(null, img);
img.src = ‘https://img12.360buyimg.com/img/’ + src;
});
promiseAll.push§;
});
Promise.all(promiseAll).then((imgsList) => {
this.imgsList = imgsList.filter((d) => {
if (d && d.width > 0) return true;
return false;
});
if (this.imgsList.length == 0) {
logger.error(‘imgsList load all error’);
return;
}
})
}
Step 2:创建渲染对象
实时渲染图片,使其变成一个连贯的动画,很重要的是:生成曲线轨迹。这个曲线轨迹需要是平滑的均匀曲线。假如生成的曲线轨迹不平滑的话,那看到的效果就会太突兀,比如上一个是 10 px,下一个就是 -10px,那显然,动画就是忽左忽右左右闪烁了。
理想的轨迹是上一个位置是 10px,接下来是 9px,然后一直平滑到 -10px,这样的坐标点就是连贯的,看起来动画就是平滑运行。
随机平滑 X 轴偏移
如果要做到平滑曲线,其实可以使用我们再熟悉不过的正弦( Math.sin )函数来实现均匀曲线。
看下图的正弦曲线:
这是 Math.sin(0) 到 Math.sin(9) 的曲线图走势图,它是一个平滑的从正数到负数,然后再从负数到正数的曲线图,完全符合我们的需求,于是我们再需要生成一个随机比率值,让摆动幅度随机起来。
const angle = getRandom(2, 10);
let ratio = getRandom(10,30)*((getRandom(0, 1) ? 1 : -1));
const getTranslateX = (diffTime) => {
if (diffTime < this.scaleTime) {// 放大期间,不进行摇摆偏移
return basicX;
} else {
return basicX + ratioMath.sin(angle(diffTime - this.scaleTime));
}
};
scaleTime 是从开始放大到最终大小,用多长时间,这里我们设置 0.1,即总共运行时间前面的 10% 的时间,点赞图片逐步放大。
diffTime,是只从开始动画运行到当前时间过了多长时间了,为百分比。实际值是从 0 --》 1 逐步增大。diffTime - scaleTime = 0 ~ 0.9, diffTime 为 0.4 的时候,说明是已经运行了 40% 的时间。
因为 Math.sin(0) 到 Math.sin(0.9) 曲线几乎是一个直线,所以不太符合摆动效果,从 Math.sin(0) 到 Math.sin(1.8) 开始有细微的变化,所以我们这里设置的 angle 最小值为 2。
这里设置角度系数 angle 最大为 10 ,从底部到顶部运行两个波峰。
当然如果运行距离再长一些,我们可以增大 angle 值,比如变成 3 个波峰(如果时间短,出现三个波峰,就会运行过快,有闪烁现象)。如下图:
Y 轴偏移
这个容易理解,开始 diffTime 为 0 ,所以运行偏移从 this.height --> image.height / 2。即从最底部,运行到顶部留下,实际上我们在顶部会淡化隐藏。
const getTranslateY = (diffTime) => {
return image.height / 2 + (this.height - image.height / 2) * (1-diffTime);
};
放大缩小
当运行时间 diffTime 小于设置的 scaleTime 的时候,按比例随着时间增大,scale 变大。超过设置的时间阈值,则返回最终大小。
const basicScale = [0.6, 0.9, 1.2][getRandom(0, 2)];
const getScale = (diffTime) => {
if (diffTime < this.scaleTime) {
return +((diffTime/ this.scaleTime).toFixed(2)) * basicScale;
} else {
return basicScale;
}
};
淡出
同放大逻辑一致,只不过淡出是在运行快到最后的位置开始生效。
const fadeOutStage = getRandom(14, 18) / 100;
const getAlpha = (diffTime) => {
let left = 1 - +diffTime;
if (left > fadeOutStage) {
return 1;
} else {
return 1 - +((fadeOutStage - left) / fadeOutStage).toFixed(2);
}
};
实时绘制
创建完绘制对象之后,就可以实时绘制了,根据上述获取到的“偏移值”,“放大”和“淡出”值,然后实时绘制点赞图片的位置即可。
每个执行周期,都需要重新绘制 canvas 上的所有的动画图片位置,最终形成所有的点赞图片都在运动的效果。
createRender(){
return (diffTime) => {
// 差值满了,即结束了 0 —》 1
if(diffTime>=1) return true;
context.save();
const scale = getScale(diffTime);
const translateX = getTranslateX(diffTime);
const translateY = getTranslateY(diffTime);
context.translate(translateX, translateY);
context.scale(scale, scale);
context.globalAlpha = getAlpha(diffTime);
// const rotate = getRotate();
// context.rotate(rotate * Math.PI / 180);
context.drawImage(
image,
-image.width / 2,
-image.height / 2,
image.width,
image.height
);
context.restore();
};
}
这里绘制的图片是原图的 width 和 height。前面我们设置了 basiceScale,如果图片更大,我们可以把 scale 再变小即可。
const basicScale = [0.6, 0.9, 1.2][getRandom(0, 2)];
实时绘制扫描器
开启实时绘制扫描器,将创建的渲染对象放入 renderList 数组,数组不为空,说明 canvas 上还有动画,就需要不停的去执行 scan,直到 canvas 上没有动画结束为止。
scan() {
this.context.clearRect(0, 0, this.width, this.height);
this.context.fillStyle = “#f4f4f4”;
this.context.fillRect(0,0,200,400);
let index = 0;
let length = this.renderList.length;
if (length > 0) {
requestAnimationFrame(this.scan.bind(this));
}
while (index < length) {
const render = this.renderList[index];
if (!render || !render.render || render.render.call(null, (Date.now() - render.timestamp) / render.duration)) {
// 结束了,删除该动画
this.renderList.splice(index, 1);
length–;
} else {
// 当前动画未执行完成,continue
index++;
}
}
}
这里就是根据执行的时间来对比,判断动画执行到的位置了:
diffTime = (Date.now() - render.timestamp) / render.duration
如果开始的时间戳是 10000,当前是100100,则说明已经运行了 100 毫秒了,如果动画本来需要执行 1000 毫秒,那么 diffTime = 0.1,代表动画已经运行了 10%。
增加动画
每点赞一次或者每接收到别人点赞一次,则调用一次 start 方法来生成渲染实例,放进渲染实例数组。如果当前扫描器未开启,则需要启动扫描器,这里使用了 scanning 变量,防止开启多个扫描器。
start() {
const render = this.createRender();
const duration = getRandom(1500, 3000);
this.renderList.push({
render,
duration,
timestamp: Date.now(),
});
if (!this.scanning) {
this.scanning = true;
requestFrame(this.scan.bind(this));
}
return this;
}
保持不扎堆
当接收到大量的点赞数据,且连续多次点赞(直播间人气很旺的时候)。那么点赞数据的渲染就需要特别注意了,否则页面就是一坨一坨的点赞动画。且衔接不紧密。
thumbsUp(num: number) {
if (num <= this.praiseLast) return;
this.thumbsStart = this.praiseLast;
this.praiseLast = num;
if (this.thumbsStart + 500 < num)
this.thumbsStart = num - 500;
const diff = this.praiseLast - this.thumbsStart;
let time = 100;
let isFirst = true;
if (this.thumbsInter != 0) {
return;
}
this.thumbsInter = setInterval(() => {
if (this.thumbsStart >= this.praiseLast) {
clearInterval(this.thumbsInter);
this.thumbsInter = 0;
return;
}
this.thumbsStart++;
this.thumbsUpAni.start();
if (isFirst) {
isFirst = false;
time = Math.round(5000 / diff);
}
}, time);
},
这里开启定时器,记录定时器里面处理的 thumbsStart 的值,如果有新增点赞,且定时器还在运行,直接更新最后的 praiseLast 值,定时器会依次将点赞请求全部处理完。
定时器的延时时间 time 根据开启定时器的时候,需要渲染多少点赞动画来决定的,比如需要渲染 100 个点赞动画,我们将 100 个点赞动画分布在 5s 内渲染完。
-
对于热门直播,会同时渲染的动画很多,不会扎堆显示,且动画完全能衔接上,不停的冒泡点赞动画。
-
对于冷门直播,有多余一个的点赞请求,我们能打散到 5s 内显示,也不会扎堆显示。
End
两者运行效果图:
两种方式渲染点赞动画都已经完成,完整源码,源码戳这里 https://github.com/antiter/praise-animation 。
这里还可以体验线上点赞动画,戳这里: https://wqs.jd.com/pglive/index.html
再比较
这两种实现方式,都可以满足要求,那么到底哪种更优呢?
我们来看下两者的数据对比。以下为未开启硬件加速的对比,采用不间断疯狂渲染点赞动画的数据对比:
整体来说,差异如下:
- CSS3 实现简单
- Canvas 更灵活,操作更细腻
- CSS3 内存消耗比 Canvas 大,如果开启硬件加速,内存消耗更大一些。
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
Canvas 大,如果开启硬件加速,内存消耗更大一些。
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-WotVPIZ9-1710874156016)]
[外链图片转存中…(img-Yaf36pqt-1710874156016)]
[外链图片转存中…(img-NGATjn88-1710874156017)]
[外链图片转存中…(img-XuV2YeyZ-1710874156017)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
[外链图片转存中…(img-qHueM62N-1710874156018)]