自古逢秋悲寂寥,我言秋日胜春朝
一个前端程序员的秋日浪漫,欢迎大家接龙,寻找代码中的秋日世界,寻找独属于你我的那个秋日。
下面是效果图,实际页面中是有动画效果的呦
落叶、树影、云朵、飞雁、风车、秋风。。。
<!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);
})();
欢迎各路大神,希望能看到各位的那个秋日,如果你或者他或者她看到这里有触动有想法,希望把这个想法接龙下去,如果可以,欢迎各路大神在评论区发表你对秋天的看法,祝福各位,秋日大吉!!!