作者:多一点

转发链接:https://mp.weixin.qq.com/s/EIf1-0HpxYVWgwikoHN9kQ

序言

小伙们,咱们又见面了。
不知道小伙伴们是否还记得

《手把手整理CSS3知识汇总【思维导图】》

《关于前端CSS写法104个知识点汇总(一)》

手把手具体教你用H5实现直播的跋扈狂点赞动画效果实践

《关于前端CSS写法104个知识点汇总(二)》

《前端开拓规范:命名规范、html规范、css规范、js规范》

《手把手教你55 个提高CSS 开拓效率的必备片段》

《深入浅出超好用的 CSS 阴影技巧》

更多的CSS实现技巧干系文章请见文章底部

回归正题

直播有一个很主要的互动:点赞。

为了陪衬直播间的氛围,直播相对付普通视频或者文本内容,点赞常日有两个分外需求:

点赞动作无限次,勾引用户猖獗点赞直播间的所有猖獗点赞,都须要在所有用户界面都动画展现出来(广播用户利用websocket)

我们先来看效果图:

从效果图上我们还看到有几点主要信息:

点赞动画图片大小不一,运动轨迹也是随机的点赞动画图片都是先放大再匀速运动。
快到顶部的时候,是逐渐消逝。
收到大量的点赞要求的时候,点赞动画不扎堆,有条不紊持续涌现。

那么如何实现这些哀求呢?下面先容两种实现办法来实现(底部附完全 demo):

CSS3 实现

用 CSS3 实现动画,显然,我们想到的是用 animation 。

首先看下 animation 合并写法,详细含义就不阐明了,如果须要可以自行理解。

animation:namedurationtiming-functiondelayiteration-countdirectionfill-modeplay-state;

我们开始来一步一步实现。

Step 1: 固定区域,设置基本样式

首先,我们先准备 1 张点赞动画图片:

看一下 HTML 构造。
外层一个构造固定全体显示动画区域的位置。
这里在一个宽 100px ,高 200px 的 div 区域。

<divclass="praise_bubble"><divclass="bubbleb1bl1"></div></div>

