三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

纯CSS实现下雨动画:线性渐变与关键帧动画的创意应用

纯CSS实现下雨动画:线性渐变与关键帧动画的创意应用

1. 项目概述:用CSS“画”一场雨

前端开发做久了,总想找点乐子。当静态页面和常规动效已经满足不了你的创作欲时,用纯CSS模拟一些自然现象就成了一个绝佳的“玩票”选择。今天要聊的,就是如何用纯粹的CSS代码,在网页上“下”一场雨。这听起来有点天方夜谭,毕竟CSS是样式表语言,不是动画引擎。但恰恰是这种“用非专业工具实现专业效果”的思路,最能锻炼我们对CSS属性的深度理解和创造性思维。

这个“简单的下雨效果”项目,核心目标不是追求物理引擎级别的逼真模拟,而是用最精简的代码,通过CSS的动画和伪元素等特性,营造出一种视觉上可信的、连续的雨滴下落氛围。它非常适合用在个人博客的背景、作品集的装饰区块,或者一些需要营造特定情绪的小型H5页面中。实现原理并不复杂,但其中关于性能优化、动画节奏控制以及视觉细节处理的技巧,却是每个前端开发者都应该掌握的实战经验。接下来,我们就从零开始,拆解这场“雨”是如何一滴滴落下的。

2. 核心思路与方案设计

2.1 效果拆解:雨滴的视觉构成

在动手写代码之前,我们需要像画家观察景物一样,先拆解“下雨”这个视觉现象。一场雨由无数雨滴组成,而每一滴雨在屏幕这个2D平面上,可以抽象为几个关键特征:

  1. 形态:现实中下落的雨滴由于速度很快,在视觉上通常是一条细长的、略带模糊的直线或短线,顶端可能稍圆,末端尖锐。在CSS中,我们可以用一个非常窄的矩形(div)加上圆角来近似模拟。
  2. 运动:雨滴基本是垂直向下运动,但可能伴有轻微的随机左右偏移,模拟风的效果。运动速度有快有慢,形成层次感。
  3. 出现与消失:雨滴从视窗上方之外出现,下落穿过整个视窗,最后在视窗下方之外消失。这是一个典型的“入场-过场-退场”动画。
  4. 层次与密度:远处的雨滴看起来细、密、速度慢且对比度低;近处的雨滴看起来粗、疏、速度快且对比度鲜明。我们需要用多个图层来表现这种空间纵深。

基于以上分析,我们的技术方案就清晰了:创建多个代表不同层次雨滴的容器,每个容器内通过伪元素批量生成雨滴线条,并为这些线条赋予随机的动画延迟、持续时间和位置,从而组合成连绵不断的雨景。

2.2 技术选型:为什么是纯CSS?

你可能会问,用JavaScript(JS)来实现不是更灵活吗?确实,JS可以更精确地控制每一个雨滴的物理属性。但我们这个项目的标题和初衷就是“有趣的CSS”,其价值在于:

  • 零依赖与高性能:纯CSS动画通常由浏览器合成器线程处理,性能开销极低,尤其是在移动设备上,比用JS频繁操作DOM要流畅得多。
  • 挑战与乐趣:在CSS的约束下解决问题,能极大提升对animation@keyframestransformopacity等属性的掌握深度。
  • 优雅降级:即使JS被禁用或加载失败,动画效果依然存在(虽然可能缺少交互)。
  • 学习价值:这是理解CSS动画性能瓶颈(如重绘与重排)、以及如何利用硬件加速的绝佳案例。

因此,我们将完全使用HTML和CSS来完成。HTML结构将极其简单,主要工作都在CSS中。

2.3 整体架构设计

我们将设计三层雨滴来模拟景深效果:

  • 远景层:雨滴细、多、透明度低、下落速度慢。负责营造雨幕的背景氛围。
  • 中景层:雨滴适中,是视觉效果的主体。
  • 近景层:雨滴粗、少、透明度高、下落速度快。能带来较强的动感和视觉冲击。

每一层都是一个独立的<div>容器,我们将其绝对定位,铺满整个屏幕。雨滴本身并不直接写在HTML里,而是通过容器的::before::after伪元素,配合CSS的线性渐变背景来“画”出多条雨滴,然后让这个背景图运动起来。这是实现大批量雨滴且保持低DOM复杂度的关键技巧。

注意:使用伪元素和背景动画的方式,而不是创建成百上千个独立的<div>,是为了最大限度地减少浏览器需要处理的DOM节点数量,这对于性能至关重要。每一个DOM元素都是性能开销,而伪元素和背景图的开销要小得多。

3. 核心细节解析与实操要点

