我的浏览器下雨了
起源
外面下雨,气氛有些无聊,心里盼着周末快点到来。摸鱼时在百度网页查询天气,偶然发现了一个非常有趣的效果:

雨滴在屏幕上滑落,仿佛整个浏览器都被雨水覆盖了。这一刻让我眼前一亮,心里不禁感叹:"牛逼!"作为前端开发者,好奇心驱使我思考:如果哪天项目经理突然提出这样的需求,我该如何实现呢?
带着这个疑问,我心里突然冒出一个念头:如果哪天那个变态需求经理看到这个效果,八成会让我实现一遍。与其等着被"点名",不如提前研究一下,免得临时抱佛脚。 于是我开始在脑海里盘算实现思路,列举了几种可能:
-
CSS 动画:用 CSS3 的动画和透明度变化,模拟雨滴下落和消失,优点是简单易用,缺点是灵活性有限。
-
JavaScript 操作 DOM:动态创建雨滴元素,通过 JS 控制它们的位置和运动轨迹,适合简单场景,但性能可能不理想。
-
Canvas 绘制:用 Canvas 逐帧绘制雨滴,能实现更复杂的物理效果,比如碰撞和水花,性能和效果都不错。
-
WebGL/着色器:用 WebGL 或 GLSL 着色器直接在 GPU 上渲染雨滴,能实现极致的视觉效果,但学习成本较高。
虽然我暂时还不会亲手实现这样的效果,但还是忍不住分析了一下实现思路和技术难点。想着想着,不由得盯着屏幕发呆,心里默默感叹:"这些效果我现在还不会啊!"
(项目经理突然路过,看到我在摸鱼,然后我被谈话了,说工作不专心,哎心里冲出一股想辞职的冲动,远方自由在呼唤我)
开个玩笑,我们还是回到技术上来,这不是故事,回归正题, 虽然自己写不出来,但好奇心还是驱使我继续琢磨:雨滴到底是怎么生成和运动的? 浏览器里如何高效渲染这么多动态元素? 雨滴与背景、界面元素又如何自然融合? 只有把这些问题逐步拆解,才能找到合适的技术方案和实现路径。
不过现在有现成的代码,实在不会写,抄一份也能用起来。
开启我的cv工具,错了,是F12,你们不用看了,我替你们找出来了,没错就是下面的那一块。 当我看到这一块的时候地一眼就被一个特殊的标签吸引到了
arduino
<video width="100%" src="https://search-operate.cdn.bcebos.com/5b8fa41e5c92ea8793a99c5081a7a9d8.mp4" poster="https://search-operate.cdn.bcebos.com/9669cac22a5833622982ecfdf38f4694.png" muted="true" autoplay="true" auto-rotate="false" playsinline="true" x5-playsinline="true" webkit-playsinline="true" t7-video-player-type="inline" crossorigin="anonymous" loop="true"></video>
就是他,为啥要放个视频内,点进去看看(打个广告:陌生链接请勿随意点击,小心诈骗)
别急着走,后面有内容
然后我就发现了
对没错,就是这样,高端是美食,都是用最简单的方式烹饪(我饿了,想干饭)。
万万没想到啊,居然是这么朴实无华的技术。
我作为一名CV工程师怎么能用CV这么简单的的东西呢。
这种效果是通过视频遮罩实现的。虽然视觉效果不错,
但总觉得缺少了一点什么,对-----技术
这么炫酷的效果怎么只能通过视频实现呢,我要捍卫做为一名前端工程师的尊严。
探索与学习
为了实现这个效果,我开始查阅资料。
CSS 动画 首先考虑css 动画;认证思考了1秒,放弃了,作为一名CV工程师,css的水平仅限于能看懂。
JavaScript 操作 DOM: 理论上技术可行,但认真分析一下,会发现需要很多dom,不断的修改重绘对性能也会产生影响,所以放弃了。
- Canvas 绘制:
用 Canvas 逐帧绘制雨滴,能实现更复杂的物理效果,比如碰撞和水花,性能和效果都不错。 但是问题又来了我不会啊。现在这个AI时代,我已经快忘记代码怎么写了。
- WebGL/着色器: 用 WebGL 或 GLSL 着色器直接在 GPU 上渲染雨滴,能实现极致的视觉效果,但学习成本较高。作者自己尝试了一些。 数学不好,所以没写出来
就在这个时候我浏览网站看别人用shader实现的效果,发现了一个很类似的 www.shadertoy.com/view/ltffzl
对没错这个就是我想要的 不是类似就是我想要的
于是,我决定深入研究这个作品的代码逻辑(开始CV)。 然而,ShaderToy 的代码大多是 GLSL(OpenGL Shading Language)编写的,对于初学者来说并不友好。 于是,我求助于 AI,让它帮我分析和理解其中的算法。
算法解析
在 AI 的帮助下,我逐步理解了这个效果的核心逻辑:
- 雨滴生成:通过随机数生成雨滴的位置和大小。
- 雨滴运动:利用时间变量控制雨滴的下落速度,并通过循环实现雨滴的重复生成。
- 背景纹理:为场景添加渐变背景,使整体效果更加真实。
以下是部分关键代码的讲解:
glsl
// 生成随机数
vec3 N13(float p) {
vec3 p3 = fract(vec3(p) * vec3(.1031, .11369, .13787));
p3 += dot(p3, p3.yzx + 19.19);
return fract(vec3((p3.x + p3.y) * p3.z, (p3.x + p3.z) * p3.y, (p3.y + p3.z) * p3.x));
}
// 雨滴层
vec2 DropLayer2(vec2 uv, float t) {
uv.y += t * 0.75; // 控制雨滴下落速度
vec2 grid = vec2(6., 1.) * 2.;
vec2 id = floor(uv * grid);
vec3 n = N13(id.x * 35.2 + id.y * 2376.1);
vec2 st = fract(uv * grid) - vec2(.5, 0);
float d = length((st - vec2(n.x - .5, n.y - .5)) * vec2(1., 1.5));
return vec2(smoothstep(.4, .0, d), 0.0);
}
实现效果
理解了算法后,我使用 Three.js 将其移植到浏览器中。 Three.js 是一个强大的 WebGL 库,可以轻松实现 3D 渲染和着色器效果。
以下是核心实现代码:
javascript
const material = new THREE.ShaderMaterial({
uniforms: {
iResolution: { value: new THREE.Vector3() },
iTime: { value: 0 },
iChannel0: { value: bgTexture },
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
precision highp float;
varying vec2 vUv;
uniform float iTime;
uniform vec3 iResolution;
void main() {
vec2 uv = vUv;
float t = iTime;
vec2 drops = DropLayer2(uv, t);
gl_FragColor = vec4(vec3(drops.x), 1.0);
}
`
});
技术分享与进阶讲解
1. 着色器中的雨滴算法原理
在 ShaderToy 的 rain shader 代码中,雨滴的生成和运动其实是通过数学函数和噪声算法实现的。 比如 N13
用于生成伪随机数, DropLayer2
用于计算每个雨滴的形状和轨迹。 核心思想是:
- 利用二维网格分布雨滴,每个网格点通过随机数决定雨滴的参数(位置、大小、速度等)。
- 时间变量
t
控制雨滴的下落和动画。 smoothstep
函数让雨滴边缘变得柔和,视觉上更自然。
关键代码拆解
glsl
vec2 DropLayer2(vec2 uv, float t) {
uv.y += t * 0.75; // 时间驱动雨滴下落
vec2 grid = vec2(6., 1.) * 2.; // 网格划分
vec2 id = floor(uv * grid); // 当前雨滴所在网格编号
vec3 n = N13(id.x * 35.2 + id.y * 2376.1); // 随机参数
vec2 st = fract(uv * grid) - vec2(.5, 0); // 局部坐标
float d = length((st - vec2(n.x - .5, n.y - .5)) * vec2(1., 1.5)); // 距离计算
return vec2(smoothstep(.4, .0, d), 0.0); // 雨滴形状
}
2. Three.js 与 WebGL 的结合
Three.js 其实是对 WebGL 的封装,让我们可以用更简单的方式写出复杂的 3D 效果。 比如 ShaderMaterial 允许我们直接插入 GLSL 代码,实现自定义的渲染逻辑。
关键代码讲解
javascript
const material = new THREE.ShaderMaterial({
uniforms: {
iResolution: { value: new THREE.Vector3() }, // 屏幕分辨率
iTime: { value: 0 }, // 动画时间
iChannel0: { value: bgTexture }, // 背景纹理
},
vertexShader: `...`, // 顶点着色器
fragmentShader: `...` // 片元着色器
});
uniforms
是传递给着色器的全局变量,比如时间、分辨率、纹理等。vertexShader
负责顶点变换,fragmentShader
负责每个像素的颜色计算。
3. 性能优化与工程实践
- GPU加速:着色器代码在 GPU 上运行,能同时处理成千上万个像素,效率极高。
- 动画驱动 :通过
requestAnimationFrame
和performance.now()
实现高帧率动画。 - UI交互:可以用滑块控制雨量、速度等参数,实时调整视觉效果。
4. 相关知识科普
- GLSL:一种专门用于图形渲染的编程语言,语法类似 C。
- WebGL:浏览器里的 3D 渲染接口,底层用 OpenGL ES。
- Three.js:让 WebGL 更易用的 JS 库,适合快速开发 3D 可视化项目。
彩蛋:如何让你的浏览器下雪?
只需把雨滴的下落速度调慢,形状变圆,颜色变白,算法稍微调整一下,雪花效果就出来了!
总结与展望
这次"浏览器下雨"之旅,不仅让我体验了前端图形学的乐趣, 也感受到 AI 和开源社区的强大。未来如果项目经理再来"为难"我,我可以自信地说:"没问题,雨、雪、闪电都能安排!"
如果你也对这些酷炫的视觉效果感兴趣,不妨试试 Three.js 和 ShaderToy,说不定下一个让人惊艳的作品就出自你的手中。
技术的乐趣,不止于实现,更在于探索和分享。
同时最后送上一句话
危险和机遇并存,AI带来的不只是危机,合理的使用AI,可以很快的视线我们灵光一闪的东西,退而其次就算实现不了也可以给到我们一些建议或者说降低我们的试错成本。
(完)