人物:Jimmy -> 我;Ivy -> 妻子;Eric -> 同事
这个一个小事件,让我有了实现落雪唯美代码效果并写这篇文章的灵感。
上周五(2022-11-11
),在吃中午饭的路上,我问了下 Eric
。
Jimmy:周五了,有什么剧推荐看看的,周末广州要静默防控的节奏。
Eric:有点老的韩剧,但是很推荐看。《孤单又灿烂的神-鬼怪》貌似是 16
年出的,奇幻与现实结合...
Jimmy:没看过,大概多长?
Eric:有 16
集,每集一个小时多点,我之前周末跟女朋友在看。
Bala,Bala...
当晚,我问了下 Ivy
,要不要看看《孤单又灿烂的神-鬼怪》这部韩剧,同事推荐,评分还不错。
Ivy:我才不看,韩剧不都偶像派那种,有点无脑
Jimmy:那我自己看
过了一会,Ivy
凑过来看了会,啊,真香。
周末两人看了两天,还把两个人看得感动流眼泪了~
不行,这么唯美伤感的片子,不应该只有我们流眼泪💧
糟糕,还有落雪时分,男女主角两人相拥的画面也太美了吧,不行,我得再感动一下 Ivy
,给个小惊喜给她。
实现的效果如下图所示:
如上图所示,我们实现了如下的功能:
사랑해요
的文字。(什么意思?读者自行百度)好,下面我们一个一个来讲解。
秉承代码即文档的理念加上注释理解
我们的 html
文件格式很简单:
<!-- 文本 -->
<div class="iLoveYou">사랑해요</div>
<!-- 心形 -->
<div class="heart"></div>
<!-- 雪花 -->
<canvas id="nightSky"></canvas>
文本处理。这里采用了文本渐变的方式处理:
.iLoveYou {
background: -webkit-linear-gradient(#fff, @red);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
心形❤️跳动。这里只用了一个 div
处理,用到了老生常谈的 ::before
和 ::after
两个伪元素,还有 @keyframe
关键帧知识点。
.heart {
position: absolute;
opacity: 0.8;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(45deg); // 旋转 45 度
width: @heartSize;
height: @heartSize;
background-color:@red;
z-index: 2;
margin-left: calc(-@heartSize / 2);
margin-top: -@heartSize;
box-shadow: -5px 5px 45px @red;
animation: heart 0.8s linear infinite; // 关键帧 heart,每 0.8 秒一次循环,无限循环
&::before, &::after { // 处理心形的椭圆
content: '';
position: absolute;
height: @heartSize;
width: @heartSize;
background-color:@red;
border-radius: 50%;
}
&::before {
top: -50%;
}
&::after {
right: 50%;
}
}
// 关键帧
@keyframes heart {
0% {
transform: rotate(45deg) scale(1.07);
}
80% {
transform: rotate(45deg) scale(1.0);
}
100% {
transform: rotate(45deg) scale(0.8);
}
}
落雪效果。这个功能是本文的重点内容。我们使用 canvas
来完成。
既然是雪花飘落,那么得有雪花吧。我们创建一个雪花类,如下:
// 雪花类
class SnowFlake {
constructor(options) {
this.x = Math.random() * options.width; // x 坐标
this.y = Math.random() * options.height; // y 坐标
this.opacity = Math.random(); // 雪花❄️透明度
this.speedX = this.random(-2, 2); // 水平移动的距离
this.speedY = this.random(0.5, 1); // 垂直移动的距离
this.radius = this.random(0.5, 4); // 雪花❄️的半径
}
// 生成 min ~ max 的数
random(min, max) {
return min + Math.random() * (max - min + 1);
}
}
画布左上角是原点(0, 0),水平往右为 x 轴正向,垂直往下是 y 轴正向
然后,我们根据实际情况,生成足够多的雪花,也就是调用 new SnowFlake({ width, height })
的次数。这时,我们生成了这么多雪花当前的位置。那么,我们接下来就是将雪花在这些位置在画布上画出来。
本文的雪花看成一个个圆来绘制。
// 画圆
ctx.beginPath();
ctx.arc(
particlesArray[i].x,
particlesArray[i].y,
particlesArray[i].radius,
0,
Math.PI*2,
false
);
ctx.fillStyle = gradient;// 画笔的颜色
ctx.fill(); // 填充成实心
现在绘制的雪花位置是固定的。接下来,我们需要计算雪花的下一帧的位置,并更新。
particlesArray[i].x += particlesArray[i].speedX;
particlesArray[i].y += particlesArray[i].speedY;
我们对 x
轴的下一帧坐标进行计算:当前的位置 + 该点在新建雪花对象的随机 x
轴的距离。y
轴的下一帧坐标计算同理。
然后,我们更新当前的雪花坐标位置。通过关键帧监听雪花掉落的函数实现:
// 更新雪花的状态
function updateSnowFallStatus() {
ctx.clearRect(0, 0, width, height); // 清空画布
drawSnowFlakes();
moveSnowFlakes();
requestAnimationFrame(updateSnowFallStatus); // 帧动画
};
还有一个问题:雪花飘落,落出画布之外,那怎么处理呢?
因为我们已经预设了雪花的数量,那么,当雪花飘落,落出画布之外,那么落出的这些点,会重新回到画布上方重新飘落(绘制)。
// 如果雪花掉落在地上,则重新从天空掉落
if(particlesArray[i].y > height) {
particlesArray[i].x = Math.random() * width; // 随机生成位置
particlesArray[i].y = -50; // 固定上方垂直飘落位置
}
最后实现的效果(可体验)👇
经过一段时间开发,我把效果给 Ivy
看 👀
Jimmy:Ivy
,像不像我们周末看的韩剧的某个场景...
说完之后,我满眼星星✨地看着她~她看了看效果,又看了看我
Ivy:咦,咦,咦,这...哈哈哈
周一去上班
Eric:怎样,看了那部韩剧了?
Jimmy:看了,全看完了,还看哭了...
Eric:牛啊,两天就看完了。看哭,不至于吧...
Jimmy:你不知道还有快进,倍速这玩意的吗?哈哈哈~ 还好这剧是 Happy Ending
本文正在参加「金石计划 . 瓜分6万现金大奖」