3.1 雨滴的绘制:线性渐变的妙用

如何用CSS“画”出一条雨滴?最优雅的方式是使用linear-gradient线性渐变。一条雨滴本质上是一个从上到下,从半透明到透明(或从一种颜色到另一种颜色)的细长条。

.raindrop { /* 这是一个示例,实际我们不会单独创建这么多元素 */ width: 1px; height: 60px; background: linear-gradient(to bottom, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 100%); border-radius: 50% 50% 20% 20%; /* 让顶部和底部呈现不同的圆角,模拟水滴形状 */ }

但在我们的方案中,我们不会为每一滴雨创建一个这样的元素。相反,我们为整个雨层容器创建一个巨大的背景图,这个背景图由许多这样的“雨滴条纹”平铺而成。这通过background-image配合多个linear-gradient来实现。

.rain-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; /* 关键:创建一个包含多条透明渐变的背景图像 */ background-image: linear-gradient(to bottom, transparent 90%, rgba(255,255,255,0.2) 100%), linear-gradient(to bottom, transparent 85%, rgba(255,255,255,0.15) 100%); /* 让背景图在X轴上重复,形成多列雨滴 */ background-size: 4px 100%; /* 每个背景条宽4px,高度100%容器 */ background-repeat: repeat-x; }

这里,background-size: 4px 100%定义了每个背景单元的宽度是4px(即雨滴之间的水平间距),高度撑满容器。background-repeat: repeat-x让这个单元在水平方向重复铺满,瞬间就形成了整排的雨滴。我们叠加多个不同透明度和截止点的渐变,可以让雨滴看起来更自然,有粗细和透明度的变化。

3.2 动画驱动:@keyframes 与 animation

雨滴下落的核心是垂直移动。我们将通过@keyframes定义一个从上方移动到下方的动画,并将其应用到背景图的位置属性上。

@keyframes fall { from { background-position: 0 -100%; /* 背景图从顶部之外开始 */ } to { background-position: 0 100%; /* 背景图移动到容器底部之外 */ } } .rain-layer { animation: fall 1.5s linear infinite; }

background-position的y轴从-100%100%,意味着背景图从完全位于容器上方,运动到完全位于容器下方,从而产生了雨滴连续不断穿过屏幕的效果。animationinfinite参数让这个动画无限循环。

3.3 随机性与层次感的关键:CSS变量与计算

一场真实的雨,雨滴不会如此整齐划一。我们需要引入随机性:不同的下落速度、不同的粗细、不同的透明度。但CSS本身没有随机函数。我们如何模拟?

技巧一:使用CSS变量和动画延迟。我们可以为每一层雨定义不同的动画时长和延迟,并利用nth-child或为不同层直接设置不同类名来实现差异。

.rain-layer--far { /* 远景:慢、细、透明 */ --drop-width: 1px; --animation-duration: 2.5s; opacity: 0.3; } .rain-layer--mid { /* 中景:中等 */ --drop-width: 1.5px; --animation-duration: 1.8s; opacity: 0.6; } .rain-layer--near { /* 近景:快、粗、实 */ --drop-width: 2px; --animation-duration: 1s; opacity: 0.9; } .rain-layer { background-size: var(--drop-width) 100%; animation: fall var(--animation-duration) linear infinite; }

技巧二:多层叠加与错位。仅仅三层还不够自然。我们可以创建多个同属“远景”或“中景”的<div>,但给它们设置不同的background-position初始值(水平偏移)和animation-delay,这样即使动画参数相同,雨滴的起始位置和时间也不同,叠加起来就更像随机雨幕。

<div class="rain-container"> <div class="rain-layer rain-layer--far" style="--start-pos: 0%;"></div> <div class="rain-layer rain-layer--far" style="--start-pos: 33%;"></div> <div class="rain-layer rain-layer--far" style="--start-pos: 66%;"></div> <div class="rain-layer rain-layer--mid"></div> <div class="rain-layer rain-layer--near"></div> </div>
.rain-layer--far { background-position: var(--start-pos, 0) -100%; }

实操心得animation-delay使用负值是一个高级技巧。animation-delay: -0.5s;意味着动画会从它整个周期的0.5秒处开始播放。这样,你可以让多个相同周期的动画实例处于不同的播放阶段,从而立即形成一个“已经在下雨”的连续状态,而不是等待所有雨滴从起点开始。这对于背景氛围动画非常有用。

4. 完整实现步骤与代码详解

4.1 HTML结构搭建

