assets/rain.js
2.9 KB| 1 | |
| 2 | (function(){ |
| 3 | function setup(canvas){ |
| 4 | const ctx = canvas.getContext("2d"); |
| 5 | let width = 0, height = 0, drops = [], ripples = []; |
| 6 | function makeDrop(seed){ |
| 7 | return { |
| 8 | x: Math.random() * (width + 160) - 80, |
| 9 | y: seed ? Math.random() * height : -30 - Math.random() * 180, |
| 10 | len: 12 + Math.random() * 15, |
| 11 | speed: 2.7 + Math.random() * 2.4, |
| 12 | opacity: 0.22 + Math.random() * 0.36, |
| 13 | width: 0.45 + Math.random() * 0.45 |
| 14 | }; |
| 15 | } |
| 16 | function resize(){ |
| 17 | const rect = canvas.parentElement.getBoundingClientRect(); |
| 18 | const dpr = Math.min(window.devicePixelRatio || 1, 2); |
| 19 | width = rect.width; height = rect.height; |
| 20 | canvas.width = width * dpr; canvas.height = height * dpr; |
| 21 | canvas.style.width = width + "px"; canvas.style.height = height + "px"; |
| 22 | ctx.setTransform(dpr, 0, 0, dpr, 0, 0); |
| 23 | drops = []; |
| 24 | const count = Math.min(220, Math.floor(95 + (width * height) / 5800)); |
| 25 | for (let i = 0; i < count; i += 1) drops.push(makeDrop(true)); |
| 26 | } |
| 27 | function spawnRipple(x){ |
| 28 | ripples.push({ x, y: height - 10 - Math.random() * 34, age: 0, life: 90 + Math.random() * 58, maxR: 14 + Math.random() * 22 }); |
| 29 | } |
| 30 | let last = performance.now(); |
| 31 | function frame(){ |
| 32 | const now = performance.now(); |
| 33 | const dt = Math.min((now - last) / 16.66, 2); |
| 34 | last = now; |
| 35 | ctx.clearRect(0, 0, width, height); |
| 36 | ctx.lineCap = "round"; |
| 37 | for (let i = drops.length - 1; i >= 0; i -= 1) { |
| 38 | const d = drops[i]; |
| 39 | d.y += d.speed * dt; d.x += d.speed * 0.11 * dt; |
| 40 | const x0 = d.x - d.len * 0.11; |
| 41 | const y0 = d.y - d.len; |
| 42 | const grad = ctx.createLinearGradient(x0, y0, d.x, d.y); |
| 43 | grad.addColorStop(0, "rgba(100,120,148," + (d.opacity * 0.25).toFixed(3) + ")"); |
| 44 | grad.addColorStop(0.4, "rgba(82,118,142," + d.opacity.toFixed(3) + ")"); |
| 45 | grad.addColorStop(1, "rgba(82,118,142," + (d.opacity * 0.42).toFixed(3) + ")"); |
| 46 | ctx.strokeStyle = grad; ctx.lineWidth = d.width; |
| 47 | ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(d.x, d.y); ctx.stroke(); |
| 48 | if (d.y >= height - 4) { |
| 49 | if (Math.random() < 0.3) spawnRipple(d.x); |
| 50 | drops[i] = makeDrop(false); |
| 51 | } |
| 52 | } |
| 53 | for (let i = ripples.length - 1; i >= 0; i -= 1) { |
| 54 | const r = ripples[i]; |
| 55 | r.age += dt; |
| 56 | const t = r.age / r.life; |
| 57 | if (t >= 1) { ripples.splice(i, 1); continue; } |
| 58 | const rad = r.maxR * (1 - Math.pow(1 - t, 2)); |
| 59 | const alpha = (1 - t) * (1 - t) * 0.55; |
| 60 | ctx.strokeStyle = "rgba(80,108,136," + alpha.toFixed(3) + ")"; |
| 61 | ctx.lineWidth = 1; |
| 62 | ctx.beginPath(); ctx.ellipse(r.x, r.y, rad, rad * 0.32, 0, 0, Math.PI * 2); ctx.stroke(); |
| 63 | } |
| 64 | requestAnimationFrame(frame); |
| 65 | } |
| 66 | resize(); window.addEventListener("resize", resize); frame(); |
| 67 | } |
| 68 | document.querySelectorAll(".rain").forEach(setup); |
| 69 | })(); |
文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。

