自古逢秋悲寂寥,我言秋日胜春朝

54 阅读7分钟

自古逢秋悲寂寥,我言秋日胜春朝

一个前端程序员的秋日浪漫,欢迎大家接龙,寻找代码中的秋日世界,寻找独属于你我的那个秋日。

下面是效果图,实际页面中是有动画效果的呦

落叶、树影、云朵、飞雁、风车、秋风。。。

在这里插入图片描述

<!doctype html>
<html lang="zh-CN">
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
	<title>秋叶 · Autumn Leaves</title>
	<link rel="preconnect" href="https://fonts.googleapis.com">
	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
	<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600&display=swap" rel="stylesheet">
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<canvas id="scene"></canvas>
	<div class="title">自古逢秋悲寂寥,我言秋日胜春朝</div>
	<script src="script.js"></script>
</body>
</html>


* { box-sizing: border-box; }
html, body { height: 100%; }
body {
	margin: 0;
	font-family: "Noto Serif SC", system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
	background: linear-gradient(180deg, #dbe7ff 0%, #f1efe9 55%, #f3e7d4 100%);
	overflow: hidden;
}

#scene {
	display: block;
	width: 100vw;
	height: 100vh;
}

.title {
	position: fixed;
	left: 50%;
	bottom: 32px;
	transform: translateX(-50%);
	color: #5a4a2f;
	text-shadow: 0 1px 0 rgba(255,255,255,.6);
	font-weight: 600;
	letter-spacing: .1em;
	background: rgba(255, 248, 229, .6);
	backdrop-filter: blur(3px);
	border: 1px solid rgba(140, 110, 60, .25);
	border-radius: 10px;
	padding: 8px 14px;
	user-select: none;
}


