想了解一下用纯CSS和JS怎么实现一段下雨的动画,于是去CodePen上面搜了一下,发现了很多很有意思的东西。有空可以常去上面逛逛,在对技术产生敬畏的同时也能学到好多好多东西。以下是自己理解的几个代码实现过程,所有源码均出自于CodePen。
代码:github
效果:http://rynxiao.com/The-heavy-rain-in-those-years/
PS: 所有效果均在代码中以注释形式给出,不再额外进行补充,如果想了解更多细节,麻烦参考源码
淅沥淅沥
像一阵细雨打湿你心田,那感觉如此甜蜜。
/**
* 雨滴容器
* 宽度为15px,高度为120px
* 0.5秒内从屏幕上方移动到屏幕90%的高度
* 模仿雨滴的下降过程
*/
.drop {
position: absolute;
bottom: 100%;
width: 15px;
height: 120px;
pointer-events: none;
animation: drop 0.5s linear infinite;
}
@keyframes drop {
0% {
transform: translateY(0vh);
}
75% {
transform: translateY(90vh);
}
100% {
transform: translateY(90vh);
}
}
雨滴容器模仿的是下降的过程,在下降过程的同时,改变真正雨滴的透明度,模仿出雨滴划过的轨迹
/**
* 雨滴
* 宽度为1px,高度为120 * 0.6 = 72px
* 设置从上到下的渐变色,模仿雨滴划过的轨迹
* 0.5s内由不透明变为透明,模仿雨滴下落碰撞到物体之后消失的情景
*/
.stem {
width: 1px;
height: 60%;
margin-left: 7px;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0.25));
animation: stem 0.5s linear infinite;
}
@keyframes stem {
0% {
opacity: 1;
}
65% {
opacity: 1;
}
75% {
opacity: 0;
}
100% {
opacity: 0;
}
}
/**
* 水花
* 雨滴碰撞到地面会溅起水花
* 宽度为15px,高度为10px
* 设置上边框为点状,加上设置圆角,模拟水花溅起时的弧形
* 设置动画,当雨滴下降到地面时,透明度设置为1,同时通过设置缩放
* 模拟水花放大的过程
*/
.splat {
width: 15px;
height: 10px;
border-top: 2px dotted rgba(255, 255, 255, 0.5);
border-radius: 50%;
opacity: 1;
transform: scale(0);
animation: splat 0.5s linear infinite;
/*display: none;*/
}
@keyframes splat {
0% {
opacity: 1;
transform: scale(0);
}
80% {
opacity: 1;
transform: scale(0);
}
90% {
opacity: 0.5;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(1.5);
}
}
源码:https://codepen.io/arickle/pen/XKjMZY
是离愁
犹记当时杏花雨,晓幕晨钟去孤舟。说好不回头,止不住,是离愁。
/**
* 雨滴
* 下落过程透明度由0变为1
* 高度由40px变为5px
*/
.drop {
position: absolute;
background: #fff;
width: 2px;
height: 40px;
opacity: 0;
left: 150px;
animation: fall 0.2s ease-in forwards;
}
@keyframes fall {
0% {
top: -100px;
opacity: 0;
height: 100px;
}
99% {
opacity: 1;
}
100% {
top: 150px;
opacity: 0;
height: 5px;
}
}
雨滴的下降过程中,高度逐渐变小,因此造成了一种下落的堕落感,这点和第一个动画略有不同。
/**
* 波纹
* 沿X轴旋转75度,造成椭圆效果
* 动画效果:
* 中心定格,设置波纹延迟,波纹逐渐变大造成扩散效果
* 先上在下再上,造成波纹的跌宕效果
*/
#ripples {
position: absolute;
width: 300px;
height: 300px;
transform: rotateX(75deg);
}
.ripple {
position: absolute;
/* ... */
border: 6px solid rgba(255, 255, 255, 0.2);
animation: grow ease-in-out forwards 1;
}
.ripple:nth-child(1) {
animation-delay: 0s;
animation-duration: 2s;
}
/* ... */
@keyframes grow {
0% {
width: 1px;
height: 1px;
opacity: 0;
transform: translateY(20px);
}
25% {
opacity: 0.5;
transform: translateY(-10px);
}
50% {
transform: translateY(10px);
}
100% {
width: 250px;
height: 250px;
opacity: 0;
transform: translateY(0);
}
}
所有过程都是中心对齐,然后像周围扩散,例如下面的水珠,模拟的其实就是从中心点的原点上跳过程。
/**
* 水珠
* 中心定位
* 通过控制水花垂直方向位移,造成溅起效果
* 通过控制水平方向便宜,造成运动效果
*/
@keyframes bounce1 {
/* 中心点 */
0% {
left: 150px;
top: 150px;
opacity: 0;
}
/* 透明度 */
5% {
opacity: 0.5;
}
/* 上升 */
50% {
top: 104px;
}
/* 下降,同时左偏 */
100% {
left: 125px;
top: 150px;
opacity: 0;
}
}
源码:https://codepen.io/Yakudoo/pen/doObLw
错过
青春灿烂,炫如夏花。你说看过彩色的瀑布,我摸着你的头,笑得像个傻瓜。
// 整个动画的关键点
// 整个动画其实是由一条条细线组成,根据下落的时差,造成瀑布的效果
// 根据计算出的屏幕宽度,每条细线1px,然后在屏幕上均匀分布360色
function anim() {
window.requestAnimationFrame(anim);
// 通过每次在绘制的细线上在绘制透明黑色,因此最先绘制的就会变暗,造成了尾巴的效果
ctx.fillStyle = repaintColor;
ctx.fillRect(0, 0, w, h);
for(var i = 0; i < total; ++i){
var currentY = dots[i] - 1;
// 不断增加每次线段的长度,绘制每次最亮的那一部分
dots[i] += dotsVel[i] += accelleration;
// 颜色其实在最开始就已经被分配出来了,根据点在的位置而定
ctx.fillStyle = 'hsl('+ colors[i] + ', 80%, 50%)';
ctx.fillRect(occupation * i, currentY, size, dotsVel[i] + 1);
// 从上面出现的条件,也是造成时差的条件
if(dots[i] > h && Math.random() < .01){
dots[i] = dotsVel[i] = 0;
}
}
}
anim();
源码:https://codepen.io/towc/pen/VYbYvQ
路过
一个人的记忆就是座城市,时间腐蚀着一切建筑,把高楼和道路全部沙化。如果你不往前走,就会被沙子掩埋。所以我们泪流满面,步步回头,可是只能往前走。
var RAIN_DROP = function(width, height, toInit, renderer){
this.width = width;
this.height = height;
this.toInit = toInit;
this.renderer = renderer;
this.init();
};
RAIN_DROP.prototype = {
SCALE_RANGE : {min : 0.2, max : 1}, // 缩放比例
VELOCITY_RANGE : {min : -1.5, max : -1}, // 水平速度范围
VELOCITY_RATE : 3, // 垂直速度基值
LENGTH_RATE : 20, // 雨滴长度
ACCELARATION_RATE : 0.01, // 加速度比例
VERTICAL_OFFSET_RATE : 0.04, // 垂直偏移量
FRONT_THRESHOLD : 0.8, // 前景临界值(前面的雨滴不至于过小,后面的雨滴不至于过大)
REFLECTION_RADIUS_RATE : 0.02, // 雨滴水花半径基值
COLOR : 'rgba(255, 255, 255, 0.5)', // 雨滴颜色
RADIUS_RATE : 0.2, // 用于设置全局透明度
THRESHOLD_RATE : 0.6,
init: function(){
// 获取随机缩放比例
this.scale = this.renderer.getRandomValue(this.SCALE_RANGE);
// 雨滴长度
this.length = this.LENGTH_RATE * this.scale;
// 雨滴水平方向速度
this.vx = this.renderer.getRandomValue(this.VELOCITY_RANGE) * this.scale;
// 雨滴垂直方向速度
this.vy = this.VELOCITY_RATE * this.scale;
// 雨滴垂直方向加速度
this.ay = this.ACCELARATION_RATE * this.scale;
// 角度
this.theta = Math.atan2(this.vy, this.vx);
// 用于计算垂直方向是否出边界的偏移量
this.offset = this.height * this.VERTICAL_OFFSET_RATE;
// 雨滴水平坐标值
this.x = this.renderer.getRandomValue({min : 0, max : this.width - this.height * Math.cos(this.theta)});
// 雨滴垂直坐标值
this.y = (this.toInit ? this.renderer.getRandomValue({min : 0, max : this.height}) : 0) - this.offset;
// 水花半径
this.radius = this.length * this.REFLECTION_RADIUS_RATE;
},
render: function(context, toFront){
// 如果雨滴在前面,但是缩放比例小于规定临界值,则去掉雨滴
// 如果雨滴在后面,但是缩放比例大于规定临界值,则去掉雨滴
// 保证了在背景前面的雨滴不至于太小,在背景靠后的雨滴不至于太大
if (toFront && this.scale < this.FRONT_THRESHOLD || !toFront && this.scale >= this.FRONT_THRESHOLD) {
return true;
}
context.save();
context.strokeStyle = this.COLOR;
// 如果雨滴的垂直方向坐标大于了临界值,则以水花的形式展示
// 否则以雨滴下落的方式展示
if (this.y >= this.height * (1 - (1 - this.scale) * this.THRESHOLD_RATE) - this.offset) {
context.lineWidth = 3;
context.globalAlpha = (1 - this.radius / this.length / this.RADIUS_RATE) * 0.5;
context.beginPath();
context.arc(this.x, this.y, this.radius, Math.PI, Math.PI * 2, false);
context.stroke();
context.restore();
this.radius *= 1.05;
if (this.radius > this.length * this.RADIUS_RATE){
return false;
}
} else {
context.lineWidth = 1;
context.beginPath();
context.moveTo(this.x, this.y);
context.lineTo(this.x + this.length * Math.cos(this.theta), this.y + this.length * Math.sin(this.theta));
context.stroke();
context.restore();
// 通过水平速度改变水平位移
this.x += this.vx;
// 通过垂直速度改变垂直位移
this.y += this.vy;
// 通过加速度,增加垂直方向的速度
// 因此雨滴并不是沿着直线运动,而是有一定的弧度
this.vy += this.ay;
}
return true;
}
};
源码:https://codepen.io/K-T/pen/YVYvdW
都是你
曾经想征服全世界,到最后回首才发现,这世界滴滴点点全部都是你。
// 关键点
function loop() {
requestAnimationFrame(loop);
// 不断绘制半透明画布,用来造成尾巴效果
context.fillStyle = 'rgba(0, 0, 0, 0.2)';
context.fillRect(0, 0, canvas.width, canvas.height);
var particle;
var i, j, ilen, jlen;
cursor.position.x += (mouseX - cursor.position.x)*0.1;
cursor.position.y += (mouseY - cursor.position.y)*0.1;
for (i = 0, ilen = particles.length; i < ilen; i++) {
particle = particles[i];
// 通过速度控制角度大小,用来控制流星划过时的速度
particle.angle += particle.speed;
// 以相同转动的速度向鼠标中心点不停的移动
particle.shift.x += (cursor.position.x - particle.shift.x) * particle.speed;
particle.shift.y += (cursor.position.y - particle.shift.y) * particle.speed;
// sin/cos 用来控制画圆
// orbit 半径轨道不断增加, force用来控制轨道系数,最低为0
// Math.sinx * (particle.orbit*particle.force) 表示以particle.orbit*particle.force为半径画圆
// 加上中心点坐标,造成不断围绕中心点旋转的效果
particle.position.x = particle.shift.x + Math.sin(i + particle.angle) * (particle.orbit*particle.force);
particle.position.y = particle.shift.y + Math.cos(i + particle.angle) * (particle.orbit*particle.force);
// 不断增加点的半径,最高为100
particle.orbit += (cursor.orbit - particle.orbit) * 0.01;
context.beginPath();
// 根据点所在的位置,随机颜色
context.fillStyle = "hsl("+((particle.position.x/canvas.width + particle.position.y/canvas.height) * 180) + ", 100%, 70%)";
context.arc(particle.position.x, particle.position.y, particle.size/2, 0, Math.PI*2, true);
context.fill();
}
}
源码:https://codepen.io/vennsoh/pen/DuLbo