这件事要从大促提及....每一年411都是豌豆公主的专属樱花节。既然是樱花节,那么不少活动和元素都是围绕着樱花展来的。就好比此次要分享的内容,须要作一个樱花飞舞的画面,固然因为时间问题,可能还不是那么炫酷,这里主要是和你们分享一些思路,具体的效果,你们能够本身发挥想象不是。canvas
这里我们暂且只说一部分需求的描述。整个活动就是一个抽奖活动,点击抽奖按钮以后,须要有一个樱花飞舞的转场动画。动画要求:每一个花瓣都能从大到小的的变化,而且可以作出层次感的透视效果,当所有樱花都缩小完成,开始逐渐飞出屏幕。markdown
动画的动做主要分几个步奏:dom
- 花瓣要能从大到小的变换,咱们能够经过scale来处理。
- 花瓣从大到小的变化须要有层次感,也就是说每一个花瓣都须要有延迟绘制的能力,也就是须要delay方法
- 花瓣依此飞出屏幕。这个的话其实能够沿用上边delay的逻辑,只要一片花瓣延迟了缩小动做,那么理论上以后的动画都会慢半拍。
每一个花瓣都应该拥有本身的属性
这句话应该怎么去理解? 你们不妨这么想,咱们的这一组樱花飞舞的动画其实不是一个花瓣就能完成的,可是若是针对所有花瓣一个个的处理就太麻烦了,因此咱们就须要一个类将花瓣进行抽象,那这个类须要那些东西呢?以下:函数
- 花瓣的图片信息:imgResource
- 花瓣的坐标:x、y
- 花瓣的大小:width、height
- 花瓣的缩放程度:scale
- 最终缩放的倍数:finalSizeScale
- 花瓣的旋转度数:rotate
- 花瓣的透明度:opacity
- 花瓣的变化的系数(包括透明度,大小,移动速度):speed
- 改变花瓣大小与透明度以前的等待时间:delay
- 花瓣的移动前的等待时间:shrinkDelay
代码以下优化
// 生成樱花;
for(let i = 0; i < 20; i++) {
sakuraList[i] = [];
for (let j = 0; j < i; j++) {
sakuraList[i].push(new Sakura(
ctx,
imgInfo,
Math.random() * canvasCenterX * 4 - canvasCenterX * 2, //x
Math.random() * canvasCenterY * 4 - canvasCenterY * 2, //y
1.8, // scale
(Math.random() * 180) * Math.PI/180, // rotate
// 速度这里i越小越快
Math.floor(Math.random() * 3 + 3) / 100, // speed
0, //opacity
canvas, //canvas
Math.floor(Math.random() * 7 + 20) / 100, // final
i * 2, //delay
));
}
}
复制代码
经过上边的代码,咱们不难看出这是个初始化樱花花瓣的过程,其中咱们应该关注的是随机的位置的计算。动画
这里边有一个比较关键的就是,如何能够相对随机均匀的分布花瓣,也许不少同窗第一反应就是用random
,而后经过画布的width、height
来进行随机分布。
这个方法乍一看,感受一切正常,那咱们也用代码实现一下,看看分布效果。ui
Math.random() * canvas.width, //x
Math.random() * canvas.height, //y
复制代码
如图所示,其实这个分布效果并不会很理想。解决方案也很简单就是咱们能够经过画布的中心点进行计算,用代码实现一下~this
Math.random() * canvasCenterX * 4 - canvasCenterX * 2, //x
Math.random() * canvasCenterY * 4 - canvasCenterY * 2, //y
复制代码
这么一看,是否是均匀很多了。 spa
需求上说,我们花瓣的出现须要由大到小的一个变化过程,那么逻辑也就比较简单了,在不考虑其余状况下,咱们只须要定义两个值:设计
- 花瓣在初始化的时候的大小
- 花瓣花瓣最终静止以后的大小
这两个值咱们在初始化的时候就已经定义了,代码片断就是:
class Sakura {
/** * imgResource 图片地址 * x * y * scale 图片缩放 * rotate 旋转 * speed 速度 * opacity 透明度 * finalSizeScale * delay 延迟 */
constructor( imgResource, x = 0, y = 0, scale = 4, rotate = 0, speed = 1, opacity = 0, finalSizeScale = 0.2, delay ) {
this.imgResource = imgResource;
this.width = imgResource.width;
this.height = imgResource.height;
this.x = x;
this.y = y;
this.scale = scale; // 初始化的大小
this.rotate = rotate;
this.speed = speed;
this.opacity = opacity;
this.finalSizeScale = finalSizeScale; // 最终要缩放到的大小
this.delay = delay;
this.shrinkDelay = 7;
}
}
复制代码
那有了这两个值,咱们就能够经过刷新机制进行计算,那先看看效果。
/** * 缩放方法 * sakura就是咱们以前初始化的花瓣对象 */
changeScale(sakura) {
if(sakura.scale > sakura.finalSizeScale){
sakura.scale -= sakura.speed;
} else {
sakura.scale = sakura.finalSizeScale;
}
},
复制代码
那么动画效果是实现了,可是总觉的是有些生硬,那咱们能够加入一些缓动的算子进入,进行一些优化。
/** * 缩放方法 * sakura就是咱们以前初始化的花瓣对象 */
changeScale(sakura) {
if(sakura.scale > sakura.finalSizeScale){
sakura.scale -= ((sakura.scale - sakura.finalSizeScale) * sakura.speed);
} else {
sakura.scale = sakura.finalSizeScale;
}
},
复制代码
如上代码,咱们将缩小的方式进行了一些优化,替换上了一个缓动公式
当前值 += (目标值-当前值)*系数
固然,这个系数你们能够慢慢的去调本身想要的感受,我这里就是个小丑。
透明度的变化其实与缩放是相同的思路,因此函数也是能够直接使用
/** * 透明度变换的方法 * sakura就是咱们以前初始化的花瓣对象 */
changeOpacity(sakura) {
if(sakura.opacity < 1) {
// 当前值 += (目标值-当前值)*系数
sakura.opacity += ((1 - sakura.opacity) * sakura.speed);
} else {
sakura.opacity = 1;
}
},
复制代码
这边透明度的变换就直接使用以前的缓动公式来实现,让咱们看看效果
看样子,貌似没有问题,只是咱们花瓣太多了,显得不那么明显,可是从和上边的动图进行比较,其实已经发生了一些视觉上的变化
须要一个层次感,说白了,就是须要有一个对每个花瓣进行一个延迟设置,以此来实现一个视觉差。那其实咱们能够经过设定一个delay
属性,经过减法操做来实现一个delayAnimation
的方法。
/** * 延迟方法 * sakura就是咱们以前初始化的花瓣对象 */
delayAnimation(sakura) {
sakura.delay > 0 && sakura.delay --;
},
复制代码
delay的方法设计好以后,咱们还须要考虑一点就是对透明度和缩放作一个拦截,要否则delay就没有任何意义了。
drawSakuraAnimation() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (let item of sakuraList) {
for(let item2 of item) {
this.ctx.globalAlpha = item2.opacity;
item2.delay <= 0 && this.changeScale(item2);
item2.delay <= 0 && this.changeOpacity(item2);
if(item2.opacity > 0) {
this.ctx.save();
this.ctx.rotate(item2.rotate);
item2.ctx.drawImage(item2.imgResource, item2.x, item2.y, item2.width*item2.scale, item2.height*item2.scale);
this.ctx.restore();
}
this.delayAnimation(item2);
}
}
window.requestAnimationFrame(this.drawSakuraAnimation);
},
复制代码
那如上,能够看到只有在delay为0以后才会开始执行绘制,那咱们来看看效果。
那能够看到,和以前的动画比起来,确实多了一个渐入的效果,固然时间能够由你们进行优化。
仔细分析一下,其实这是两个动做:
- 依此
- 飞出屏幕
目前来看依此,其实咱们是已经实现了,咱们每个花瓣都执行的是同一套动做流程,可是由于以前的delayAnimation
方法,就会致使每一个花瓣的状态是有差别的,可是整体的运动曲线不变。说白了就是,delay致使有些花瓣执行方法慢半拍。
飞出屏幕的话,咱们又能够用到缓动方法啦~ 可是这里要注意的是,由于咱们不可能一开始就会飞,是须要等到必定的值以后,花瓣才会飞舞。所以须要一个值进行一个判断,这里我用的是透明度
drawSakuraAnimation() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (let item of sakuraList) {
for(let item2 of item) {
this.ctx.globalAlpha = item2.opacity;
item2.delay <= 0 && this.changeScale(item2);
item2.delay <= 0 && this.changeOpacity(item2);
if(item2.opacity > 0) {
this.ctx.save();
this.ctx.rotate(item2.rotate);
item2.ctx.drawImage(item2.imgResource, item2.x, item2.y, item2.width*item2.scale, item2.height*item2.scale);
this.ctx.restore();
}
// 当透明度到达一个值以后会进行一些位移操做
// 当前值 += (目标值-当前值)*系数
if (item2.opacity >= 0.9) {
item2.x += (-600-item2.x)*item2.speed;
item2.y += (-600-item2.y)*item2.speed;
item2.ctx.drawImage(item2.imgResource, item2.x, item2.y, item2.width*item2.scale, item2.height*item2.scale);
}
this.delayAnimation(item2);
}
}
window.requestAnimationFrame(this.drawSakuraAnimation);
},
复制代码
你们不难看出,我这边是在当透明度大于0.9的时候,花瓣就会往一个方向进行移动。接下来咱们看看效果。
由于gif把动画的一些关键帧给省略了,因此略显尴尬,可是请相信我,其实还能够...可是其实我以为可能飞出屏幕太快了,我想等花瓣落地差很少了再飞舞,那容我偷个懒吧,我用一个简单的减法操做一下。
细心的同窗可能以前有看到,我声明的属性里边有一个shrinkDelay
尚未用到,那这个其实就是用来做“依次”的延迟的。来来来~show me the code!
drawSakuraAnimation() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (let item of sakuraList) {
for(let item2 of item) {
this.ctx.globalAlpha = item2.opacity;
item2.delay <= 0 && this.changeScale(item2);
item2.delay <= 0 && this.changeOpacity(item2);
if(item2.opacity > 0) {
this.ctx.save();
this.ctx.rotate(item2.rotate);
item2.ctx.drawImage(item2.imgResource, item2.x, item2.y, item2.width*item2.scale, item2.height*item2.scale);
this.ctx.restore();
}
if (item2.opacity >= 0.9) {
item2.shrinkDelay -= 0.1;
if (item2.shrinkDelay <= 0) {
item2.x += (-600-item2.x)*item2.speed;
item2.y += (-600-item2.y)*item2.speed;
}
item2.ctx.drawImage(item2.imgResource, item2.x, item2.y, item2.width*item2.scale, item2.height*item2.scale);
}
this.delayAnimation(item2);
}
}
window.requestAnimationFrame(this.drawSakuraAnimation);
},
复制代码
能够看到,我就是简单的用shrinkDelay
的自减,来进行一个延迟,不要和我同样懒.....那看一下效果
怎么样?就目前来看,其实咱们的需求基本上是完成了,固然你们能够机进行一些优化~码起来!
通关了这个动画,其实你们会发现,当遇到一个稍微复杂一些动画,咱们就能够进行一个动做拆解,固然不包括那些脑中自带显卡的大神们,23333。
我总结了一下个人拆解:
- 对花瓣进行属性的抽象,就是找共同点
- 随机分布樱花以及优化
- 花瓣入场的缩放过程
- 花瓣入场的透明度变换过程
- 花瓣入场的渐序透视
- 花瓣落地以后飞出屏幕,以及优化
已上,就是我本次的分享,不知道有没有收获呢?要有收获的话,不妨点个赞吧,嘿嘿嘿🌹。同时也欢迎你们提出各类意见与思路。