(() => {
	const canvas = document.getElementById("scene");
	const ctx = canvas.getContext("2d");

	// DPR 适配
	const getDpr = () => Math.min(window.devicePixelRatio || 1, 2);
	let dpr = getDpr();

	function resize() {
		const { innerWidth: w, innerHeight: h } = window;
		dpr = getDpr();
		canvas.style.width = w + "px";
		canvas.style.height = h + "px";
		canvas.width = Math.floor(w * dpr);
		canvas.height = Math.floor(h * dpr);
		ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
	}
	window.addEventListener("resize", resize);
	resize();

	// 工具
	const rand = (min, max) => Math.random() * (max - min) + min;
	const choice = arr => arr[(Math.random() * arr.length) | 0];

	// 速度倍率(可调):数值越大下落越快,风力更强
	const SPEED = 0.01;

	// 风的全局噪声(缓慢变化)
	let windSeed = Math.random() * 1000;
	function windAt(time) {
		// 使用多频正弦叠加模拟阵风
		const t = time * 0.00015 + windSeed;
		const base = Math.sin(t) * 0.6 + Math.sin(t * 0.37) * 0.3 + Math.sin(t * 1.73) * 0.1;
		return base; // -1..1 之间
	}

	// 叶片颜色(偏黄、橙、褐)
	const leafPalettes = [
		["#F2C94C", "#F2994A", "#D5912B"],
		["#FFD166", "#F4A261", "#C57B57"],
		["#F6D365", "#FDA085", "#C27C2C"],
	];

	class Leaf {
		constructor(bounds, now) {
			this.bounds = bounds;
			this.reset(now, true);
		}

		reset(now, initial = false) {
			const { width, height } = this.bounds();
			this.palette = choice(leafPalettes);
			this.colorMain = choice(this.palette);
			this.colorEdge = choice(this.palette);
			this.size = rand(10, 26);
			this.aspect = rand(0.8, 1.6); // 宽高比,控制叶形
			this.curve = rand(0.2, 0.7); // 中脉弯曲程度
			this.rotation = rand(0, Math.PI * 2);
			this.angularVelocity = rand(-0.8, 0.8) * 0.6;
			this.swingAmp = rand(8, 28); // 左右摆幅
			this.swingFreq = rand(0.6, 1.6);
			this.phase = rand(0, Math.PI * 2);
			this.airDrag = rand(0.96, 0.985);
			this.spinDrag = rand(0.985, 0.998);

			this.vx = rand(-10, 10) * 0.2;
			this.vy = rand(20, 60) * 0.2 * SPEED;

			if (initial) {
				this.x = rand(0, width);
				this.y = rand(-height, height);
			} else {
				this.x = rand(-width * 0.2, width * 1.2);
				this.y = -rand(10, height * 0.25);
			}

			this.birth = now;
		}

		step(dt, now, wind) {
			// 风对速度的影响
			const gust = wind * rand(18, 32) * SPEED;
			this.vx += gust * 0.02;
			this.vy += 9.8 * 0.12 * SPEED; // 重力(随倍率增强)

			// 空气阻力
			this.vx *= this.airDrag;
			this.vy *= this.airDrag;
			this.angularVelocity *= this.spinDrag;

			// 左右摇摆(随时间)
			const sway = Math.sin((now * 0.001) * this.swingFreq + this.phase) * this.swingAmp;

			this.x += (this.vx + sway * 0.02) * dt;
			this.y += this.vy * dt;
			this.rotation += this.angularVelocity * dt * 0.0015;

			const { width, height } = this.bounds();
			if (this.y - this.size > height + 20 || this.x < -60 || this.x > width + 60) {
				this.reset(now);
			}
		}

		draw(ctx) {
			ctx.save();
			ctx.translate(this.x, this.y);
			ctx.rotate(this.rotation);

			const w = this.size * this.aspect;
			const h = this.size;

			// 叶片形状(左右不对称的心形/椭圆叶,带中脉)
			ctx.beginPath();
			ctx.moveTo(0, 0);
			ctx.bezierCurveTo(w * 0.2, -h * (0.5 + this.curve * 0.2), w * 0.9, -h * 0.2, w * 0.6, h * 0.3);
			ctx.bezierCurveTo(w * 0.3, h * 0.9, -w * 0.2, h * 0.9, -w * 0.6, h * 0.3);
			ctx.bezierCurveTo(-w * 0.9, -h * 0.1, -w * 0.2, -h * (0.5 + this.curve * 0.2), 0, 0);
			const grd = ctx.createLinearGradient(-w, -h, w, h);
			grd.addColorStop(0, this.colorEdge);
			grd.addColorStop(0.5, this.colorMain);
			grd.addColorStop(1, this.colorEdge);
			ctx.fillStyle = grd;
			ctx.fill();

			// 叶脉与描边
			ctx.lineWidth = 0.8;
			ctx.strokeStyle = "rgba(90,74,47,0.55)";
			ctx.beginPath();
			ctx.moveTo(-w * 0.55, h * 0.2);
			ctx.quadraticCurveTo(0, -h * this.curve, w * 0.55, h * 0.2);
			ctx.stroke();

			// 侧脉
			ctx.lineWidth = 0.6;
			for (let i = -2; i <= 2; i++) {
				const t = i / 2;
				ctx.beginPath();
				ctx.moveTo(t * w * 0.5, h * (0.1 + Math.abs(t) * 0.15));
				ctx.quadraticCurveTo(t * w * 0.75, h * (0.2 + Math.abs(t) * 0.3), t * w, h * (0.35 + Math.abs(t) * 0.45));
				ctx.stroke();
			}

			// 叶柄
			ctx.beginPath();
			ctx.moveTo(-w * 0.6, h * 0.3);
			ctx.lineTo(-w * 0.8, h * 0.5);
			ctx.strokeStyle = "rgba(90,74,47,0.6)";
			ctx.lineWidth = 1.1;
			ctx.stroke();

			ctx.restore();
		}
	}

	// 背景元素:远近树影与地面
	function drawBackground(ctx, w, h, time) {
		// 工具函数:画柔和山丘
		function drawHill(y, color, amp, freq, offset) {
			ctx.beginPath();
			ctx.moveTo(0, h);
			ctx.lineTo(0, y);
			for (let x = 0; x <= w; x += 24) {
				const dy = Math.sin((x * freq + offset) * 0.002) * amp;
				ctx.lineTo(x, y + dy);
			}
			ctx.lineTo(w, h);
			ctx.closePath();
			ctx.fillStyle = color;
			ctx.fill();
		}

		// 工具函数:云(由多段贝塞尔组合,不用圆)
		function drawCloud(cx, cy, s, t, alpha) {
			const drift = Math.sin(t * 0.00025 + cx * 0.002) * 30 + (t * 0.01 % (w + 400)) * 0.02;
			ctx.save();
			ctx.globalAlpha = alpha;
			ctx.translate(cx + drift, cy);
			const cloudGrad = ctx.createLinearGradient(-120 * s, -40 * s, 140 * s, 40 * s);
			cloudGrad.addColorStop(0, "rgba(255,255,255,0.9)");
			cloudGrad.addColorStop(1, "rgba(240,245,255,0.7)");
			ctx.fillStyle = cloudGrad;
			ctx.beginPath();
			ctx.moveTo(-120 * s, 10 * s);
			ctx.bezierCurveTo(-100 * s, -20 * s, -40 * s, -30 * s, 0, -16 * s);
			ctx.bezierCurveTo(40 * s, -34 * s, 90 * s, -18 * s, 110 * s, 4 * s);
			ctx.bezierCurveTo(80 * s, 20 * s, 20 * s, 28 * s, -20 * s, 24 * s);
			ctx.bezierCurveTo(-70 * s, 28 * s, -110 * s, 24 * s, -120 * s, 10 * s);
			ctx.closePath();
			ctx.fill();
			ctx.restore();
		}

		// 工具函数:鸟(V 形剪影,带振翅)
		function drawBird(bx, by, s, t, alpha) {
			const flap = Math.sin(t * 0.008 + bx * 0.02) * 0.6 + 0.6;
			ctx.save();
			ctx.globalAlpha = alpha;
			ctx.translate(bx + Math.sin(t * 0.0006 + by) * 10, by + Math.sin(t * 0.001 + bx) * 2);
			ctx.strokeStyle = "#584b39";
			ctx.lineWidth = 1.6 * s;
			ctx.lineCap = "round";
			ctx.beginPath();
			ctx.moveTo(-12 * s, 0);
			ctx.lineTo(0, -8 * s * flap);
			ctx.lineTo(12 * s, 0);
			ctx.stroke();
			ctx.restore();
		}

		// 工具函数:画树(树干+枝条+非圆形树冠)
		function drawTree(baseX, baseY, scale, swayT, alpha) {
			ctx.save();
			ctx.globalAlpha = alpha;
			ctx.translate(baseX, baseY);
			const sway = Math.sin(swayT * 0.001 + baseX * 0.01) * 6 * scale;

			// 树干(不使用圆形)
			ctx.fillStyle = "#7b5e3b";
			ctx.beginPath();
			ctx.moveTo(-6 * scale, 0);
			ctx.lineTo(-2 * scale, -60 * scale);
			ctx.lineTo((-1 * scale + sway * 0.06), -120 * scale);
			ctx.lineTo(2 * scale, -60 * scale);
			ctx.lineTo(6 * scale, 0);
			ctx.closePath();
			ctx.fill();

			// 分叉枝条(条形/锥形多边形)
			ctx.fillStyle = "#6f5333";
			for (let i = 0; i < 5; i++) {
				const angle = (-Math.PI / 3 + (i / 4) * (2 * Math.PI / 3)) + sway * 0.002;
				const len = (60 + i * 12) * scale;
				const baseWidth = (6 - i) * 0.9 * scale;
				const tipWidth = Math.max(1.2 * scale, baseWidth * 0.35);
				const ox = -1 * scale + Math.sin(i) * 2 * scale; // 与主干连接点微偏移
				const oy = -100 * scale + i * -6 * scale;
				const dx = Math.cos(angle) * len + sway * 0.15;
				const dy = Math.sin(angle) * len * 0.6;
				ctx.beginPath();
				ctx.moveTo(ox - baseWidth, oy);
				ctx.lineTo(ox + baseWidth, oy);
				ctx.lineTo(ox + dx + tipWidth, oy + dy + tipWidth * 0.3);
				ctx.lineTo(ox + dx - tipWidth, oy + dy - tipWidth * 0.3);
				ctx.closePath();
				ctx.fill();
			}

			// 非圆形树冠(不规则多边形片)
			const crownGrad = ctx.createLinearGradient(-50 * scale, -160 * scale, 60 * scale, -40 * scale);
			crownGrad.addColorStop(0, "#a07a3c");
			crownGrad.addColorStop(1, "#c79a4a");
			ctx.fillStyle = crownGrad;
			for (let i = 0; i < 5; i++) {
				const cx = (-30 + i * 28) * scale + sway * (0.10 + i * 0.04);
				const cy = (-118 - i * 10) * scale + Math.sin(swayT * 0.0013 + i) * 2 * scale;
				const w1 = 50 * scale, h1 = 26 * scale;
				ctx.beginPath();
				ctx.moveTo(cx - w1 * 0.6, cy - h1 * 0.2);
				ctx.lineTo(cx - w1 * 0.2, cy - h1 * 0.6);
				ctx.lineTo(cx + w1 * 0.5, cy - h1 * 0.2);
				ctx.lineTo(cx + w1 * 0.3, cy + h1 * 0.4);
				ctx.lineTo(cx - w1 * 0.5, cy + h1 * 0.3);
				ctx.closePath();
				ctx.fill();
			}

			ctx.restore();
		}

		// 先画云(在山丘之上、树之前)
		drawCloud(w * 0.18, h * 0.18, 0.9, time, 0.9);
		drawCloud(w * 0.55, h * 0.22, 1.1, time + 800, 0.85);
		drawCloud(w * 0.82, h * 0.16, 0.8, time + 1600, 0.8);

		// 工具函数:风车(远景),简洁塔架+机舱+三叶片
		function drawTurbine(tx, ty, scale, t, alpha) {
			ctx.save();
			ctx.globalAlpha = alpha;
			ctx.translate(tx, ty);
			// 塔架(细高梯形)
			ctx.fillStyle = "rgba(180, 180, 190, 0.75)";
			ctx.beginPath();
			ctx.moveTo(-4 * scale, 0);
			ctx.lineTo(-2 * scale, -90 * scale);
			ctx.lineTo(2 * scale, -90 * scale);
			ctx.lineTo(4 * scale, 0);
			ctx.closePath();
			ctx.fill();

			// 机舱(小矩形)
			ctx.fillStyle = "rgba(200, 200, 210, 0.9)";
			ctx.fillRect(-6 * scale, -98 * scale, 12 * scale, 8 * scale);

			// 轮毂与叶片(细长三角形,三等分)
			const angle = (t * 0.004) % (Math.PI * 2);
			ctx.translate(0, -94 * scale);
			for (let i = 0; i < 3; i++) {
				const a = angle + (i * 2 * Math.PI) / 3;
				ctx.save();
				ctx.rotate(a);
				ctx.beginPath();
				ctx.moveTo(0, 0);
				ctx.lineTo(0, -52 * scale);
				ctx.lineTo(3.2 * scale, -49 * scale);
				ctx.closePath();
				ctx.fillStyle = "rgba(220, 220, 228, 0.95)";
				ctx.fill();
				ctx.restore();
			}

			ctx.restore();
		}

		// 背景层次:远山 -> 近山 -> 草地 -> 小径 -> 树
		ctx.save();

		// 远山(冷一点的褐灰色)
		drawHill(h * 0.68, "rgba(110, 96, 80, 0.22)", 24, 1.6, 0);
		// 中山(更近更暖)
		drawHill(h * 0.74, "rgba(150, 120, 80, 0.28)", 30, 1.4, 200);

		// 远处风车(位于山丘上方,轻透明)
		drawTurbine(w * 0.35, h * 0.74, 0.8, time, 0.35);
		drawTurbine(w * 0.50, h * 0.72, 0.9, time + 300, 0.38);
		drawTurbine(w * 0.65, h * 0.75, 0.75, time + 600, 0.33);

		// 草地(近景,带渐变)
		const grassGrad = ctx.createLinearGradient(0, h * 0.8, 0, h);
		grassGrad.addColorStop(0, "rgba(190, 160, 110, 0.35)");
		grassGrad.addColorStop(1, "rgba(140, 110, 60, 0.45)");
		ctx.fillStyle = grassGrad;
		ctx.fillRect(0, h * 0.78, w, h * 0.22);

		// 小径(透视梯形)
		ctx.beginPath();
		ctx.moveTo(w * 0.45, h * 0.78);
		ctx.lineTo(w * 0.55, h * 0.78);
		ctx.lineTo(w * 0.72, h);
		ctx.lineTo(w * 0.28, h);
		ctx.closePath();
		const pathGrad = ctx.createLinearGradient(0, h * 0.78, 0, h);
		pathGrad.addColorStop(0, "rgba(200, 185, 150, 0.55)");
		pathGrad.addColorStop(1, "rgba(160, 140, 110, 0.8)");
		ctx.fillStyle = pathGrad;
		ctx.fill();

		// 远处树排(轻微风摆,低透明)
		for (let i = 0; i < 7; i++) {
			const x = (w / 7) * i + 30;
			drawTree(x, h * 0.78, 0.6, time, 0.2);
		}

		// 近处树(两侧各一棵,层次更足)
		drawTree(w * 0.15, h * 0.82, 1.0, time, 0.55);
		drawTree(w * 0.85, h * 0.82, 1.1, time + 500, 0.5);

		// 草地纹理:几簇草丛
		ctx.strokeStyle = "rgba(100, 80, 50, 0.35)";
		ctx.lineWidth = 1;
		for (let i = 0; i < 24; i++) {
			const gx = rand(0, w);
			const gy = rand(h * 0.82, h * 0.98);
			ctx.beginPath();
			ctx.moveTo(gx, gy);
			ctx.quadraticCurveTo(gx - 6, gy - 14, gx - 2, gy - 2);
			ctx.moveTo(gx, gy);
			ctx.quadraticCurveTo(gx + 6, gy - 16, gx + 2, gy - 3);
			ctx.stroke();
		}

		// 地面落叶(静态几片,增强“像”)
		for (let i = 0; i < 18; i++) {
			const lx = rand(20, w - 20);
			const ly = rand(h * 0.86, h * 0.99);
			const s = rand(6, 12);
			const a = rand(0, Math.PI * 2);
			const palette = choice(leafPalettes);
			ctx.save();
			ctx.translate(lx, ly);
			ctx.rotate(a);
			const c1 = choice(palette);
			const c2 = choice(palette);
			const grd = ctx.createLinearGradient(-s, -s, s, s);
			grd.addColorStop(0, c1);
			grd.addColorStop(1, c2);
			ctx.fillStyle = grd;
			ctx.beginPath();
			ctx.moveTo(0, 0);
			ctx.bezierCurveTo(s * 0.25, -s * 0.5, s * 0.9, -s * 0.2, s * 0.6, s * 0.3);
			ctx.bezierCurveTo(s * 0.3, s * 0.9, -s * 0.2, s * 0.9, -s * 0.6, s * 0.3);
			ctx.bezierCurveTo(-s * 0.9, -s * 0.1, -s * 0.25, -s * 0.5, 0, 0);
			ctx.fill();
			ctx.restore();
		}

		ctx.restore();

		// 最上层画几只飞鸟(覆盖在树前/后都可,这里放前景上层)
		for (let i = 0; i < 6; i++) {
			const bx = (i / 6) * w + 40 + Math.sin(time * 0.0005 + i) * 30;
			const by = h * 0.18 + (i % 3) * 18 + Math.sin(time * 0.001 + i * 2) * 6;
			drawBird(bx, by, 0.9 - i * 0.08, time + i * 120, 0.85);
		}
	}

	// 初始化叶片
	const leaves = [];
	function populate(now) {
		leaves.length = 0;
		const { innerWidth: w, innerHeight: h } = window;
		const density = Math.max(18, Math.min(64, Math.floor((w * h) / 24000)));
		for (let i = 0; i < density; i++) leaves.push(new Leaf(() => ({ width: w, height: h }), now));
	}
	populate(performance.now());
	window.addEventListener("resize", () => populate(performance.now()));

	let last = performance.now();
	function frame(now) {
		const { innerWidth: w, innerHeight: h } = window;
		const dt = Math.min(50, now - last); // ms
		last = now;

		// 清屏(保留淡淡拖影以更柔和)
		ctx.clearRect(0, 0, w, h);
		drawBackground(ctx, w, h, now);

		const wind = windAt(now);
		for (let i = 0; i < leaves.length; i++) {
			leaves[i].step(dt, now, wind);
			leaves[i].draw(ctx);
		}

		requestAnimationFrame(frame);
	}
	requestAnimationFrame(frame);
})();


欢迎各路大神,希望能看到各位的那个秋日,如果你或者他或者她看到这里有触动有想法,希望把这个想法接龙下去,如果可以,欢迎各路大神在评论区发表你对秋天的看法,祝福各位,秋日大吉!!!