.praise_bubble{width:100px;height:200px;position:relative;background-color:#f4f4f4;}.bubble{position:absolute;left:50%;bottom:0;}

Step 2: 运动起来

利用 animation 的帧动画,定义一个 bubble_y 的帧序列。

.bl1{animation:bubble_y4slinear1forwards;}@keyframesbubble_y{0%{margin-bottom:0;}100%{margin-bottom:200px;}}

这里设置运行韶光 4s ;采取线性运动 linear,如果有需求当然也可以利用其他曲线,比如 ease;每个点赞动画只运行 1 次;动画是只须要向前 forwards。

Step 3: 增加渐隐

渐隐效果,利用 opacity 即可。
这里我们固定在末了 1/4 开始渐隐。
修正 bubble_y:

@keyframesbubble_y{0%{margin-bottom:0;}75%{opacity:1;}100%{margin-bottom:200px;opacity:0;}}

Step 4: 增加动画放大效果

在最开始一小段韶光,图片由小变大。

于是我们新增一个动画:bubble_big_1。

这里从 0.3 倍原图放大到 1 倍。
这里把稳运行韶光,比如上面设置,从动画开始到结束统共是 4s,那么这个放大韶光就可以按需设置了,比如 0.5s。

.bl1{animation:bubble_big0.5slinear1forwards;}@keyframesbubble_big_1{0%{transform:scale(.3);}100%{transform:scale(1);}}

Step 5: 设置偏移

我们先定义帧动画:bubble_1 来实行偏移。
图片开始放大阶段,这里没有设置偏移,保持中间原点不变。

在运行到 25% 4 = 1s,即 1s之后,是向左偏移 -8px, 2s 的时候,向右偏移 8px,3s 的时候,向做偏移 15px ,终极向右偏移 15px。

大家可以想到了,这是定义的一个经典的旁边摆动轨迹,“向左向右向左向右” 曲线摆动效果。

@keyframesbubble_1{0%{}25%{margin-left:-8px;}50%{margin-left:8px}75%{margin-left:-15px}100%{margin-left:15px}}

效果图如下:

Step 6: 补齐动画样式

这里预设了一种运行曲线轨迹,旁边摆动的样式,我们在再预设更多种曲线,达到随机轨迹的目的。

比如 bubble_1 的旁边偏移动画轨迹,我们可以修正偏移值,来达到不同的曲线轨迹。

Step 7: JS 操作随机增加节点样式

供应增加点赞的方法,随机将点赞的样式组合,然后渲染到节点上。

letpraiseBubble=document.getElementById("praise_bubble");letlast=0;functionaddPraise(){constb=Math.floor(Math.random()6)+1;constbl=Math.floor(Math.random()11)+1;//bl1~bl11letd=document.createElement("div");d.className=`bubbleb${b}bl${bl}`;d.dataset.t=String(Date.now());praiseBubble.appendChild(d);}setInterval(()=>{addPraise();},300)

在利用 CSS 来实现点赞的时候,常日还须要把稳设置 bubble 的随机延时,比如:

.bl2{animation:bubble_2$bubble_timelinear.4s1forwards,bubble_big_2$bubble_scalelinear.4s1forwards,bubble_y$bubble_timelinear.4s1forwards;}

这里如果是随机到 bl2,那么延时 0.4s 再运行,bl3 延时 0.6s ……

如果是批量更新到节点上,不设置延时的话,那就会扎堆涌现。
随机“ bl ”样式,就随机了延时,然后批量涌现,都会自动错峰显示。
我们还须要增加当前用户手动点赞的动画,这个不须要延时。

其余,有可能同时别人下发了点赞 40 个,业务需求常日是希望这 40 个点赞气泡都能依次涌现,制造持续的点赞氛围,否则下发量大又会扎堆显示了。

那么我们还须要分批打散点赞数量,比如一次点赞的韶光($bubble_time)是 4s, 那么 4s 内,希望同时涌现多少个点赞呢?比如是 10个,那么 40 个点赞,须要分批 4 次渲染。

window.requestAnimationFrame(()=>{//连续循环处理批次render();});

其余还须要手动打消节点。
以防节点过多带来的性能问题。
如下是完全的效果图。

Canvas 绘图实现

这个很随意马虎理解,直接在 canvas 上绘制动画就行,如果不理解 canvas 的,可往后续学习下。

Step 1:初始化

页面元素上新建 canvas 标签,初始化 canvas。

canvas 上可以设置 width 和 height 属性,也可以在 style 属性里面设置 width 和 height。

canvas 上 style 的 width 和 height 是 canvas 在浏览器中被渲染的高度和宽度,即在页面中的实际宽高。
canvas 标签的 width 和 height 是画布实际宽度和高度。

<canvasid="thumsCanvas"width="200"height="400"style="width:100px;height:200px"></canvas>

页面上一个宽 200,高 400 的 canvas 画布,然后全体 canvas 显示在 页面 宽 100,高 200 的区域内。
canvas 画布的内容被等比缩小一倍显示在页面。

定义一个点赞类,ThumbsUpAni,布局函数便是读取 canvas,保存宽高值。

classThumbsUpAni{constructor(){constcanvas=document.getElementById('thumsCanvas');this.context=canvas.getContext('2d')!;this.width=canvas.width;this.height=canvas.height;}}Step 2:提前加载图片资源

将须要随机渲染的点赞图片,先预加载,得到图片的宽高,如果有下载失落败的,则不显示该随机图片即可。
没啥说的,大略易懂。

loadImages(){constimages=['jfs/t1/93992/8/9049/4680/5e0aea04Ec9dd2be8/608efd890fd61486.png','jfs/t1/108305/14/2849/4908/5e0aea04Efb54912c/bfa59f27e654e29c.png','jfs/t1/98805/29/8975/5106/5e0aea05Ed970e2b4/98803f8ad07147b9.png','jfs/t1/94291/26/9105/4344/5e0aea05Ed64b9187/5165fdf5621d5bbf.png','jfs/t1/102753/34/8504/5522/5e0aea05E0b9ef0b4/74a73178e31bd021.png','jfs/t1/102954/26/9241/5069/5e0aea05E7dde8bda/720fcec8bc5be9d4.png'];constpromiseAll=[]asArray<Promise<any>>;images.forEach((src)=>{constp=newPromise(function(resolve){constimg=newImage;img.onerror=img.onload=resolve.bind(null,img);img.src='https://img12.360buyimg.com/img/'+src;});promiseAll.push(p);});Promise.all(promiseAll).then((imgsList)=>{this.imgsList=imgsList.filter((d)=>{if(d&&d.width>0)returntrue;returnfalse;});if(this.imgsList.length==0){logger.error('imgsListloadallerror');return;}})}Step 2:创建渲染工具

实时渲染图片,使其变成一个连贯的动画,很主要的是:天生曲线轨迹。
这个曲线轨迹须要是平滑的均匀曲线。
如果天生的曲线轨迹不平滑的话,那看到的效果就会太突兀,比如上一个是 10 px,下一个便是 -10px,那显然,动画便是忽左忽右旁边闪烁了。

空想的轨迹是上一个位置是 10px,接下来是 9px,然后一贯平滑到 -10px,这样的坐标点便是连贯的,看起来动画便是平滑运行。

随机平滑 X 轴偏移

如果要做到平滑曲线,实在可以利用我们再熟习不过的正弦( Math.sin )函数来实现均匀曲线。

看下图的正弦曲线:

这是 Math.sin(0) 到 Math.sin(9) 的曲线图走势图,它是一个平滑的从正数到负数,然后再从负数到正数的曲线图,完备符合我们的需求,于是我们再须要天生一个随机比率值,让摆动幅度随机起来。

constangle=getRandom(2,10);letratio=getRandom(10,30)((getRandom(0,1)?1:-1));constgetTranslateX=(diffTime)=>{if(diffTime<this.scaleTime){//放大期间,不进行摇摆偏移returnbasicX;}else{returnbasicX+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。
即从最底部,运行到顶部留下,实际上我们在顶部会淡化隐蔽。

constgetTranslateY=(diffTime)=>{returnimage.height/2+(this.height-image.height/2)(1-diffTime);};放大缩小

当运行韶光 diffTime 小于设置的 scaleTime 的时候,按比例随着韶光增大,scale 变大。
超过设置的韶光阈值,则返回终极大小。

constbasicScale=[0.6,0.9,1.2][getRandom(0,2)];constgetScale=(diffTime)=>{if(diffTime<this.scaleTime){return+((diffTime/this.scaleTime).toFixed(2))basicScale;}else{returnbasicScale;}};淡出

同放大逻辑同等,只不过淡出是在运行快到末了的位置开始生效。

constfadeOutStage=getRandom(14,18)/100;constgetAlpha=(diffTime)=>{letleft=1-+diffTime;if(left>fadeOutStage){return1;}else{return1-+((fadeOutStage-left)/fadeOutStage).toFixed(2);}};实时绘制

创建完绘制工具之后,就可以实时绘制了,根据上述获取到的“偏移值”,“放大”和“淡出”值,然后实时绘制点赞图片的位置即可。

每个实行周期,都须要重新绘制 canvas 上的所有的动画图片位置,终极形成所有的点赞图片都在运动的效果。

createRender(){return(diffTime)=>{//差值满了,即结束了0---》1if(diffTime>=1)returntrue;context.save();constscale=getScale(diffTime);consttranslateX=getTranslateX(diffTime);consttranslateY=getTranslateY(diffTime);context.translate(translateX,translateY);context.scale(scale,scale);context.globalAlpha=getAlpha(diffTime);//constrotate=getRotate();//context.rotate(rotateMath.PI/180);context.drawImage(image,-image.width/2,-image.height/2,image.width,image.height);context.restore();};}

这里绘制的图片是原图的 width 和 height。
前面我们设置了 basiceScale,如果图片更大,我们可以把 scale 再变小即可。

constbasicScale=[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);letindex=0;letlength=this.renderList.length;if(length>0){requestAnimationFrame(this.scan.bind(this));}while(index<length){constrender=this.renderList[index];if(!render||!render.render||render.render.call(null,(Date.now()-render.timestamp)/render.duration)){//结束了,删除该动画this.renderList.splice(index,1);length--;}else{//当前动画未实行完成,continueindex++;}}}

这里便是根据实行的韶光来比拟,判断动画实行到的位置了:

diffTime=(Date.now()-render.timestamp)/render.duration

如果开始的韶光戳是 10000,当前是100100,则解释已经运行了 100 毫秒了,如果动画本来须要实行 1000 毫秒,那么 diffTime = 0.1,代表动画已经运行了 10%。

增加动画

每点赞一次或者每吸收到别人点赞一次,则调用一次 start 方法来天生渲染实例,放进渲染实例数组。
如果当前扫描器未开启,则须要启动扫描器,这里利用了 scanning 变量,防止开启多个扫描器。

start(){constrender=this.createRender();constduration=getRandom(1500,3000);this.renderList.push({render,duration,timestamp:Date.now(),});if(!this.scanning){this.scanning=true;requestFrame(this.scan.bind(this));}returnthis;}保持不扎堆

当吸收到大量的点赞数据,且连续多次点赞(直播间人气很旺的时候)。
那么点赞数据的渲染就须要特殊把稳了,否则页面便是一坨一坨的点赞动画。
且衔接失慎密。

thumbsUp(num:number){if(num<=this.praiseLast)return;this.thumbsStart=this.praiseLast;this.praiseLast=num;if(this.thumbsStart+500<num)this.thumbsStart=num-500;constdiff=this.praiseLast-this.thumbsStart;lettime=100;letisFirst=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

再比较

这两种实现办法,都可以知足哀求,那么到底哪种更优呢?

我们来看下两者的数据比拟。
以下为未开启硬件加速的比拟,采取不间断猖獗渲染点赞动画的数据比拟:

整体来说,差异如下:

CSS3 实现大略Canvas 更灵巧,操作更细腻CSS3 内存花费比 Canvas 大,如果开启硬件加速,内存花费更大一些。
推举关于CSS利用技巧知识点干系文章

《手把手整理CSS3知识汇总【思维导图】》

《关于前端CSS写法104个知识点汇总(一)》

《关于前端CSS写法104个知识点汇总(二)》

《前端开拓规范:命名规范、html规范、css规范、js规范》

《手把手教你55 个提高CSS 开拓效率的必备片段》

《深入浅出超好用的 CSS 阴影技巧》

《手把手教你常见的CSS布局办法【实践】》

《你未必知道的49个CSS知识点》

《手把手教你css 中多种边框的实现小窍门【实践】》

《手把手教你深入CSS实现一个粒子动效的按钮》

《CSS变量实现暗黑模式,我的小铺页面已经支持》

《手把手教你利用CSS掌握文本溢出截断省略办理方案合集》

《本日全国哀悼日,手把手教你一段css让全站变灰》

作者:多一点

转发链接:https://mp.weixin.qq.com/s/EIf1-0HpxYVWgwikoHN9kQ