HTML结构极其简洁,就是一个全屏的容器,里面嵌套多个雨层。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>纯CSS下雨效果</title> <link rel="stylesheet" href="style.css"> <style> /* 基础样式,为演示提供深色背景 */ body { margin: 0; padding: 0; height: 100vh; background: linear-gradient(to bottom, #0a0a2a, #1a1a40); overflow: hidden; display: flex; justify-content: center; align-items: center; color: #ccc; font-family: sans-serif; } .demo-text { position: relative; z-index: 10; text-align: center; padding: 2em; background: rgba(0, 0, 0, 0.5); border-radius: 10px; } </style> </head> <body> <div class="demo-text"> <h1>CSS模拟下雨效果</h1> <p>观察背景,这是一场由纯CSS驱动的雨。</p> </div> <!-- 下雨效果容器 --> <div class="rain-container"> <!-- 远景层 (多复制几个,增加密度和随机感) --> <div class="rain-layer layer-far"></div> <div class="rain-layer layer-far" style="--x-offset: 20px;"></div> <div class="rain-layer layer-far" style="--x-offset: 40px;"></div> <!-- 中景层 --> <div class="rain-layer layer-mid"></div> <!-- 近景层 --> <div class="rain-layer layer-near"></div> </div> </body> </html>

4.2 CSS核心样式实现

以下是style.css的完整内容,包含了所有核心样式和动画。

/* style.css */ .rain-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; /* 确保雨滴不会干扰页面其他元素的交互 */ z-index: 1; } /* 雨滴层的通用样式 */ .rain-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; /* 使用多个线性渐变叠加,创建雨滴条纹。 格式:linear-gradient(方向, 颜色 停止点, 颜色 停止点...) 这里创建从透明到半透明白色再到透明的条纹,模拟雨滴的拖尾和模糊感 */ background-image: linear-gradient(to bottom, transparent 0%, rgba(255, 255, 255, 0.1) 10%, rgba(255, 255, 255, 0.3) 30%, transparent 100% ); /* 关键:背景图尺寸。宽度(--drop-width)控制雨滴间距,高度100% */ background-size: var(--drop-width, 2px) 100%; background-repeat: repeat-x; /* 初始背景位置。--x-offset用于水平错位,y轴从-100%开始(屏幕上方) */ background-position: calc(var(--x-offset, 0px) - var(--drop-width, 2px)) -100%; /* 应用下落动画 */ animation: fall-down var(--duration, 1s) linear infinite; /* 使用负延迟让动画立即进入循环状态的不同阶段 */ animation-delay: var(--delay, 0s); opacity: var(--opacity, 0.7); } /* 下落动画定义 */ @keyframes fall-down { from { /* 从屏幕上方开始 */ background-position-y: -100%; } to { /* 移动到屏幕下方 */ background-position-y: 100vh; /* 使用视窗高度单位,更精确 */ } } /* 远景层样式:细、慢、淡 */ .layer-far { --drop-width: 1px; /* 雨滴很细 */ --duration: 3s; /* 下落速度慢 */ --opacity: 0.2; /* 透明度低 */ --delay: -0.7s; /* 动画延迟 */ /* 可以增加背景图复杂度,让远景雨滴更稀疏模糊 */ background-image: linear-gradient(to bottom, transparent 80%, rgba(255,255,255,0.05) 95%, transparent 100%); } /* 中景层样式:适中 */ .layer-mid { --drop-width: 1.8px; --duration: 1.8s; --opacity: 0.5; --delay: -0.3s; } /* 近景层样式:粗、快、实 */ .layer-near { --drop-width: 2.5px; --duration: 1s; --opacity: 0.8; --delay: -0.1s; /* 近景雨滴可以更“实”一些,减少模糊 */ background-image: linear-gradient(to bottom, transparent 0%, rgba(255, 255, 255, 0.9) 15%, rgba(255, 255, 255, 0.2) 40%, transparent 100% ); } /* 为了增加随机感,我们可以利用:nth-child选择器微调同一层内不同实例的参数 */ .layer-far:nth-child(2) { --delay: -1.5s; --x-offset: 15px; } .layer-far:nth-child(3) { --delay: -2.2s; --x-offset: 30px; opacity: 0.15; }

4.3 代码逐行解析与参数调整指南

  1. pointer-events: none;:这个属性非常重要。它让.rain-container及其子元素不会响应鼠标事件(如点击、悬停),这样雨滴效果就不会盖住页面正文内容的交互。
  2. background-image:这是我们“画”雨滴的笔刷。linear-gradientto bottom表示从上到下的渐变。transparentrgba(255,255,255,alpha)的交替使用,创造了一条不透明的“线”(雨滴)和周围透明的区域。调整颜色和透明度(alpha)值可以改变雨滴的亮度和虚实程度。调整百分比停止点(如10%,30%)可以改变雨滴的“长度”和头尾的渐变效果。
  3. background-size: var(--drop-width, 2px) 100%;:这是控制雨滴密度和粗细的核心。--drop-width这个CSS自定义属性(变量)在后面的.layer-far等类中被定义。宽度值越小,雨滴看起来越细,同时水平重复的雨滴列数就越多(因为背景图在水平方向重复铺满)。高度100%确保渐变条纹贯穿整个容器。
  4. background-position:初始的background-position-y: -100%让背景图(也就是第一排雨滴)从容器顶部之外开始。在动画中,我们将其改变为100vh(视窗高度),使其移动到屏幕底部之外。background-position-x我们通过--x-offset变量进行微调,让不同层的雨滴水平起始位置错开,避免完全对齐显得生硬。
  5. animation属性fall-down是动画名称,var(--duration)控制单次动画周期时长(速度),linear是匀速运动,infinite无限循环。var(--delay),特别是负值延迟,用于错开各层雨滴的动画相位。
  6. CSS变量(Custom Properties):我们大量使用了--drop-width--duration--opacity--delay等变量。这使得在HTML中通过类名(如layer-far)批量控制一整套样式变得非常方便和清晰,也易于维护和调整。

参数调整实战:如果你想下“暴雨”,可以尝试:

  • 减小--drop-width(如0.8px),增加雨滴密度。
  • 缩短--duration(如0.7s),让雨下得更急。
  • 提高--opacity(如0.9),让雨更明显。
  • .rain-container里复制更多.rain-layer元素。 反之,调整这些参数可以得到“毛毛雨”或“细雨”的效果。

5. 高级优化与效果增强

5.1 性能优化:触发硬件加速

CSS动画的性能很大程度上取决于是否触发了GPU硬件加速。最常用的方法是使用transformopacity属性进行动画,因为这两个属性可以被浏览器优化,避免重排(Reflow)和重绘(Repaint)。

在我们的方案中,动画作用在background-position上。在现代浏览器中,对background-position进行动画通常也能得到较好的优化,但为了极致性能,我们可以考虑一个替代方案:使用transform: translateY来移动整个雨层容器

/* 优化版动画定义 */ @keyframes fall-down-optimized { from { transform: translateY(-100%); } to { transform: translateY(100vh); } } .rain-layer { /* 移除旧的background-position动画 */ /* animation: fall-down var(--duration, 1s) linear infinite; */ /* 应用新的transform动画 */ animation: fall-down-optimized var(--duration, 1s) linear infinite; /* 其他样式不变 */ background-position: var(--x-offset, 0px) 0; /* 背景位置y轴初始为0 */ }

这个改动将动画对象从背景图位置变成了整个元素的位置变换。transform属性在大多数情况下能更稳定地触发GPU加速,动画会更加平滑,尤其是在低性能设备或复杂页面上。你需要将雨滴背景的初始位置也调整到与容器顶部对齐。

5.2 增加风效:让雨丝倾斜

真实的雨很少是绝对垂直的。我们可以通过skewrotate变换让整个雨层容器倾斜,模拟风吹的效果。

.rain-layer { /* 在原有样式基础上添加 */ transform: skewX(-15deg); /* 让雨滴向左倾斜15度 */ /* 如果使用了上面的transform动画,需要合并变换,注意顺序 */ /* animation: fall-down-optimized 1s linear infinite; */ /* 此时动画中的transform会覆盖这里的skew,所以需要写在动画关键帧里 */ } /* 或者,在动画关键帧中整合倾斜和下落 */ @keyframes fall-down-with-wind { from { transform: translateY(-100%) skewX(-10deg); } to { transform: translateY(100vh) skewX(-10deg); } }

5.3 交互可能性:用CSS变量实现动态控制

虽然我们是纯CSS项目,但可以通过结合一点点HTML和CSS变量的能力,实现简单的交互。例如,用复选框(checkbox)切换雨的大小。

<input type="checkbox" id="heavy-rain-toggle"> <label for="heavy-rain-toggle">暴雨模式</label>
#heavy-rain-toggle:checked ~ .rain-container .layer-mid, #heavy-rain-toggle:checked ~ .rain-container .layer-near { --duration: 0.6s; --opacity: 0.9; filter: blur(0.3px); /* 增加一点模糊模拟速度感 */ } #heavy-rain-toggle:checked ~ .rain-container .layer-far { --drop-width: 0.7px; --duration: 1.5s; }

当复选框被勾选时,通过CSS选择器找到对应的雨层,并覆盖其CSS变量,瞬间改变动画参数,实现效果切换。这展示了CSS变量在动态主题和交互样式中的强大威力。

6. 常见问题、排查与浏览器兼容性

6.1 效果不流畅或卡顿

  • 问题:动画看起来有卡顿、跳帧。
  • 排查与解决
    1. 检查性能:打开浏览器开发者工具(F12)的“性能(Performance)”或“渲染(Rendering)”面板,勾选“Paint flashing”。如果页面元素频繁闪烁绿色(表示重绘),说明性能开销大。尝试使用5.1节提到的transform替代background-position动画。
    2. 减少图层和复杂度:检查是否创建了过多的.rain-layer元素。通常远景层2-3个,中景和近景各1个就足够了。过多的绝对定位元素叠加会增加合成层管理开销。
    3. 降低密度:增大--drop-width的值,减少水平方向重复的雨滴列数。
    4. 确保使用硬件加速:为动画元素添加will-change: transform;属性,可以提示浏览器提前优化。但不要滥用,仅用于确实需要动画的元素。

6.2 雨滴看起来太“硬”或不自然

  • 问题:雨滴线条边缘锐利,像一根根针,缺乏雨水的模糊和动感。
  • 排查与解决
    1. 优化渐变:调整background-imagelinear-gradient的色标。让透明到半透明的过渡更平滑,区域更广。例如,将rgba(255,255,255,0.3) 30%改为rgba(255,255,255,0.2) 20%, rgba(255,255,255,0.4) 40%,创造一个更宽的半透明区域。
    2. 添加模糊滤镜:为雨层添加微小的filter: blur(0.3px);。半像素左右的模糊可以极大地柔化边缘,模拟运动模糊。注意,滤镜有性能成本,谨慎使用。
    3. 叠加多层不同透明度的渐变:就像我们在示例代码中做的那样,在一个background-image属性里叠加多个渐变,可以创造出更丰富的透明度和形状变化。

6.3 浏览器兼容性注意事项

我们的方案主要依赖CSS动画、渐变和CSS变量(Custom Properties)。

特性兼容性降级方案
CSS动画 (@keyframes,animation)IE10+, 所有现代浏览器对于不支持的浏览器(如IE9),动画不会播放,页面静态显示雨滴背景,通常可接受。
CSS线性渐变 (linear-gradient)IE10+, 所有现代浏览器在不支持的浏览器中,背景可能显示为纯色或失效。可提供一个简单的备用背景色。
CSS变量 (--var)IE不支持, Edge 15+, Chrome 49+, Firefox 31+这是最大的兼容性痛点。如果需要在旧浏览器运行,必须放弃CSS变量,直接将值写死在各个类中,或者使用Sass/Less等预处理器变量在编译时替换。
vh单位IE9+ (部分支持), 所有现代浏览器@keyframes中使用vh可能导致IE/旧Edge动画异常。回退方案是使用100%,但这依赖于容器高度必须明确为100vh。

兼容性处理建议:对于个人项目或面向现代浏览器的场景,可以放心使用。如果需要兼容IE,建议:

  1. 移除CSS变量,用固定值或预处理器。
  2. 将动画中的100vh改为100%,并确保.rain-container的父级(通常是body/html)高度设置为100vh
  3. 考虑使用JavaScript特性检测库(如Modernizr)来为不支持关键特性的浏览器提供简化样式或提示。

6.4 在滚动页面上应用

如果下雨效果不是用于全屏背景,而是页面中的一个区块,且页面可滚动,直接使用上述代码可能会导致动画区域错乱。

  • 问题:雨滴动画可能只相对于它自己的容器运动,当页面滚动时,效果很奇怪。
  • 解决:关键在于background-position的动画。如果容器不是全屏,确保动画的起始和结束位置是相对于容器本身的高度。通常,将@keyframes中的100vh改为100%即可,这表示背景图移动到容器自身高度的100%处(即底部)。同时,确保容器overflow: hidden
.rain-container { position: relative; /* 非全屏时,通常用relative */ width: 300px; height: 200px; overflow: hidden; } @keyframes fall-for-container { from { background-position-y: -100%; } to { background-position-y: 100%; /* 改为容器高度的100% */ } }

这场用CSS编织的雨,从构思到实现,涉及了CSS背景、动画、变量、伪元素乃至性能优化的多个知识点。它不像一个功能性的组件那样有直接的业务价值,但它像一段前端工程师的“抒情诗”,展示了在有限的语法里创造无限可能的乐趣。调整每一个渐变色标,微调动画的延迟和时长,看着屏幕上从生硬的线条变为生动的雨幕,这个过程本身就是一种享受。

← 返回列表