一个前端测试,哪位佬友可以在faber和Kimi k3 上试试吗
铁锤.王
2026-07-29 14:43
1
利用animate js,文字组成大海,模拟文字组成的海浪被风吹动,风吹过大海,形成海浪,一个文字组成的小船在波浪上前行 。真实,有趣 。
这是GLM5.2的效果:
最新回复 (6)
-
行思渐远 07-29 14:511楼k3 code客户端写的,不是网页上

text-sea.zip (4.1 KB)

-
魔教少主-黑小虎 07-29 14:592楼<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>字海 — Sea of Words</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js"></script>
<style>
:root{
--ink:#dfe9f5;
--dim:#5b7492;
--deep:#020810;
--accent:#6ec8ff;
}
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%;overflow:hidden;background:var(--deep);font-family:"Songti SC","Noto Serif SC",serif}
canvas{position:fixed;inset:0;display:block}
.frame{position:fixed;inset:0;pointer-events:none;z-index:10}
.title{position:absolute;top:6vh;left:6vw;color:var(--ink)}
.title h1{
font-size:clamp(3rem,8vw,7rem);
font-weight:900;letter-spacing:.35em;line-height:1;
writing-mode:vertical-rl;
text-shadow:0 0 40px rgba(110,200,255,.35);
opacity:0;
}
.title h1 span{display:inline-block}
.sub{
position:absolute;top:7vh;left:calc(6vw + clamp(4rem,9vw,8rem));
color:var(--dim);font-size:.72rem;letter-spacing:.55em;
writing-mode:vertical-rl;text-transform:uppercase;
font-family:"Helvetica Neue",sans-serif;opacity:0;
}
.meta{
position:absolute;bottom:5vh;right:6vw;text-align:right;color:var(--dim);
font-family:"Helvetica Neue",sans-serif;font-size:.68rem;letter-spacing:.32em;
line-height:2.1;opacity:0;
}
.meta b{color:var(--ink);font-weight:500}
.wind-hud{
position:absolute;bottom:5vh;left:6vw;display:flex;align-items:center;gap:.7rem;
color:var(--dim);font-family:"Helvetica Neue",sans-serif;
font-size:.68rem;letter-spacing:.32em;opacity:0;
}
.wind-hud svg{width:16px;height:16px;stroke:var(--accent)}
.wind-bar{width:120px;height:1px;background:rgba(110,200,255,.18);position:relative}
.wind-bar i{position:absolute;left:0;top:-1px;height:3px;width:20%;background:var(--accent);
box-shadow:0 0 12px var(--accent)}
.hint{
position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
color:var(--dim);font-size:.7rem;letter-spacing:.6em;
font-family:"Helvetica Neue",sans-serif;opacity:0;
}
.vignette{position:fixed;inset:0;z-index:5;pointer-events:none;
background:radial-gradient(ellipse at 50% 60%,transparent 55%,rgba(0,4,10,.75) 100%)}
</style>
</head>
<body>
<canvas id="sea"></canvas>
<div class="vignette"></div>
<div class="frame">
<div class="title"><h1 id="h1"></h1></div>
<div class="sub" id="sub">A SEA COMPOSED OF CHARACTERS</div>
<div class="wind-hud" id="hud">
<!-- Lucide: wind -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.8 19.6A2 2 0 1 0 14 16H2"/><path d="M17.5 8a2.5 2.5 0 1 1 2 4H2"/><path d="M9.8 4.4A2 2 0 1 1 11 8H2"/></svg>
<span>风力</span>
<div class="wind-bar"><i id="windbar"></i></div>
</div>
<div class="meta" id="meta">
<b>风起于青萍之末</b><br>
浪成于微澜之间<br>
LAT 30.66 · LON 122.51
</div>
<div class="hint" id="hint">移动光标 唤风</div>
</div>
<script>
"use strict";
const cvs = document.getElementById('sea');
const ctx = cvs.getContext('2d', {alpha:false});
let W, H;
/* ============================================================
PERFORMANCE STRATEGY
- 所有海面文字预渲染进精灵图集(sprite atlas):
每个字 × 5 个亮度档,一次性画好,之后全部用 drawImage。
彻底消灭每帧上千次 fillText / font 切换 / shadowBlur。
- DPR 锁定 1,网格加大,波形函数用查表 sin。
============================================================ */
// ---- fast sin lookup ----
const SIN_N = 2048, SIN_TAB = new Float32Array(SIN_N);
for(let i=0;i<SIN_N;i++) SIN_TAB[i]=Math.sin(i/SIN_N*Math.PI*2);
const K = SIN_N/(Math.PI*2);
function fsin(x){ return SIN_TAB[((x*K)|0)&(SIN_N-1)]; }
// ---- atlas ----
const SEA_TEXT = "浪潮汐波涛海流沫湾澜漪沧涟".split('');
const TIERS = 5; // brightness levels
const GS = 40; // glyph cell px in atlas
const atlas = document.createElement('canvas');
const TIER_COLORS = [
"rgba(50,90,140,1)",
"rgba(70,120,175,1)",
"rgba(100,155,205,1)",
"rgba(150,200,240,1)",
"rgba(225,242,255,1)"
];
function buildAtlas(){
atlas.width = GS*SEA_TEXT.length; atlas.height = GS*TIERS;
const a = atlas.getContext('2d');
a.textAlign="center"; a.textBaseline="middle";
for(let t=0;t<TIERS;t++){
a.font = "28px 'Songti SC','Noto Serif SC',serif";
a.fillStyle = TIER_COLORS[t];
if(t===TIERS-1){ a.shadowColor="rgba(140,205,255,.9)"; a.shadowBlur=10; }
else a.shadowBlur=0;
for(let i=0;i<SEA_TEXT.length;i++)
a.fillText(SEA_TEXT[i], i*GS+GS/2, t*GS+GS/2);
}
}
buildAtlas();
// foam glyphs pre-rendered too
const FOAM_CH = ["ᐟ","ˊ","~","ᐠ"];
const foamAtlas = document.createElement('canvas');
(function(){
foamAtlas.width=16*FOAM_CH.length; foamAtlas.height=16;
const a=foamAtlas.getContext('2d');
a.textAlign="center"; a.textBaseline="middle";
a.font="12px serif"; a.fillStyle="rgba(200,230,255,1)";
FOAM_CH.forEach((c,i)=>a.fillText(c,i*16+8,8));
})();
// wind glyph
const windGlyph = document.createElement('canvas');
(function(){
windGlyph.width=20; windGlyph.height=20;
const a=windGlyph.getContext('2d');
a.textAlign="center"; a.textBaseline="middle";
a.font="14px serif"; a.fillStyle="rgba(150,210,255,1)";
a.fillText("风",10,10);
})();
// ---- grid ----
const CELL = 30;
let cols, rows, grid;
function resize(){
W = innerWidth; H = innerHeight;
cvs.width = W; cvs.height = H; // DPR=1 on purpose
ctx.imageSmoothingEnabled = true;
buildGrid();
buildSky();
}
function buildGrid(){
grid = [];
cols = Math.ceil(W/CELL)+2;
const seaTop = H*0.42;
rows = Math.ceil((H-seaTop)/CELL)+1;
for(let r=0;r<rows;r++){
const depth = r/rows;
for(let c=0;c<cols;c++){
grid.push({
x:c*CELL + (r%2)*CELL*0.5,
baseY:seaTop + r*CELL,
gi:(Math.random()*SEA_TEXT.length)|0,
depth,
seed:Math.random()*6.28
});
}
}
}
// pre-rendered sky (gradient + moon glow) — static, blit each frame
const sky = document.createElement('canvas');
function buildSky(){
sky.width=W; sky.height=H;
const s = sky.getContext('2d');
const bg = s.createLinearGradient(0,0,0,H);
bg.addColorStop(0,"#04101f"); bg.addColorStop(0.45,"#061627"); bg.addColorStop(1,"#02070e");
s.fillStyle=bg; s.fillRect(0,0,W,H);
const mg = s.createRadialGradient(W*0.78,H*0.16,0,W*0.78,H*0.16,H*0.3);
mg.addColorStop(0,"rgba(180,215,255,.14)"); mg.addColorStop(1,"transparent");
s.fillStyle=mg; s.fillRect(0,0,W,H);
}
addEventListener('resize', resize);
// ---- wind ----
const wind = {base:0.6, gust:0, gustX:-9999, target:0.6};
const mouse = {vx:0, px:-9999};
addEventListener('pointermove', e=>{
if(mouse.px>-9998) mouse.vx = mouse.vx*0.8 + (e.clientX-mouse.px)*0.2;
mouse.px = e.clientX;
});
function spawnGust(){
wind.gustX = -W*0.3;
anime({
targets: wind,
gustX: W*1.4,
gust: [{value:1,duration:900,easing:'easeOutQuad'},{value:0,duration:2600,easing:'easeInOutSine'}],
duration: 3500 + Math.random()*2000,
easing:'linear',
complete: ()=> setTimeout(spawnGust, 1500+Math.random()*3500)
});
}
// ---- wave model (gust term computed per-column, cached) ----
let gustCol = new Float32Array(0);
function precomputeGust(){
if(gustCol.length!==cols) gustCol = new Float32Array(cols);
const inv = 1/(W*0.18);
for(let c=0;c<cols;c++){
const d = (c*CELL - wind.gustX)*inv;
gustCol[c] = Math.exp(-d*d)*wind.gust;
}
}
function waveY(x, t, depthMul, g){
const amp = (14 + 26*wind.base) * (1-depthMul*0.75) * (1+g*1.6);
return fsin(x*0.012 + t*1.1) * amp*0.55
+ fsin(x*0.021 - t*1.7 + 2) * amp*0.30
+ fsin(x*0.006 + t*0.55) * amp*0.45
+ fsin(x*0.045 - t*2.6) * amp*0.12*(1+g*2);
}
function slopeAt(x,t){
const e=6; return (waveY(x+e,t,0,0)-waveY(x-e,t,0,0))/(2*e);
}
// ---- boat ----
const boat = {x:-140, speed:0.55};
const BOAT_ROWS = [
{txt:"帆", dx:6, dy:-46, size:26, col:"#eaf4ff", sail:true},
{txt:"│", dx:8, dy:-26, size:18, col:"#9db8d4", sail:true},
{txt:"一叶轻舟", dx:0, dy:-4, size:17, col:"#eaf4ff"},
{txt:"╰────╯", dx:0, dy:10, size:15, col:"#7d9cbd"}
];
// ---- spray ----
const MAX_SPRAY = 160;
const sprays = [];
function addSpray(x,y,pow){
if(sprays.length>=MAX_SPRAY) return;
sprays.push({x,y,vx:(Math.random()-0.3)*2*pow,vy:-Math.random()*2.4*pow-0.6,
gi:(Math.random()*FOAM_CH.length)|0, life:1});
}
// ---- adaptive quality: drop deepest rows if slow ----
let quality = 1; // 1 = full, 0.6 = skip deep rows
let fpsAcc=0, fpsN=0, fpsT=0;
// ---- render ----
const windbar = document.getElementById('windbar');
let T=0, last=0, hudTick=0;
function frame(ts){
const dt = Math.min((ts-last)/1000, 0.05); last = ts;
// fps watch → adapt
fpsAcc+=dt; fpsN++; fpsT+=dt;
if(fpsT>2){ const f=fpsN/fpsAcc;
if(f<40 && quality>0.55) quality-=0.15;
else if(f>55 && quality<1) quality+=0.05;
fpsAcc=0;fpsN=0;fpsT=0; }
wind.target = 0.45 + Math.min(Math.abs(mouse.vx)*0.02, 0.9);
wind.base += (wind.target - wind.base)*0.02;
mouse.vx *= 0.95;
T += dt * (0.9 + wind.base*0.8);
if((hudTick++&7)===0)
windbar.style.width = Math.min(100,(wind.base+wind.gust)*62)+"%";
ctx.drawImage(sky,0,0);
precomputeGust();
// ---- sea: pure drawImage from atlas ----
const maxDepth = quality; // skip deepest rows when adapting
const n = grid.length;
for(let i=0;i<n;i++){
const p = grid[i];
if(p.depth > maxDepth) continue;
const c = (p.x/CELL)|0;
const g = gustCol[c<cols?c:cols-1];
const y = p.baseY + waveY(p.x, T + p.depth*0.7, p.depth, g);
const lift = p.baseY - y;
const bright = lift>0 ? lift/34 : 0;
// tier 0..4
let tier = ((0.4 + (1-p.depth)*1.4 + bright*2.2 + g*1.2)) | 0;
if(tier>4) tier=4;
const size = CELL*(0.62 + (1-p.depth)*0.3 + bright*0.25);
const sway = fsin(T*2+p.seed)*2*(1-p.depth) + g*8;
ctx.globalAlpha = Math.min(1, 0.14 + (1-p.depth)*0.5 + bright*0.5 + g*0.25);
ctx.drawImage(atlas, p.gi*GS, tier*GS, GS, GS,
p.x+sway-size*0.5, y-size*0.5, size, size);
if(tier===4 && p.depth<0.12 && bright>1.1 && Math.random()<0.05)
addSpray(p.x, y-8, 1+g);
}
ctx.globalAlpha = 1;
// ---- wind streaks ----
if(wind.gust>0.05){
for(let i=0;i<10;i++){
const wx = wind.gustX + (i-5)*46 + fsin(T*3+i)*10;
const wy = H*0.42 - 60 - (i%4)*26 + fsin(T*5+i*2)*6;
ctx.globalAlpha = 0.35*wind.gust*(1-Math.abs(i-5)/6);
ctx.drawImage(windGlyph, wx, wy);
}
ctx.globalAlpha=1;
}
// ---- boat (few fillText calls — cheap) ----
boat.x += (boat.speed + wind.base*0.9 + wind.gust*2.2) * dt * 36;
if(boat.x > W+160) boat.x = -160;
const by = H*0.42 + waveY(boat.x, T, 0, 0) - 6;
const tilt = Math.atan(slopeAt(boat.x, T))*0.85 + fsin(T*1.4)*0.03;
ctx.save();
ctx.translate(boat.x, by); ctx.rotate(tilt);
ctx.textAlign="center"; ctx.textBaseline="middle";
for(const r of BOAT_ROWS){
ctx.font = r.size+"px 'Songti SC','Noto Serif SC',serif";
ctx.fillStyle = r.col;
const lean = r.sail ? (wind.base+wind.gust)*6 : 0;
ctx.fillText(r.txt, r.dx+lean, r.dy);
}
ctx.restore();
if(Math.random()<0.18+wind.gust*0.4) addSpray(boat.x+30, by+8, 0.9+wind.gust);
// ---- spray ----
for(let i=sprays.length-1;i>=0;i--){
const s=sprays[i];
s.x+=s.vx; s.y+=s.vy; s.vy+=0.09; s.life-=0.028;
if(s.life<=0){ sprays[i]=sprays[sprays.length-1]; sprays.pop(); continue; }
ctx.globalAlpha = s.life*0.8;
ctx.drawImage(foamAtlas, s.gi*16,0,16,16, s.x,s.y,16,16);
}
ctx.globalAlpha=1;
requestAnimationFrame(frame);
}
// ---- boot ----
resize();
requestAnimationFrame(frame);
spawnGust();
const h1 = document.getElementById('h1');
"字海".split('').forEach(c=>{const s=document.createElement('span');s.textContent=c;h1.appendChild(s);});
anime.timeline()
.add({targets:'#h1', opacity:[0,1], duration:400, easing:'linear'})
.add({targets:'#h1 span', translateY:[60,0], opacity:[0,1], delay:anime.stagger(260),
duration:1400, easing:'easeOutExpo'}, 0)
.add({targets:'#sub', opacity:[0,1], translateY:[20,0], duration:1200, easing:'easeOutExpo'}, 800)
.add({targets:['#meta','#hud'], opacity:[0,1], translateY:[14,0], delay:anime.stagger(150),
duration:1000, easing:'easeOutExpo'}, 1300)
.add({targets:'#hint', opacity:[0,0.85], duration:900, easing:'linear'}, 2100)
.add({targets:'#hint', opacity:0, duration:1200, easing:'linear', delay:2600});
</script>
</body>
</html>
-
魔教少主-黑小虎 07-29 15:033楼fable-max
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>文字沧海 · 长风破浪</title>
<style>
:root{
--ink:#1d4e79;
}
*{ margin:0; padding:0; box-sizing:border-box; }
html,body{ width:100%; height:100%; overflow:hidden; }
body{
position:relative;
background:linear-gradient(180deg,#79b4d8 0%, #a7d0e6 34%, #cfe6ef 52%, #f2e3bd 100%);
font-family:"Kaiti SC","STKaiti","KaiTi","TW-Kai","Noto Serif SC","Songti SC","SimSun",serif;
-webkit-user-select:none; user-select:none;
-webkit-tap-highlight-color:transparent;
cursor:default;
}
/* ---------- 天空 ---------- */
#sun{ position:absolute; right:11%; top:11%; width:120px; height:120px; z-index:2; }
#sunCore{
position:absolute; left:50%; top:50%; width:74px; height:74px; margin:-37px 0 0 -37px;
border-radius:50%;
background:radial-gradient(circle at 42% 38%, #fffbe8 0%, #ffe9a8 46%, #ffd670 72%, rgba(255,214,112,0) 100%);
box-shadow:0 0 46px 18px rgba(255,224,140,.55);
display:flex; align-items:center; justify-content:center;
color:#e09f35; font-size:30px;
}
#sunRing{ position:absolute; left:50%; top:50%; width:0; height:0; }
#sunRing span{
position:absolute; left:0; top:0; color:#f4c96b; font-size:14px; opacity:.85;
transform-origin:0 0;
text-shadow:0 0 8px rgba(255,220,130,.8);
}
.cloud{ position:absolute; z-index:3; transition:filter 2.5s; }
.cloud span{
position:absolute; color:#ffffff; text-shadow:0 2px 10px rgba(255,255,255,.9);
}
body.storm .cloud{ filter:brightness(.58) saturate(.7); }
#birds{ position:absolute; inset:0; z-index:4; pointer-events:none; }
.birdGroup{ position:absolute; left:0; top:0; }
.birdGroup i{
position:absolute; font-style:normal; color:#33566e; opacity:.75;
transform:rotate(180deg); display:inline-block;
}
/* ---------- 标题(竖排) ---------- */
#titleBox{
position:absolute; left:4.2%; top:6%; z-index:6;
display:flex; flex-direction:row-reverse; gap:14px;
pointer-events:none;
}
#titleMain{
writing-mode:vertical-rl; font-size:34px; letter-spacing:10px;
color:var(--ink); text-shadow:0 1px 0 rgba(255,255,255,.5);
}
#titleMain span{ display:inline-block; }
#seal{
width:24px; height:24px; margin-top:14px; border-radius:4px;
background:#c33d2e; color:#fff; font-size:15px; line-height:24px; text-align:center;
box-shadow:0 1px 4px rgba(120,30,20,.4); transform:rotate(-3deg);
}
#titleSub{
writing-mode:vertical-rl; font-size:13px; letter-spacing:5px;
color:#41708f; opacity:.9; padding-top:6px;
}
#titleSub em{ font-style:normal; font-size:11px; opacity:.75; margin-top:10px; }
/* ---------- 风带 ---------- */
#streaks{ position:absolute; inset:0; z-index:24; pointer-events:none; }
.streak{
position:absolute; white-space:nowrap; color:#ffffff;
text-shadow:0 0 10px rgba(255,255,255,.75); opacity:0; will-change:transform;
}
.streak .tail{
letter-spacing:3px; opacity:.8;
-webkit-mask-image:linear-gradient(to left,#000 30%,transparent 100%);
mask-image:linear-gradient(to left,#000 30%,transparent 100%);
}
.streak .head{ font-size:1.25em; }
/* ---------- 海 ---------- */
#sea{
position:absolute; left:0; right:0; bottom:0; height:46%;
overflow:hidden; z-index:9;
background:linear-gradient(180deg,#8fb6cc 0%, #5d92b4 13%, #35719b 36%, #1d537f 70%, #123c5f 100%);
transition:filter 2.5s;
}
body.storm #sea{ filter:brightness(.76) saturate(.82); }
#sea::before{ /* 海平线雾光 */
content:""; position:absolute; left:0; right:0; top:0; height:16px; z-index:18;
background:linear-gradient(180deg,rgba(255,246,220,.55),rgba(255,255,255,0));
pointer-events:none;
}
#glint{
position:absolute; top:0; height:64%; width:30vw; z-index:17;
background:radial-gradient(ellipse 50% 60% at 50% 0%, rgba(255,228,150,.38), rgba(255,228,150,0) 70%);
pointer-events:none; mix-blend-mode:soft-light; transition:opacity 2.5s;
}
body.storm #glint{ opacity:.15; }
#haze{
position:absolute; left:0; right:0; top:0; height:34%; z-index:12;
background:linear-gradient(180deg,rgba(214,232,240,.5),rgba(214,232,240,0));
pointer-events:none;
}
.seaRow{ position:absolute; left:0; top:0; white-space:nowrap; will-change:contents; }
.seaRow span{
display:inline-block; transform-origin:50% 100%;
will-change:transform; opacity:0;
}
/* ---------- 小船 ---------- */
#boat{ position:absolute; left:0; top:0; z-index:14; will-change:transform; pointer-events:none; transform-origin:0 0; }
#boatInner{ position:relative; transform-origin:50% 88%; }
#boatInner::before{ /* 柔光,把船从文字背景里衬出来 */
content:""; position:absolute; left:-24px; right:-24px; top:-30px; bottom:-12px;
background:radial-gradient(ellipse 55% 52% at 48% 62%, rgba(255,248,228,.30), rgba(255,248,228,0) 72%);
pointer-events:none;
}
#rig{ position:relative; margin-left:30px; width:92px; height:82px; }
#pennant{
position:absolute; left:-5px; top:-31px; color:#d94f30; font-size:19px;
transform-origin:0 70%;
text-shadow:0 1px 2px rgba(80,20,10,.3), 0 0 6px rgba(255,255,255,.5);
}
#mast{
position:absolute; left:3px; top:-18px; height:102px; width:3px;
background:linear-gradient(#8a5e3a,#4a2f1b); border-radius:2px;
box-shadow:1px 0 1px rgba(0,0,0,.15);
}
#sail{
color:#f9f4e4; font-size:20px; line-height:1; padding-left:8px;
text-shadow:1px 1px 0 rgba(90,70,40,.28);
transform-origin:0 100%;
}
#sail div{ height:20px; }
#hull{
color:#5d3820; font-size:22px; font-weight:bold;
text-shadow:0 1px 0 rgba(255,240,210,.3), 0 2px 4px rgba(15,25,45,.4);
margin-top:0px;
}
/* ---------- 粒子层 ---------- */
#fxBack{ position:absolute; inset:0; z-index:13; pointer-events:none; }
#fxMid{ position:absolute; inset:0; z-index:15; pointer-events:none; }
#fxFront{ position:absolute; inset:0; z-index:22; pointer-events:none; }
#rainLayer{ position:absolute; inset:0; z-index:27; pointer-events:none; }
.pt{ position:absolute; will-change:transform,opacity; }
/* ---------- 风暴罩 / 闪电 ---------- */
#veil{
position:absolute; inset:0; z-index:25; pointer-events:none; opacity:0;
background:linear-gradient(180deg,rgba(24,34,52,.72),rgba(28,40,58,.5) 55%,rgba(16,26,44,.62));
}
#flash{
position:absolute; inset:0; z-index:26; pointer-events:none; opacity:0;
background:radial-gradient(ellipse at 30% 0%, rgba(255,255,255,.95), rgba(220,235,255,.5) 55%, rgba(200,220,255,.25));
}
/* ---------- UI ---------- */
#ui{
position:absolute; left:18px; bottom:16px; z-index:40;
display:flex; align-items:center; gap:8px;
font-family:"PingFang SC","Microsoft YaHei","Noto Sans SC",sans-serif;
}
#ui .lab{ color:rgba(255,255,255,.92); font-size:13px; letter-spacing:2px; text-shadow:0 1px 3px rgba(10,30,50,.5); margin-right:2px; }
.chip{
appearance:none; -webkit-appearance:none; outline:none;
font-family:inherit;
padding:6px 15px; border-radius:999px; font-size:13px; letter-spacing:1px;
color:var(--ink); background:rgba(255,255,255,.55);
border:1px solid rgba(255,255,255,.7);
backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
cursor:pointer; transition:all .35s;
}
.chip:hover{ background:rgba(255,255,255,.8); }
.chip.on{ background:var(--ink); color:#fff; border-color:rgba(255,255,255,.3); }
#hint{
position:absolute; left:50%; bottom:18px; transform:translateX(-50%); z-index:40;
color:rgba(255,255,255,.9); font-size:13px; letter-spacing:3px;
text-shadow:0 1px 4px rgba(10,30,50,.6); pointer-events:none;
font-family:"PingFang SC","Microsoft YaHei","Noto Sans SC",sans-serif;
}
</style>
</head>
<body>
<!-- 天空 -->
<div id="sun"><div id="sunCore">日</div><div id="sunRing"></div></div>
<div id="birds"></div>
<div id="titleBox">
<div style="display:flex;flex-direction:column;align-items:center;gap:0;">
<div id="titleMain"></div>
<div id="seal">舟</div>
</div>
<div id="titleSub">长风破浪会有时,直挂云帆济沧海<em>—— 李白 · 行路难</em></div>
</div>
<div id="streaks"></div>
<!-- 海 -->
<div id="sea"><div id="glint"></div><div id="haze"></div></div>
<!-- 小船 -->
<div id="boat">
<div id="boatInner">
<div id="rig">
<div id="pennant">风</div>
<div id="mast"></div>
<div id="sail">
<div>帆</div>
<div>帆帆</div>
<div>帆帆帆</div>
<div>帆帆帆帆</div>
</div>
</div>
<div id="hull">\乘风破浪/</div>
</div>
</div>
<div id="fxBack"></div>
<div id="fxMid"></div>
<div id="fxFront"></div>
<div id="rainLayer"></div>
<div id="veil"></div>
<div id="flash"></div>
<div id="ui">
<span class="lab">风力</span>
<button class="chip" data-w="0">微风</button>
<button class="chip on" data-w="1">清风</button>
<button class="chip" data-w="2">狂风</button>
</div>
<div id="hint">点击海面 · 唤一阵风</div>
<script>/* anime.js v3.2.2 (MIT License) © Julian Garnier — animejs.com,已内联以便离线使用 */
/*
* anime.js v3.2.2
* (c) 2023 Julian Garnier
* Released under the MIT license
* animejs.com
*/
!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):n.anime=e()}(this,function(){"use strict";var i={update:null,begin:null,loopBegin:null,changeBegin:null,change:null,changeComplete:null,loopComplete:null,complete:null,loop:1,direction:"normal",autoplay:!0,timelineOffset:0},M={duration:1e3,delay:0,endDelay:0,easing:"easeOutElastic(1, .5)",round:0},j=["translateX","translateY","translateZ","rotate","rotateX","rotateY","rotateZ","scale","scaleX","scaleY","scaleZ","skew","skewX","skewY","perspective","matrix","matrix3d"],l={CSS:{},springs:{}};function C(n,e,t){return Math.min(Math.max(n,e),t)}function u(n,e){return-1<n.indexOf(e)}function o(n,e){return n.apply(null,e)}var w={arr:function(n){return Array.isArray(n)},obj:function(n){return u(Object.prototype.toString.call(n),"Object")},pth:function(n){return w.obj(n)&&n.hasOwnProperty("totalLength")},svg:function(n){return n instanceof SVGElement},inp:function(n){return n instanceof HTMLInputElement},dom:function(n){return n.nodeType||w.svg(n)},str:function(n){return"string"==typeof n},fnc:function(n){return"function"==typeof n},und:function(n){return void 0===n},nil:function(n){return w.und(n)||null===n},hex:function(n){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(n)},rgb:function(n){return/^rgb/.test(n)},hsl:function(n){return/^hsl/.test(n)},col:function(n){return w.hex(n)||w.rgb(n)||w.hsl(n)},key:function(n){return!i.hasOwnProperty(n)&&!M.hasOwnProperty(n)&&"targets"!==n&&"keyframes"!==n}};function d(n){n=/\(([^)]+)\)/.exec(n);return n?n[1].split(",").map(function(n){return parseFloat(n)}):[]}function c(r,t){var n=d(r),e=C(w.und(n[0])?1:n[0],.1,100),a=C(w.und(n[1])?100:n[1],.1,100),o=C(w.und(n[2])?10:n[2],.1,100),n=C(w.und(n[3])?0:n[3],.1,100),u=Math.sqrt(a/e),i=o/(2*Math.sqrt(a*e)),c=i<1?u*Math.sqrt(1-i*i):0,s=i<1?(i*u-n)/c:-n+u;function f(n){var e=t?t*n/1e3:n,e=i<1?Math.exp(-e*i*u)*(+Math.cos(c*e)+s*Math.sin(c*e)):(1+s*e)*Math.exp(-e*u);return 0===n||1===n?n:1-e}return t?f:function(){var n=l.springs[r];if(n)return n;for(var e=0,t=0;;)if(1===f(e+=1/6)){if(16<=++t)break}else t=0;return n=e*(1/6)*1e3,l.springs[r]=n}}function q(e){return void 0===e&&(e=10),function(n){return Math.ceil(C(n,1e-6,1)*e)*(1/e)}}var H=function(b,e,M,t){if(0<=b&&b<=1&&0<=M&&M<=1){var x=new Float32Array(11);if(b!==e||M!==t)for(var n=0;n<11;++n)x[n]=k(.1*n,b,M);return function(n){return b===e&&M===t||0===n||1===n?n:k(r(n),e,t)}}function r(n){for(var e=0,t=1;10!==t&&x[t]<=n;++t)e+=.1;var r=e+.1*((n-x[--t])/(x[t+1]-x[t])),a=O(r,b,M);if(.001<=a){for(var o=n,u=r,i=b,c=M,s=0;s<4;++s){var f=O(u,i,c);if(0===f)return u;u-=(k(u,i,c)-o)/f}return u}if(0===a)return r;for(var l,d,p=n,h=e,g=e+.1,m=b,v=M,y=0;0<(l=k(d=h+(g-h)/2,m,v)-p)?g=d:h=d,1e-7<Math.abs(l)&&++y<10;);return d}};function r(n,e){return 1-3*e+3*n}function k(n,e,t){return((r(e,t)*n+(3*t-6*e))*n+3*e)*n}function O(n,e,t){return 3*r(e,t)*n*n+2*(3*t-6*e)*n+3*e}e={linear:function(){return function(n){return n}}},t={Sine:function(){return function(n){return 1-Math.cos(n*Math.PI/2)}},Expo:function(){return function(n){return n?Math.pow(2,10*n-10):0}},Circ:function(){return function(n){return 1-Math.sqrt(1-n*n)}},Back:function(){return function(n){return n*n*(3*n-2)}},Bounce:function(){return function(n){for(var e,t=4;n<((e=Math.pow(2,--t))-1)/11;);return 1/Math.pow(4,3-t)-7.5625*Math.pow((3*e-2)/22-n,2)}},Elastic:function(n,e){void 0===e&&(e=.5);var t=C(n=void 0===n?1:n,1,10),r=C(e,.1,2);return function(n){return 0===n||1===n?n:-t*Math.pow(2,10*(n-1))*Math.sin((n-1-r/(2*Math.PI)*Math.asin(1/t))*(2*Math.PI)/r)}}},["Quad","Cubic","Quart","Quint"].forEach(function(n,e){t[n]=function(){return function(n){return Math.pow(n,e+2)}}}),Object.keys(t).forEach(function(n){var r=t[n];e["easeIn"+n]=r,e["easeOut"+n]=function(e,t){return function(n){return 1-r(e,t)(1-n)}},e["easeInOut"+n]=function(e,t){return function(n){return n<.5?r(e,t)(2*n)/2:1-r(e,t)(-2*n+2)/2}},e["easeOutIn"+n]=function(e,t){return function(n){return n<.5?(1-r(e,t)(1-2*n))/2:(r(e,t)(2*n-1)+1)/2}}});var e,t,s=e;function P(n,e){if(w.fnc(n))return n;var t=n.split("(")[0],r=s[t],a=d(n);switch(t){case"spring":return c(n,e);case"cubicBezier":return o(H,a);case"steps":return o(q,a);default:return o(r,a)}}function a(n){try{return document.querySelectorAll(n)}catch(n){}}function I(n,e){for(var t,r=n.length,a=2<=arguments.length?e:void 0,o=[],u=0;u<r;u++)u in n&&(t=n[u],e.call(a,t,u,n))&&o.push(t);return o}function f(n){return n.reduce(function(n,e){return n.concat(w.arr(e)?f(e):e)},[])}function p(n){return w.arr(n)?n:(n=w.str(n)?a(n)||n:n)instanceof NodeList||n instanceof HTMLCollection?[].slice.call(n):[n]}function h(n,e){return n.some(function(n){return n===e})}function g(n){var e,t={};for(e in n)t[e]=n[e];return t}function x(n,e){var t,r=g(n);for(t in n)r[t]=(e.hasOwnProperty(t)?e:n)[t];return r}function D(n,e){var t,r=g(n);for(t in e)r[t]=(w.und(n[t])?e:n)[t];return r}function V(n){var e,t,r,a,o,u,i;return w.rgb(n)?(e=/rgb\((\d+,\s*[\d]+,\s*[\d]+)\)/g.exec(t=n))?"rgba("+e[1]+",1)":t:w.hex(n)?(e=(e=n).replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i,function(n,e,t,r){return e+e+t+t+r+r}),e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e),"rgba("+parseInt(e[1],16)+","+parseInt(e[2],16)+","+parseInt(e[3],16)+",1)"):w.hsl(n)?(t=/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(t=n)||/hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(t),n=parseInt(t[1],10)/360,u=parseInt(t[2],10)/100,i=parseInt(t[3],10)/100,t=t[4]||1,0==u?r=a=o=i:(r=c(u=2*i-(i=i<.5?i*(1+u):i+u-i*u),i,n+1/3),a=c(u,i,n),o=c(u,i,n-1/3)),"rgba("+255*r+","+255*a+","+255*o+","+t+")"):void 0;function c(n,e,t){return t<0&&(t+=1),1<t&&--t,t<1/6?n+6*(e-n)*t:t<.5?e:t<2/3?n+(e-n)*(2/3-t)*6:n}}function B(n){n=/[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(n);if(n)return n[1]}function m(n,e){return w.fnc(n)?n(e.target,e.id,e.total):n}function v(n,e){return n.getAttribute(e)}function y(n,e,t){var r,a,o;return h([t,"deg","rad","turn"],B(e))?e:(r=l.CSS[e+t],w.und(r)?(a=document.createElement(n.tagName),(n=n.parentNode&&n.parentNode!==document?n.parentNode:document.body).appendChild(a),a.style.position="absolute",a.style.width=100+t,o=100/a.offsetWidth,n.removeChild(a),n=o*parseFloat(e),l.CSS[e+t]=n):r)}function $(n,e,t){var r;if(e in n.style)return r=e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),e=n.style[e]||getComputedStyle(n).getPropertyValue(r)||"0",t?y(n,e,t):e}function b(n,e){return w.dom(n)&&!w.inp(n)&&(!w.nil(v(n,e))||w.svg(n)&&n[e])?"attribute":w.dom(n)&&h(j,e)?"transform":w.dom(n)&&"transform"!==e&&$(n,e)?"css":null!=n[e]?"object":void 0}function W(n){if(w.dom(n)){for(var e,t=n.style.transform||"",r=/(\w+)\(([^)]*)\)/g,a=new Map;e=r.exec(t);)a.set(e[1],e[2]);return a}}function X(n,e,t,r){var a=u(e,"scale")?1:0+(u(a=e,"translate")||"perspective"===a?"px":u(a,"rotate")||u(a,"skew")?"deg":void 0),o=W(n).get(e)||a;return t&&(t.transforms.list.set(e,o),t.transforms.last=e),r?y(n,o,r):o}function T(n,e,t,r){switch(b(n,e)){case"transform":return X(n,e,r,t);case"css":return $(n,e,t);case"attribute":return v(n,e);default:return n[e]||0}}function E(n,e){var t=/^(\*=|\+=|-=)/.exec(n);if(!t)return n;var r=B(n)||0,a=parseFloat(e),o=parseFloat(n.replace(t[0],""));switch(t[0][0]){case"+":return a+o+r;case"-":return a-o+r;case"*":return a*o+r}}function Y(n,e){var t;return w.col(n)?V(n):/\s/g.test(n)?n:(t=(t=B(n))?n.substr(0,n.length-t.length):n,e?t+e:t)}function F(n,e){return Math.sqrt(Math.pow(e.x-n.x,2)+Math.pow(e.y-n.y,2))}function Z(n){for(var e,t=n.points,r=0,a=0;a<t.numberOfItems;a++){var o=t.getItem(a);0<a&&(r+=F(e,o)),e=o}return r}function G(n){if(n.getTotalLength)return n.getTotalLength();switch(n.tagName.toLowerCase()){case"circle":return 2*Math.PI*v(n,"r");case"rect":return 2*v(t=n,"width")+2*v(t,"height");case"line":return F({x:v(t=n,"x1"),y:v(t,"y1")},{x:v(t,"x2"),y:v(t,"y2")});case"polyline":return Z(n);case"polygon":return e=n.points,Z(n)+F(e.getItem(e.numberOfItems-1),e.getItem(0))}var e,t}function Q(n,e){var e=e||{},n=e.el||function(n){for(var e=n.parentNode;w.svg(e)&&w.svg(e.parentNode);)e=e.parentNode;return e}(n),t=n.getBoundingClientRect(),r=v(n,"viewBox"),a=t.width,t=t.height,e=e.viewBox||(r?r.split(" "):[0,0,a,t]);return{el:n,viewBox:e,x:+e[0],y:+e[1],w:a,h:t,vW:e[2],vH:e[3]}}function z(n,e){var t=/[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g,r=Y(w.pth(n)?n.totalLength:n,e)+"";return{original:r,numbers:r.match(t)?r.match(t).map(Number):[0],strings:w.str(n)||e?r.split(t):[]}}function A(n){return I(n?f(w.arr(n)?n.map(p):p(n)):[],function(n,e,t){return t.indexOf(n)===e})}function _(n){var t=A(n);return t.map(function(n,e){return{target:n,id:e,total:t.length,transforms:{list:W(n)}}})}function R(e){for(var t=I(f(e.map(function(n){return Object.keys(n)})),function(n){return w.key(n)}).reduce(function(n,e){return n.indexOf(e)<0&&n.push(e),n},[]),a={},n=0;n<t.length;n++)!function(n){var r=t[n];a[r]=e.map(function(n){var e,t={};for(e in n)w.key(e)?e==r&&(t.value=n[e]):t[e]=n[e];return t})}(n);return a}function J(n,e){var t,r=[],a=e.keyframes;for(t in e=a?D(R(a),e):e)w.key(t)&&r.push({name:t,tweens:function(n,t){var e,r=g(t),a=(/^spring/.test(r.easing)&&(r.duration=c(r.easing)),w.arr(n)&&(2===(e=n.length)&&!w.obj(n[0])?n={value:n}:w.fnc(t.duration)||(r.duration=t.duration/e)),w.arr(n)?n:[n]);return a.map(function(n,e){n=w.obj(n)&&!w.pth(n)?n:{value:n};return w.und(n.delay)&&(n.delay=e?0:t.delay),w.und(n.endDelay)&&(n.endDelay=e===a.length-1?t.endDelay:0),n}).map(function(n){return D(n,r)})}(e[t],n)});return r}function K(i,c){var s;return i.tweens.map(function(n){var n=function(n,e){var t,r={};for(t in n){var a=m(n[t],e);w.arr(a)&&1===(a=a.map(function(n){return m(n,e)})).length&&(a=a[0]),r[t]=a}return r.duration=parseFloat(r.duration),r.delay=parseFloat(r.delay),r}(n,c),e=n.value,t=w.arr(e)?e[1]:e,r=B(t),a=T(c.target,i.name,r,c),o=s?s.to.original:a,u=w.arr(e)?e[0]:o,a=B(u)||B(a),r=r||a;return w.und(t)&&(t=o),n.from=z(u,r),n.to=z(E(t,u),r),n.start=s?s.end:0,n.end=n.start+n.delay+n.duration+n.endDelay,n.easing=P(n.easing,n.duration),n.isPath=w.pth(e),n.isPathTargetInsideSVG=n.isPath&&w.svg(c.target),n.isColor=w.col(n.from.original),n.isColor&&(n.round=1),s=n})}var U={css:function(n,e,t){return n.style[e]=t},attribute:function(n,e,t){return n.setAttribute(e,t)},object:function(n,e,t){return n[e]=t},transform:function(n,e,t,r,a){var o;r.list.set(e,t),e!==r.last&&!a||(o="",r.list.forEach(function(n,e){o+=e+"("+n+") "}),n.style.transform=o)}};function nn(n,u){_(n).forEach(function(n){for(var e in u){var t=m(u[e],n),r=n.target,a=B(t),o=T(r,e,a,n),t=E(Y(t,a||B(o)),o),a=b(r,e);U[a](r,e,t,n.transforms,!0)}})}function en(n,e){return I(f(n.map(function(o){return e.map(function(n){var e,t,r=o,a=b(r.target,n.name);if(a)return t=(e=K(n,r))[e.length-1],{type:a,property:n.name,animatable:r,tweens:e,duration:t.end,delay:e[0].delay,endDelay:t.endDelay}})})),function(n){return!w.und(n)})}function tn(n,e){function t(n){return n.timelineOffset||0}var r=n.length,a={};return a.duration=r?Math.max.apply(Math,n.map(function(n){return t(n)+n.duration})):e.duration,a.delay=r?Math.min.apply(Math,n.map(function(n){return t(n)+n.delay})):e.delay,a.endDelay=r?a.duration-Math.max.apply(Math,n.map(function(n){return t(n)+n.duration-n.endDelay})):e.endDelay,a}var rn=0;var N,S=[],an=("undefined"!=typeof document&&document.addEventListener("visibilitychange",function(){L.suspendWhenDocumentHidden&&(n()?N=cancelAnimationFrame(N):(S.forEach(function(n){return n._onDocumentVisibility()}),an()))}),function(){!(N||n()&&L.suspendWhenDocumentHidden)&&0<S.length&&(N=requestAnimationFrame(on))});function on(n){for(var e=S.length,t=0;t<e;){var r=S[t];r.paused?(S.splice(t,1),e--):(r.tick(n),t++)}N=0<t?requestAnimationFrame(on):void 0}function n(){return document&&document.hidden}function L(n){var c,s=0,f=0,l=0,d=0,p=null;function h(n){var e=window.Promise&&new Promise(function(n){return p=n});return n.finished=e}e=x(i,n=n=void 0===n?{}:n),t=J(r=x(M,n),n),n=_(n.targets),r=tn(t=en(n,t),r),a=rn,rn++;var e,t,r,a,k=D(e,{id:a,children:[],animatables:n,animations:t,duration:r.duration,delay:r.delay,endDelay:r.endDelay});h(k);function g(){var n=k.direction;"alternate"!==n&&(k.direction="normal"!==n?"normal":"reverse"),k.reversed=!k.reversed,c.forEach(function(n){return n.reversed=k.reversed})}function m(n){return k.reversed?k.duration-n:n}function o(){s=0,f=m(k.currentTime)*(1/L.speed)}function v(n,e){e&&e.seek(n-e.timelineOffset)}function y(e){for(var n=0,t=k.animations,r=t.length;n<r;){for(var a=t[n],o=a.animatable,u=a.tweens,i=u.length-1,c=u[i],i=(i&&(c=I(u,function(n){return e<n.end})[0]||c),C(e-c.start-c.delay,0,c.duration)/c.duration),s=isNaN(i)?1:c.easing(i),f=c.to.strings,l=c.round,d=[],p=c.to.numbers.length,h=void 0,g=0;g<p;g++){var m=void 0,v=c.to.numbers[g],y=c.from.numbers[g]||0,m=c.isPath?function(e,t,n){function r(n){return e.el.getPointAtLength(1<=t+(n=void 0===n?0:n)?t+n:0)}var a=Q(e.el,e.svg),o=r(),u=r(-1),i=r(1),c=n?1:a.w/a.vW,s=n?1:a.h/a.vH;switch(e.property){case"x":return(o.x-a.x)*c;case"y":return(o.y-a.y)*s;case"angle":return 180*Math.atan2(i.y-u.y,i.x-u.x)/Math.PI}}(c.value,s*v,c.isPathTargetInsideSVG):y+s*(v-y);!l||c.isColor&&2<g||(m=Math.round(m*l)/l),d.push(m)}var b=f.length;if(b)for(var h=f[0],M=0;M<b;M++){f[M];var x=f[M+1],w=d[M];isNaN(w)||(h+=x?w+x:w+" ")}else h=d[0];U[a.type](o.target,a.property,h,o.transforms),a.currentValue=h,n++}}function b(n){k[n]&&!k.passThrough&&k[n](k)}function u(n){var e=k.duration,t=k.delay,r=e-k.endDelay,a=m(n);if(k.progress=C(a/e*100,0,100),k.reversePlayback=a<k.currentTime,c){var o=a;if(k.reversePlayback)for(var u=d;u--;)v(o,c[u]);else for(var i=0;i<d;i++)v(o,c[i])}!k.began&&0<k.currentTime&&(k.began=!0,b("begin")),!k.loopBegan&&0<k.currentTime&&(k.loopBegan=!0,b("loopBegin")),a<=t&&0!==k.currentTime&&y(0),(r<=a&&k.currentTime!==e||!e)&&y(e),t<a&&a<r?(k.changeBegan||(k.changeBegan=!0,k.changeCompleted=!1,b("changeBegin")),b("change"),y(a)):k.changeBegan&&(k.changeCompleted=!0,k.changeBegan=!1,b("changeComplete")),k.currentTime=C(a,0,e),k.began&&b("update"),e<=n&&(f=0,k.remaining&&!0!==k.remaining&&k.remaining--,k.remaining?(s=l,b("loopComplete"),k.loopBegan=!1,"alternate"===k.direction&&g()):(k.paused=!0,k.completed||(k.completed=!0,b("loopComplete"),b("complete"),!k.passThrough&&"Promise"in window&&(p(),h(k)))))}return k.reset=function(){var n=k.direction;k.passThrough=!1,k.currentTime=0,k.progress=0,k.paused=!0,k.began=!1,k.loopBegan=!1,k.changeBegan=!1,k.completed=!1,k.changeCompleted=!1,k.reversePlayback=!1,k.reversed="reverse"===n,k.remaining=k.loop,c=k.children;for(var e=d=c.length;e--;)k.children[e].reset();(k.reversed&&!0!==k.loop||"alternate"===n&&1===k.loop)&&k.remaining++,y(k.reversed?k.duration:0)},k._onDocumentVisibility=o,k.set=function(n,e){return nn(n,e),k},k.tick=function(n){u(((l=n)+(f-(s=s||l)))*L.speed)},k.seek=function(n){u(m(n))},k.pause=function(){k.paused=!0,o()},k.play=function(){k.paused&&(k.completed&&k.reset(),k.paused=!1,S.push(k),o(),an())},k.reverse=function(){g(),k.completed=!k.reversed,o()},k.restart=function(){k.reset(),k.play()},k.remove=function(n){cn(A(n),k)},k.reset(),k.autoplay&&k.play(),k}function un(n,e){for(var t=e.length;t--;)h(n,e[t].animatable.target)&&e.splice(t,1)}function cn(n,e){var t=e.animations,r=e.children;un(n,t);for(var a=r.length;a--;){var o=r[a],u=o.animations;un(n,u),u.length||o.children.length||r.splice(a,1)}t.length||r.length||e.pause()}return L.version="3.2.1",L.speed=1,L.suspendWhenDocumentHidden=!0,L.running=S,L.remove=function(n){for(var e=A(n),t=S.length;t--;)cn(e,S[t])},L.get=T,L.set=nn,L.convertPx=y,L.path=function(n,e){var t=w.str(n)?a(n)[0]:n,r=e||100;return function(n){return{property:n,el:t,svg:Q(t),totalLength:G(t)*(r/100)}}},L.setDashoffset=function(n){var e=G(n);return n.setAttribute("stroke-dasharray",e),e},L.stagger=function(n,e){var i=(e=void 0===e?{}:e).direction||"normal",c=e.easing?P(e.easing):null,s=e.grid,f=e.axis,l=e.from||0,d="first"===l,p="center"===l,h="last"===l,g=w.arr(n),m=g?parseFloat(n[0]):parseFloat(n),v=g?parseFloat(n[1]):0,y=B(g?n[1]:n)||0,b=e.start||0+(g?m:0),M=[],x=0;return function(n,e,t){if(d&&(l=0),p&&(l=(t-1)/2),h&&(l=t-1),!M.length){for(var r,a,o,u=0;u<t;u++)s?(r=p?(s[0]-1)/2:l%s[0],a=p?(s[1]-1)/2:Math.floor(l/s[0]),r=r-u%s[0],a=a-Math.floor(u/s[0]),o=Math.sqrt(r*r+a*a),"x"===f&&(o=-r),M.push(o="y"===f?-a:o)):M.push(Math.abs(l-u)),x=Math.max.apply(Math,M);c&&(M=M.map(function(n){return c(n/x)*x})),"reverse"===i&&(M=M.map(function(n){return f?n<0?-1*n:-n:Math.abs(x-n)}))}return b+(g?(v-m)/x:m)*(Math.round(100*M[e])/100)+y}},L.timeline=function(u){var i=L(u=void 0===u?{}:u);return i.duration=0,i.add=function(n,e){var t=S.indexOf(i),r=i.children;function a(n){n.passThrough=!0}-1<t&&S.splice(t,1);for(var o=0;o<r.length;o++)a(r[o]);t=D(n,x(M,u)),t.targets=t.targets||u.targets,n=i.duration,t.autoplay=!1,t.direction=i.direction,t.timelineOffset=w.und(e)?n:E(e,n),a(i),i.seek(t.timelineOffset),e=L(t),a(e),r.push(e),n=tn(r,u);return i.delay=n.delay,i.endDelay=n.endDelay,i.duration=n.duration,i.seek(0),i.reset(),i.autoplay&&i.play(),i},i},L.easing=P,L.penner=s,L.random=function(n,e){return Math.floor(Math.random()*(e-n+1))+n},L});
</script>
<script>
/* =========================================================
文字沧海 —— 用文字组成大海:风吹过海面,掀起文字的浪,
一叶文字小船乘风破浪。动画引擎:anime.js
========================================================= */
const TAU = Math.PI * 2;
const $ = s => document.querySelector(s);
const rnd = (a,b) => a + Math.random()*(b-a);
const clamp = (v,a,b) => v<a?a:(v>b?b:v);
let W = innerWidth, H = innerHeight;
let sF = clamp(W/1150, .68, 1.05); // 全局缩放(适配小屏)
/* ---------- 风力档位 ---------- */
const PRESETS = [
{ name:'微风', amp: 7, spd:.85, gust:.75, int:[6.5,11], storm:0 },
{ name:'清风', amp: 14, spd:1.1, gust:1.0, int:[4.5,8], storm:0 },
{ name:'狂风', amp: 26, spd:1.7, gust:1.5, int:[2.2,4.2], storm:1 },
];
let presetIdx = 1;
const dyn = { amp:PRESETS[1].amp, spd:PRESETS[1].spd, storm:0 }; // 由 anime 平滑过渡
if (matchMedia('(prefers-reduced-motion: reduce)').matches) PRESETS.forEach(p=>p.amp*=.55);
/* ---------- 海面文字(诗句) ---------- */
const PHRASES = [
'长风破浪会有时直挂云帆济沧海',
'春江潮水连海平海上明月共潮生',
];
/* ---------- 海的构建 ---------- */
const seaEl = $('#sea');
const rows = [];
let seaTop = 0, seaH = 0;
function buildSea(){
rows.forEach(r => r.el.remove());
rows.length = 0;
W = innerWidth; H = innerHeight;
sF = clamp(W/1150, .68, 1.05);
seaH = seaEl.clientHeight; seaTop = H - seaH;
const N = 6;
const tops = [.045,.14,.27,.42,.60,.80];
const zIdx = [10,11,12,13,16,17]; // 船在 14,中景水花 15
const colorsFix = ['#d9ecf5','#b7d7e7','#93c2da','#5b9ec6','#4a94c2','#3f8ab8'];
for (let i=0;i<N;i++){
const d = i/(N-1);
const depth = .35 + .65*Math.pow(d,1.25); // 波幅深度系数
const fs = Math.round((13 + 17*Math.pow(d,1.15)) * sF);
const ls = Math.round(5*(1-d));
const el = document.createElement('div');
el.className = 'seaRow';
el.style.cssText = `top:${tops[i]*seaH}px; font-size:${fs}px; letter-spacing:${ls}px; z-index:${zIdx[i]};`;
seaEl.appendChild(el);
const phrase = PHRASES[(i+1)%2];
const charW = fs + ls;
const count = Math.ceil(W/charW) + 2;
const els=[], us=[], cols=[];
const base = hexToHsl(colorsFix[i]);
for (let c=0;c<count;c++){
const sp = document.createElement('span');
sp.textContent = phrase[(c + i*3) % phrase.length];
const L = clamp(base.l + rnd(-6,6), 8, 96); // 每字亮度微抖 → 水光粼粼
const col = `hsl(${base.h},${base.s}%,${L}%)`;
sp.style.color = col;
sp.dataset.op = (i<2? .85 : .95);
el.appendChild(sp);
els.push(sp); us.push((c*charW + charW*.5)/W); cols.push(col);
}
rows.push({ el, els, us, cols, d, depth, fs, charW,
ph: i*1.7 + i*i*.31,
y:new Float32Array(count), foam:new Uint8Array(count),
rowTop: tops[i]*seaH });
}
}
function hexToHsl(hex){
const n = parseInt(hex.slice(1),16);
let r=(n>>16&255)/255, g=(n>>8&255)/255, b=(n&255)/255;
const mx=Math.max(r,g,b), mn=Math.min(r,g,b); let h,s,l=(mx+mn)/2;
if(mx===mn){h=s=0;}else{
const dd=mx-mn; s=l>.5?dd/(2-mx-mn):dd/(mx+mn);
switch(mx){case r:h=(g-b)/dd+(g<b?6:0);break;case g:h=(b-r)/dd+2;break;default:h=(r-g)/dd+4;}
h/=6;
}
return {h:Math.round(h*360), s:Math.round(s*100), l:Math.round(l*100)};
}
/* ---------- 阵风 ---------- */
const gusts = [];
function spawnGust(strength, fromU, fast){
const g = { u: fromU!=null? fromU-.22 : -.28, pw:0, sig:rnd(.09,.14), str:strength };
g.inv2 = 1/(2*g.sig*g.sig);
gusts.push(g);
const dur = (fast?1900:3000)/(0.75 + dyn.spd*.35) * rnd(.9,1.15);
anime({ targets:g, u:1.3, duration:dur, easing:'easeInOutSine' });
anime({ targets:g, keyframes:[
{ pw: strength*dyn.amp*1.25, duration:dur*.3, easing:'easeOutQuad' },
{ pw: strength*dyn.amp*.85, duration:dur*.32, easing:'linear' },
{ pw: 0, duration:dur*.38, easing:'easeInQuad' },
],
complete:()=>{ const k=gusts.indexOf(g); if(k>-1) gusts.splice(k,1); } });
windStreak(dur, fromU);
return g;
}
/* 风带:一行「~~~风」贴着海面掠过 */
function windStreak(dur, fromU){
const n = dyn.storm>.5 ? 3 : 2;
for(let i=0;i<n;i++){
const el = document.createElement('div');
el.className = 'streak';
const fs = rnd(15,22)*sF;
el.style.fontSize = fs+'px';
/* 第一条贴近海平线(风扫过海面),其余在高空 */
el.style.top = (i===0 ? rnd(.42,.53) : rnd(.12,.34))*H + 'px';
el.innerHTML = `<span class="tail">${'~'.repeat(Math.round(rnd(8,14)))}</span><span class="head">风</span>`;
$('#streaks').appendChild(el);
const startX = (fromU!=null? fromU*W - W*.45 : -W*.42);
anime({ targets:el,
translateX:[startX, W*1.18], translateY:rnd(-26,26), rotate:rnd(-3,1),
opacity:[{value:.9,duration:dur*.25},{value:.75,duration:dur*.4},{value:0,duration:dur*.35}],
duration:dur*rnd(.95,1.1), delay:i*170, easing:'easeInOutSine',
complete:()=>el.remove() });
}
}
/* ---------- 粒子 ---------- */
function particle(layer, txt, x, y, style){
const sp = document.createElement('span');
sp.className='pt'; sp.textContent=txt;
sp.style.cssText = `left:${x}px; top:${y}px; ${style||''}`;
layer.appendChild(sp);
return sp;
}
const SPRAY_CH = ['°','∘','。','·','°','∘','沫','花'];
function spray(x, y, n, big, layer){
for(let i=0;i<n;i++){
const ch = SPRAY_CH[Math.floor(Math.random()*SPRAY_CH.length)];
const sp = particle(layer||$('#fxBack'), ch, x, y,
`color:#eef8ff; font-size:${rnd(9,big?16:12)*sF}px; text-shadow:0 0 6px rgba(255,255,255,.7); opacity:0;`);
const dx = rnd(-1,1)*(big?46:26)*sF, up = rnd(14, big?52:34)*sF;
anime({ targets:sp,
keyframes:[
{ translateY:-up, translateX:dx*.6, opacity:.95, duration:rnd(220,340), easing:'easeOutQuad' },
{ translateY:up*.7, translateX:dx, opacity:0, duration:rnd(380,520), easing:'easeInQuad' },
],
rotate:rnd(-90,90),
complete:()=>sp.remove() });
}
}
/* ---------- 小船 ---------- */
const boatEl = $('#boat'), boatInner = $('#boatInner');
const boat = { u:.18, y:0, a:0, surge:0, lastSlam:0, h:112 };
const BOAT_D = .72; // 船所在浪层深度
function boatWaterline(){ return seaTop + rows[4].rowTop + rows[4].fs*.1; }
let pennantAnim=null, sailFlutter=null;
function boatIdleAnims(){
if(pennantAnim) pennantAnim.pause();
if(sailFlutter) sailFlutter.pause();
const speed = 900 - dyn.amp*18;
pennantAnim = anime({ targets:'#pennant',
keyframes:[{skewY:-10,rotate:-6},{skewY:8,rotate:3},{skewY:-6,rotate:-2},{skewY:10,rotate:5}],
duration:Math.max(380,speed), loop:true, easing:'easeInOutSine' });
sailFlutter = anime({ targets:'#rig',
rotate:[{value:-1.1},{value:1.1}], duration:Math.max(500,speed*1.4),
direction:'alternate', loop:true, easing:'easeInOutSine' });
}
/* ---------- 波形函数 ---------- */
function waveEnv(u){ // 阵风局部影响:附加波幅 + 风倾
let gAmp=0;
for(const g of gusts){
const du=u-g.u, e=Math.exp(-du*du*g.inv2);
gAmp += g.pw*e;
}
return {gAmp, lean:gAmp/22};
}
function waveY(u, t, ph, depth){
const A = dyn.amp * depth * sF * (1 + .16*Math.sin(TAU*t/23)); // 海的呼吸
const y1 = .58*Math.sin(TAU*1.35*u - TAU*.30*dyn.spd*t + ph);
const y2 = .30*Math.sin(TAU*2.90*u - TAU*.52*dyn.spd*t + ph*1.7 + 1.3);
const y3 = .18*Math.sin(TAU*5.80*u - TAU*.83*dyn.spd*t + ph*2.4);
const env = waveEnv(u);
const yg = env.gAmp * depth * Math.sin(TAU*6.2*u - TAU*.9*t);
return { y: A*(y1+y2+y3) + yg,
dx: A*.42*Math.cos(TAU*1.35*u - TAU*.30*dyn.spd*t + ph),
A: A*1.06 + env.gAmp*depth,
lean: env.lean };
}
/* ---------- 主渲染循环(由 anime 引擎驱动) ---------- */
const FOAM_SHADOW = '0 0 7px rgba(255,255,255,.95)';
let prevT = 0;
function render(t){
const dt = clamp(t - prevT, 0, .05); prevT = t;
/* --- 海面文字 --- */
for(const r of rows){
const n = r.els.length;
for(let i=0;i<n;i++){
const w = waveY(r.us[i], t, r.ph, r.depth);
r.y[i] = w.y;
r._lean = w.lean; // 行内近似即可
r._A = w.A;
const el = r.els[i];
const crest = -w.y / (w.A+3);
const wasFoam = r.foam[i];
if(!wasFoam && crest > .68){ r.foam[i]=1; el.style.color='#f4fbff'; el.style.textShadow=FOAM_SHADOW; }
else if(wasFoam && crest < .54){ r.foam[i]=0; el.style.color=r.cols[i]; el.style.textShadow='none'; }
el._dx = w.dx; el._lean = w.lean;
}
for(let i=0;i<n;i++){
const el = r.els[i];
const yl = r.y[i>0?i-1:i], yr = r.y[i<n-1?i+1:i];
const rot = r.d>.5 ? clamp((yr-yl)/(2*r.charW)*34, -11, 11) : 0;
const sk = clamp(-el._lean*13, -16, 16);
el.style.transform =
`translate3d(${el._dx.toFixed(1)}px,${r.y[i].toFixed(1)}px,0) rotate(${rot.toFixed(1)}deg) skewX(${sk.toFixed(1)}deg)${r.foam[i]?' scale(1.07)':''}`;
}
}
/* --- 小船 --- */
boat.u += dt*( .0135 + dyn.amp*.00042 + boat.surge );
if(boat.u > 1.15) boat.u = -.15;
const bw = waveY(boat.u, t, rows[3].ph, BOAT_D);
const e = .012;
const slope = (waveY(boat.u+e,t,rows[3].ph,BOAT_D).y - waveY(boat.u-e,t,rows[3].ph,BOAT_D).y) / (2*e*W);
const targetY = bw.y, targetA = clamp(Math.atan(slope)*57.3*.85, -16, 16) + bw.lean*7;
/* 阵风推船:涌浪 + 加速 */
if(bw.lean > .25 && boat.surge < .004) {
anime({ targets:boat, surge:.0085, duration:500, easing:'easeOutQuad',
complete:()=>anime({targets:boat, surge:0, duration:1600, easing:'easeOutQuad'}) });
}
boat.y += (targetY - boat.y)*Math.min(1, dt*5.5);
boat.a += (targetA - boat.a)*Math.min(1, dt*4.2);
/* 拍浪水花 */
if(targetY - boat.y > 9*sF && t-boat.lastSlam > 1.1){
boat.lastSlam = t;
spray(boat._x + rnd(30,90)*sF, boatWaterline()+boat.y, 5+Math.round(dyn.amp*.2), dyn.amp>18, $('#fxMid'));
}
const bx = boat.u*(W+260*sF) - 130*sF;
boat._x = bx;
boatEl.style.transform = `translate3d(${bx.toFixed(1)}px,${(boatWaterline()+boat.y-(boat.h-7)*sF).toFixed(1)}px,0) scale(${sF})`;
boatInner.style.transform = `rotate(${boat.a.toFixed(1)}deg)`;
$('#sail').style.transform = `skewX(${(-7 - dyn.amp*.28 - bw.lean*12).toFixed(1)}deg) scaleX(${(1+dyn.amp*.006).toFixed(3)})`;
/* 尾迹 */
if(t - (render._wk||0) > .22){
render._wk = t;
const wy = boatWaterline()+boat.y+rnd(-2,5);
const sp = particle($('#fxBack'), ['∘','。','~','°'][Math.floor(rnd(0,4))], bx+rnd(4,22)*sF, wy,
`color:#e6f4fc; font-size:${rnd(10,15)*sF}px; opacity:.9; text-shadow:0 0 4px rgba(255,255,255,.4);`);
anime({ targets:sp, translateX:-rnd(30,80)*sF, translateY:rnd(0,8), opacity:0, scale:rnd(.9,1.5),
duration:rnd(1000,1600), easing:'easeOutQuad', complete:()=>sp.remove() });
}
/* --- 风暴罩 --- */
$('#veil').style.opacity = (dyn.storm*.55).toFixed(2);
}
/* anime 作为总时钟 */
const clock = { t:0 };
anime({ targets:clock, t:36000, duration:36000000, easing:'linear', loop:true,
update: () => render(clock.t) });
/* ---------- 自动阵风 ---------- */
(function autoGust(){
const p = PRESETS[presetIdx];
setTimeout(()=>{ spawnGust(rnd(.7,1.15)*p.gust); autoGust(); }, rnd(p.int[0],p.int[1])*1000);
})();
/* ---------- 鱼跃 ---------- */
(function fishLoop(first){
setTimeout(()=>{
if(dyn.storm < .4){
const x0 = rnd(.15,.8)*W;
const wl = seaTop + rows[4].rowTop + rows[4].fs*.2;
const fish = particle($('#fxBack'), '鱼', x0, wl,
`color:#9fc9de; font-size:${19*sF}px; text-shadow:0 1px 3px rgba(20,50,80,.4);`);
spray(x0+6, wl+4, 4);
anime({ targets:fish,
translateX: 95*sF,
keyframes:[
{ translateY:-78*sF, duration:430, easing:'easeOutQuad' },
{ translateY: 16*sF, duration:430, easing:'easeInQuad' },
],
rotate:[-52, 78],
duration:860, easing:'linear',
complete:()=>{ spray(x0+95*sF, wl+6, 5); fish.remove(); } });
}
fishLoop(false);
}, (first? rnd(5,8) : rnd(9,16))*1000);
})(true);
/* ---------- 海鸟 ---------- */
let birdCount = 0;
(function birdLoop(first){
setTimeout(()=>{
if(dyn.storm < .4 && birdCount < 2){
birdCount++;
const gEl = document.createElement('div');
gEl.className='birdGroup';
const n = Math.round(rnd(1,3));
for(let i=0;i<n;i++){
const b=document.createElement('i'); b.textContent='人';
const fs = rnd(9,13)*sF;
b.style.cssText=`left:${i*rnd(16,26)}px; top:${rnd(-8,8)}px; font-size:${fs}px;`;
gEl.appendChild(b);
anime({ targets:b, scaleY:[1,.3], duration:rnd(240,320), direction:'alternate',
loop:true, easing:'easeInOutSine', delay:i*90 });
}
$('#birds').appendChild(gEl);
const y0 = rnd(.08,.3)*H, ltr = Math.random()<.7;
const dur = rnd(13000,19000);
/* 单实例:X 匀速穿越,Y 分段起伏(滑翔) */
const seg = 6, yKeys = [];
let yy = y0;
for(let k=0;k<seg;k++){ yy += rnd(-26,26); yKeys.push({ value:yy, duration:dur/seg, easing:'easeInOutSine' }); }
gEl.style.transform = `translate(${ltr?-80:W+80}px, ${y0}px)`;
anime({ targets:gEl,
translateX: ltr? [-80, W+80] : [W+80, -80],
translateY: yKeys,
duration:dur, easing:'linear',
complete:()=>{ gEl.remove(); birdCount--; } });
}
birdLoop(false);
}, (first? rnd(2.5,4) : rnd(9,20))*1000);
})(true);
/* ---------- 暴风雨:文字雨 + 闪电 ---------- */
let rainN = 0;
setInterval(()=>{
if(dyn.storm > .45 && rainN < 30){
rainN++;
const sp = particle($('#rainLayer'), '丶', rnd(-.25,1)*W, -20,
`color:rgba(218,238,255,.85); font-size:${rnd(12,17)}px;`);
anime({ targets:sp, translateY:H*.98, translateX:W*.22, opacity:[.85,.4],
duration:rnd(520,720), easing:'linear', complete:()=>{ sp.remove(); rainN--; } });
}
}, 75);
function bolt(){
anime({ targets:'#flash', opacity:[0,.42,.06,.3,0], duration:640, easing:'linear' });
}
(function boltLoop(){
setTimeout(()=>{ if(dyn.storm > .6) bolt(); boltLoop(); }, rnd(8,15)*1000);
})();
/* ---------- 太阳 / 云 ---------- */
(function buildSun(){
const ring = $('#sunRing'), R = 62;
for(let i=0;i<10;i++){
const s=document.createElement('span'); s.textContent='光';
s.style.transform = `rotate(${i*36}deg) translateY(${-R}px) rotate(${-i*36}deg)`;
ring.appendChild(s);
}
anime({ targets:ring, rotate:'1turn', duration:46000, easing:'linear', loop:true });
anime({ targets:'#sunCore', scale:[1,1.06], duration:3800, direction:'alternate', loop:true, easing:'easeInOutSine' });
})();
function makeCloud(x, y, s){
const c = document.createElement('div');
c.className='cloud';
c.style.cssText = `left:${x}px; top:${y}px; font-size:${Math.round(17*s*sF)}px;`;
const pos = [[0,10],[14,4],[30,8],[46,3],[62,9],[76,13],[8,-6],[24,-10],[40,-12],[56,-7],[16,18],[34,21],[52,19],[68,15],[44,-2]];
pos.forEach(p=>{
const sp=document.createElement('span');
sp.textContent='云';
sp.style.cssText=`left:${p[0]*s*sF}px; top:${p[1]*s*sF}px; opacity:${rnd(.45,.9)};`;
c.appendChild(sp);
});
document.body.appendChild(c);
(function drift(first){
const startX = first? 0 : -(90*s*sF + 120);
const dist = W + 240;
anime({ targets:c, translateX:[startX, dist], duration:(dist-startX)*rnd(700,950)/10,
easing:'linear', complete:()=>drift(false) });
})(true);
return c;
}
/* ---------- 交互 ---------- */
document.addEventListener('pointerdown', ev=>{
if(ev.target.closest('#ui')) return;
if(ev.clientY < H*.42) return;
const u = ev.clientX/W;
spawnGust(rnd(.9,1.25)*PRESETS[presetIdx].gust, u, true);
/* 点击处「风」字绽开 */
const sp = particle($('#fxFront'), '风', ev.clientX-10, ev.clientY-12,
`color:rgba(255,255,255,.95); font-size:20px; text-shadow:0 0 10px rgba(255,255,255,.9);`);
anime({ targets:sp, scale:[.6,2.4], opacity:[.95,0], rotate:15, duration:900,
easing:'easeOutQuad', complete:()=>sp.remove() });
spray(ev.clientX, ev.clientY, 6, false, $('#fxFront'));
anime({ targets:'#hint', opacity:.35, duration:800, easing:'linear' });
});
document.querySelectorAll('.chip').forEach(btn=>{
btn.addEventListener('click', ()=>{
document.querySelectorAll('.chip').forEach(b=>b.classList.remove('on'));
btn.classList.add('on');
presetIdx = +btn.dataset.w;
const p = PRESETS[presetIdx];
anime({ targets:dyn, amp:p.amp, spd:p.spd, storm:p.storm,
duration:2000, easing:'easeInOutQuad', complete:boatIdleAnims });
document.body.classList.toggle('storm', p.storm>0);
if(p.storm){ spawnGust(1.4); setTimeout(()=>{ if(dyn.storm>.3) bolt(); }, rnd(1800,3200)); }
});
});
/* ---------- 启动 ---------- */
function intro(){
/* 海字逐浪浮现 */
rows.forEach((r,ri)=>{
anime({ targets:r.els, opacity: el=>+el.dataset.op,
duration:900, delay:anime.stagger(6,{start:ri*130}), easing:'easeOutCubic' });
});
/* 标题落墨 */
const tm = $('#titleMain');
'文字沧海'.split('').forEach(ch=>{ const s=document.createElement('span'); s.textContent=ch; tm.appendChild(s); });
anime({ targets:'#titleMain span', opacity:[0,1], translateY:[-14,0],
delay:anime.stagger(160,{start:250}), duration:700, easing:'easeOutCubic' });
anime({ targets:'#seal', opacity:[0,1], scale:[.3,1], delay:1100, duration:600, easing:'easeOutBack' });
anime({ targets:'#titleSub', opacity:[0,.9], delay:1300, duration:900, easing:'linear' });
anime({ targets:'#boat', opacity:[0,1], duration:1400, delay:300, easing:'linear' });
anime({ targets:'#hint', opacity:[0,.9], delay:2200, duration:1200, easing:'linear' });
setTimeout(()=>spawnGust(1.0), 1500);
}
let glintX;
function layout(){
glintX = W*.89 - W*.15;
$('#glint').style.left = (W*.89 - W*.15)+'px';
}
buildSea(); layout(); boatIdleAnims();
makeCloud(W*.16, H*.10, 1); makeCloud(W*.42, H*.17, .62); makeCloud(W*.58, H*.24, .8); makeCloud(W*.8, H*.06, 1.15);
intro();
setTimeout(()=>{ boat.h = boatEl.offsetHeight || 110; }, 400);
let rsT;
addEventListener('resize', ()=>{
clearTimeout(rsT);
rsT = setTimeout(()=>{ buildSea(); layout();
boat.h = boatEl.offsetHeight || 110;
rows.forEach(r=>r.els.forEach(el=>el.style.opacity=el.dataset.op)); }, 350);
});
</script>
</body>
</html>
-
starpipi 07-29 15:444楼kimi-k3的感觉还行,居然做了个鱼跳出来的效果
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文字之海 · Text Sea</title>
<style>
html, body {
margin: 0; height: 100%; overflow: hidden;
background: linear-gradient(#04091a 0%, #0a1c38 48%, #07101f 100%);
font-family: "Songti SC", "STSong", "Noto Serif SC", serif;
cursor: default;
}
#sky { position: absolute; inset: 0; }
.star { position: absolute; color: #9db8e8; opacity: .4; font-size: 12px; user-select: none; }
#moon {
position: absolute; top: 7%; right: 9%; font-size: 46px; color: #ffe9b8;
text-shadow: 0 0 26px rgba(255,233,184,.85), 0 0 90px rgba(255,233,184,.35);
user-select: none;
}
.cloud { position: absolute; color: rgba(190,215,255,.30); font-size: 22px; letter-spacing: 4px; user-select: none; }
.streak { position: absolute; color: rgba(170,215,255,0); font-size: 16px; user-select: none; }
#sea { position: absolute; left: 0; right: 0; bottom: 0; }
.sea-char {
position: absolute; width: 16px; text-align: center; font-size: 15px;
color: #2e7fb8; user-select: none; will-change: transform;
}
#boat { position: absolute; left: 0; top: 0; will-change: transform; user-select: none; }
.boat-char { position: absolute; width: 16px; text-align: center; font-size: 15px; line-height: 18px; }
#fish {
position: absolute; font-size: 15px; color: #8fe3ff; opacity: 0;
user-select: none; text-shadow: 0 0 8px rgba(143,227,255,.6); white-space: nowrap;
}
.splash { position: absolute; font-size: 13px; color: #cfeaff; user-select: none; }
#title {
position: absolute; top: 6%; left: 5%; writing-mode: vertical-rl;
color: rgba(215,232,255,.85); font-size: 20px; letter-spacing: 8px;
text-shadow: 0 0 14px rgba(120,180,255,.35); user-select: none; opacity: 0;
}
#hint {
position: absolute; bottom: 12px; right: 16px; color: rgba(160,190,230,.35);
font-size: 12px; letter-spacing: 2px; user-select: none;
}
</style>
</head>
<body>
<div id="sky"></div>
<div id="moon">月</div>
<div id="sea"></div>
<div id="boat"></div>
<div id="fish">><(((°></div>
<div id="title">風起時 · 文字成海</div>
<div id="hint">点击海面 唤起一阵风</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<script>
(() => {
const W = () => window.innerWidth;
const H = () => window.innerHeight;
const CW = 16, RH = 18; // 字符格子尺寸
const SEA_CHARS = ['~','~','~','≈','∽','~','~'];
const seaEl = document.getElementById('sea');
const boatEl = document.getElementById('boat');
const fishEl = document.getElementById('fish');
const skyEl = document.getElementById('sky');
const seaTop = () => H() * 0.55;
// ---------- 海面:字符网格 ----------
let cols = 0, rows = 0, cells = [];
const SURFACE_TONES = ['#2e7fb8','#3f97cf','#5cb2e2','#86cdf2','#c9ecff'];
const NEAR_TONES = ['#1f6395','#2a74ab','#3786c1','#4a9bd4','#66b3e6'];
function buildSea() {
seaEl.innerHTML = '';
cells = [];
cols = Math.ceil(W() / CW) + 1;
rows = Math.floor(H() * 0.46 / RH);
seaEl.style.top = seaTop() + 'px';
const frag = document.createDocumentFragment();
for (let r = 0; r < rows; r++) {
const depthAtt = Math.pow(0.86, r); // 越深越平静
const baseColor = r === 0 ? SURFACE_TONES[0]
: r < 4 ? NEAR_TONES[0]
: `rgba(27,90,138,${Math.max(0.12, 0.9 - r * 0.07)})`;
for (let c = 0; c < cols; c++) {
const el = document.createElement('span');
el.className = 'sea-char';
el.textContent = SEA_CHARS[(Math.random() * SEA_CHARS.length) | 0];
el.style.left = c * CW + 'px';
el.style.top = r * RH + 'px';
el.style.color = baseColor;
frag.appendChild(el);
cells.push({ el, x: c * CW, row: r, depthAtt, lastLevel: 0 });
}
}
seaEl.appendChild(frag);
}
// ---------- 风与浪 ----------
const wind = { strength: 1 }; // 全局风力(anime 驱动)
const gusts = []; // 阵风(移动的浪峰包络)
function waveAt(x, t) {
const s = wind.strength;
let y = Math.sin(x * 0.012 + t * 1.15) * 9 * s
+ Math.sin(x * 0.024 - t * 1.8 + 2.0) * 5.5 * s
+ Math.sin(x * 0.045 + t * 2.9 + 5.2) * 2.5 * s;
for (const g of gusts) {
const d = (x - g.x) / g.w;
if (d > -3 && d < 3) y += Math.exp(-d * d) * g.amp * Math.sin(x * 0.05 - t * 5 + g.phase);
}
return y;
}
// 全局风:呼吸般强弱变化
function breeze() {
anime({
targets: wind,
strength: 0.6 + Math.random() * 1.15,
duration: 3000 + Math.random() * 3500,
easing: 'easeInOutSine',
complete: breeze
});
}
// 阵风:一个高斯包络从左向右扫过海面
function spawnGust(fromClick) {
const g = {
x: -300,
w: 120 + Math.random() * 170,
amp: (6 + Math.random() * 9) * (fromClick ? 1.5 : 1),
phase: Math.random() * 6
};
gusts.push(g);
anime({
targets: g,
x: W() + 300,
duration: 3200 + Math.random() * 2600,
easing: 'linear',
complete() {
gusts.splice(gusts.indexOf(g), 1);
if (!fromClick) setTimeout(() => spawnGust(false), 900 + Math.random() * 3200);
}
});
windStreaks();
}
// 风的痕迹:空中掠过的「〜」
const streaks = [];
function windStreaks() {
for (let i = 0; i < 3; i++) {
const el = streaks[(Math.random() * streaks.length) | 0];
if (!el) return;
el.style.top = (H() * (0.12 + Math.random() * 0.3)) + 'px';
el.style.left = '0px';
anime.remove(el);
anime({
targets: el,
translateX: [-120, W() + 120],
opacity: [{ value: 0.45, duration: 500 }, { value: 0, duration: 1600 }],
delay: i * 260 + Math.random() * 300,
duration: 2600 + Math.random() * 1200,
easing: 'linear'
});
}
}
// ---------- 小船 ----------
const ART = [
" 丨☆ ",
" 丨帆 ",
" 丨帆帆 ",
" 丨帆帆帆 ",
" 丨帆帆帆帆",
"\船船船船船船/"
];
const CHAR_STYLE = {
'帆': { color: '#f3ede0', glow: 'rgba(243,237,224,.35)' },
'丨': { color: '#b08954', glow: 'rgba(176,137,84,.3)' },
'船': { color: '#d29a63', glow: 'rgba(210,154,99,.4)' },
'\': { color: '#d29a63', glow: 'rgba(210,154,99,.4)' },
'/': { color: '#d29a63', glow: 'rgba(210,154,99,.4)' },
'☆': { color: '#ff6b6b', glow: 'rgba(255,107,107,.7)' }
};
const BOAT_W = 10 * CW, BOAT_H = ART.length * RH;
const boat = { x: -300, rot: 0 };
function buildBoat() {
boatEl.innerHTML = '';
ART.forEach((line, r) => {
[...line].forEach((ch, c) => {
if (ch === ' ') return;
const st = CHAR_STYLE[ch];
const el = document.createElement('span');
el.className = 'boat-char';
el.textContent = ch;
el.style.left = c * CW + 'px';
el.style.top = r * RH + 'px';
el.style.color = st.color;
el.style.textShadow = `0 0 8px ${st.glow}`;
boatEl.appendChild(el);
});
});
}
// ---------- 船尾航迹 ----------
const wake = []; // {x, life}
let wakeTick = 0;
// ---------- 主循环 ----------
let last = performance.now();
const t0 = last;
function frame(now) {
const t = (now - t0) / 1000;
const dt = Math.min(0.05, (now - last) / 1000);
last = now;
// 船前行(顺风更快)
boat.x += 26 * (0.6 + wind.strength * 0.45) * dt;
if (boat.x > W() + 120) boat.x = -BOAT_W - 160;
const cx = boat.x + BOAT_W / 2;
const by = waveAt(cx, t);
const slope = (waveAt(cx + 10, t) - waveAt(cx - 10, t)) / 20;
const targetRot = Math.atan(slope) * 0.9 + Math.sin(t * 2.1) * 0.02;
boat.rot += (targetRot - boat.rot) * 0.08; // 惯性:转向略滞后,更真实
boatEl.style.transform =
`translate(${boat.x}px, ${seaTop() + by - BOAT_H + 10}px) rotate(${boat.rot}rad)`;
// 记录航迹
if (++wakeTick % 6 === 0) wake.push({ x: cx - BOAT_W / 2, life: 1 });
for (let i = wake.length - 1; i >= 0; i--) {
wake[i].life -= dt * 0.55;
if (wake[i].life <= 0) wake.splice(i, 1);
}
// 海面字符
for (const c of cells) {
const wy = waveAt(c.x, t - c.row * 0.10) * c.depthAtt;
const drift = Math.sin(t * 0.5 + c.row * 0.7 + c.x * 0.002) * c.row * 0.3;
c.el.style.transform = `translate(${drift.toFixed(1)}px, ${wy.toFixed(1)}px)`;
if (c.row < 4) { // 表层:浪峰变亮 + 航迹泛白
let boost = Math.max(0, -wy / (10 * wind.strength + 5));
if (c.row < 2) {
for (const p of wake) {
const d = (c.x - p.x) / 34;
if (d > -3 && d < 3) boost += Math.exp(-d * d) * p.life * 0.9;
}
}
const level = Math.min(4, (boost * 5) | 0);
if (level !== c.lastLevel) {
c.lastLevel = level;
c.el.style.color = (c.row === 0 ? SURFACE_TONES : NEAR_TONES)[level];
}
}
}
requestAnimationFrame(frame);
}
// ---------- 鱼跃 ----------
function splash(x, y) {
for (let i = 0; i < 4; i++) {
const s = document.createElement('span');
s.className = 'splash';
s.textContent = ['。','˚','・','。'][i];
s.style.left = x + 'px';
s.style.top = y + 'px';
document.body.appendChild(s);
anime({
targets: s,
translateX: (Math.random() - 0.5) * 70,
translateY: -20 - Math.random() * 30,
opacity: [1, 0],
duration: 700 + Math.random() * 400,
easing: 'easeOutQuad',
complete: () => s.remove()
});
}
}
function fishJump() {
const x = 120 + Math.random() * (W() - 320);
const y = seaTop() + waveAt(x, (performance.now() - t0) / 1000) - 6;
fishEl.style.left = x + 'px';
fishEl.style.top = y + 'px';
splash(x, y);
anime.remove(fishEl);
anime({
targets: fishEl,
opacity: [{ value: 1, duration: 150 }, { value: 1, duration: 900 }, { value: 0, duration: 250 }],
translateX: [{ value: 60, duration: 550, easing: 'easeOutQuad' }, { value: 140, duration: 750, easing: 'easeInQuad' }],
translateY: [{ value: -95, duration: 550, easing: 'easeOutQuad' }, { value: 14, duration: 750, easing: 'easeInQuad' }],
rotate: [{ value: -25, duration: 400 }, { value: 40, duration: 900 }],
duration: 1300,
complete() {
splash(x + 140, y + 14);
setTimeout(fishJump, 6000 + Math.random() * 9000);
}
});
}
// ---------- 天空 ----------
function buildSky() {
skyEl.innerHTML = '';
const starChars = ['·','+','˚','∙'];
for (let i = 0; i < 45; i++) {
const s = document.createElement('span');
s.className = 'star';
s.textContent = starChars[(Math.random() * starChars.length) | 0];
s.style.left = Math.random() * 100 + '%';
s.style.top = Math.random() * seaTop() * 0.75 + 'px';
skyEl.appendChild(s);
anime({
targets: s,
opacity: [0.1, 0.85],
direction: 'alternate',
loop: true,
duration: 1200 + Math.random() * 2200,
delay: Math.random() * 2000,
easing: 'easeInOutSine'
});
}
for (let i = 0; i < 4; i++) {
const c = document.createElement('span');
c.className = 'cloud';
c.textContent = ['云 云','云','云 云 云','云 云'][i];
c.style.top = (H() * (0.08 + i * 0.09)) + 'px';
skyEl.appendChild(c);
anime({
targets: c,
translateX: [-220, W() + 220],
duration: 70000 + Math.random() * 60000,
easing: 'linear',
loop: true
});
}
for (let i = 0; i < 6; i++) {
const st = document.createElement('span');
st.className = 'streak';
st.textContent = '〜';
skyEl.appendChild(st);
streaks.push(st);
}
anime({ targets: '#moon', scale: [1, 1.05], direction: 'alternate', loop: true, duration: 4000, easing: 'easeInOutSine' });
anime({ targets: '#title', opacity: [0, 1], translateY: [-12, 0], duration: 2400, easing: 'easeOutQuad', delay: 600 });
}
// ---------- 交互:点击唤风 ----------
window.addEventListener('pointerdown', () => spawnGust(true));
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => { buildSea(); buildSky(); }, 200);
});
// ---------- 启动 ----------
buildSea();
buildBoat();
buildSky();
breeze();
setTimeout(() => spawnGust(false), 1200);
setTimeout(fishJump, 4000);
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
-
Haskell 07-29 15:455楼楼上的大伙,放个效果图吧
懒得下本地看了哈哈
@here -
scp3500 07-29 16:036楼<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文字之海 · 风吹过文字的海</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #04101f; }
body {
font-family: "SimSun", "NSimSun", "Courier New", monospace;
background: linear-gradient(to bottom,
#02060f 0%, #061428 30%, #0a2440 48%,
#0d3054 55%, #082647 100%);
user-select: none;
cursor: default;
}
/* ---------- 天空 ---------- */
#sky { position: absolute; inset: 0; pointer-events: none; }
.star { position: absolute; color: #cfe4ff; font-size: 12px; opacity: .6; }
#moon {
position: absolute; top: 7%; right: 10%;
font-size: 44px; color: #f5e9c8;
text-shadow: 0 0 18px rgba(245,233,200,.85), 0 0 46px rgba(245,233,200,.45);
}
.cloud { position: absolute; color: rgba(190,215,240,.28); font-size: 20px; letter-spacing: 2px; white-space: nowrap; }
.gull { position: absolute; color: rgba(220,235,250,.75); font-size: 16px; }
/* ---------- 标题 ---------- */
#title {
position: absolute; top: 10%; width: 100%; text-align: center;
color: #dceeff; font-size: 30px; letter-spacing: 14px;
text-shadow: 0 0 14px rgba(120,190,255,.6);
pointer-events: none; z-index: 5;
}
#title span { display: inline-block; opacity: 0; }
#subtitle {
position: absolute; top: calc(10% + 46px); width: 100%; text-align: center;
color: rgba(170,205,240,.55); font-size: 13px; letter-spacing: 6px;
pointer-events: none; z-index: 5; opacity: 0;
}
/* ---------- 风力计 ---------- */
#windvane {
position: absolute; top: 20px; left: 24px; z-index: 6;
color: rgba(190,220,250,.8); font-size: 14px; letter-spacing: 2px;
}
#windArrows { color: #9fd4ff; }
/* ---------- 风痕 ---------- */
.streak {
position: absolute; color: rgba(200,230,255,.0); font-size: 15px;
white-space: nowrap; pointer-events: none; z-index: 3;
}
/* ---------- 文字海洋 ---------- */
#ocean { position: absolute; inset: 0; overflow: hidden; }
.cell { position: absolute; left: 0; top: 0; will-change: transform; font-size: 18px; }
/* ---------- 小船 ---------- */
#boat {
position: absolute; left: 0; top: 0; z-index: 4;
font-size: 22px; line-height: 1.06;
transform-origin: 50% 100%;
will-change: transform;
filter: drop-shadow(0 4px 8px rgba(0,0,0,.5));
}
#boat pre { font-family: inherit; }
.c-mast { color: #d8c49a; }
.c-sail { color: #eef6ff; text-shadow: 0 0 8px rgba(220,240,255,.45); }
.c-hull { color: #c98d4e; text-shadow: 0 0 6px rgba(160,100,40,.6); }
.c-keel { color: #8a5a2e; }
/* ---------- 浪花 / 鱼 ---------- */
.splash { position: absolute; color: #dff2ff; font-size: 14px; z-index: 4; pointer-events: none; }
.fish { position: absolute; color: #ffd88a; font-size: 15px; z-index: 4; pointer-events: none; white-space: nowrap; }
</style>
</head>
<body>
<div id="sky">
<div id="moon">月</div>
</div>
<div id="title"></div>
<div id="subtitle">风 吹 过 文 字 的 海</div>
<div id="windvane">风力 <span id="windArrows">→</span></div>
<div id="ocean"></div>
<div id="boat">
<pre id="boatArt"><span class="c-mast"> 丨</span>
<span class="c-sail"> 帆帆丨</span>
<span class="c-sail"> 帆帆帆帆丨</span>
<span class="c-sail">帆帆帆帆帆帆丨</span>
<span class="c-hull">舟舟舟舟舟舟舟</span>
<span class="c-keel">  ̄ ̄ ̄ ̄ ̄</span></pre>
</div>
<script>
/* =========================================================
文字之海 —— 风、浪与一只文字小船
海浪场:多组正弦波叠加 + 阵风行波(anime.js 驱动风力起伏)
========================================================= */
const W = () => window.innerWidth;
const H = () => window.innerHeight;
const ocean = document.getElementById('ocean');
const boat = document.getElementById('boat');
/* ---------- 全局风(anime.js tween 的目标对象) ---------- */
const wind = { amp: 6, phase: 0, dir: 1 }; // amp: 风力(浪高加成) phase: 阵风行波相位
function gustLoop() {
anime({
targets: wind,
amp: anime.random(2, 26),
duration: anime.random(2200, 5200),
easing: 'easeInOutSine',
complete: gustLoop
});
}
gustLoop();
/* ---------- 波浪函数:基础浪 + 阵风推起来的行波 ---------- */
function waveY(x, t) {
const base = 13 * Math.sin(x * 0.011 + t * 0.9)
+ 8 * Math.sin(x * 0.023 - t * 1.35)
+ 4 * Math.sin(x * 0.047 + t * 2.1);
const gust = wind.amp * Math.sin(x * 0.028 - wind.phase);
return base + gust;
}
const MAX_AMP = 13 + 8 + 4 + 26; // 用于浪尖归一化
/* ---------- 字符池 ---------- */
const CHARS = {
foam: ['≋','浪','°','˚','≈','花','沫'],
body: ['~','≈','〜','波','水','∽','浪'],
deep: ['~','·','。','水','流','丶','~']
};
const pick = arr => arr[(Math.random() * arr.length) | 0];
/* ---------- 构建海洋网格 ---------- */
const SP_X = 22, ROW_GAP = 27, ROWS = 10, LAG = 0.14;
let cells = [], oceanTop = 0;
function buildOcean() {
ocean.innerHTML = '';
cells = [];
oceanTop = H() * 0.48;
const cols = Math.ceil(W() / SP_X) + 2;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < cols; c++) {
const s = document.createElement('span');
s.className = 'cell';
const cell = {
el: s, x: c * SP_X, row: r,
state: '', // 'foam' | 'body' | 'deep'
ch: r < 3 ? pick(CHARS.body) : pick(CHARS.deep)
};
s.textContent = cell.ch;
ocean.appendChild(s);
cells.push(cell);
}
}
}
function setCellState(cell, state) {
if (cell.state === state) return;
cell.state = state;
cell.ch = pick(CHARS[state]);
cell.el.textContent = cell.ch;
}
/* ---------- 主循环 ---------- */
let t = 0, last = performance.now();
let boatX = -200;
const BOAT_W = 7 * 22, BOAT_H = 6 * 23.5;
function tick(now) {
const dt = Math.min(0.05, (now - last) / 1000);
last = now;
t += dt;
wind.phase += dt * (1.0 + wind.amp * 0.16) * wind.dir;
/* --- 海面每个字符的位置与形态 --- */
for (let i = 0; i < cells.length; i++) {
const cell = cells[i];
const r = cell.row;
// 深层海水:相位滞后 + 振幅衰减,制造"涌"的层次
const v = waveY(cell.x, t - r * LAG) * (1 - r * 0.055);
const y = oceanTop + r * ROW_GAP + v;
cell.el.style.transform = `translate3d(${cell.x}px, ${y}px, 0)`;
const crest = -v / MAX_AMP; // 越靠近波峰越大
if (r <= 1 && crest > 0.52) {
setCellState(cell, 'foam');
const L = 78 + crest * 20;
cell.el.style.color = `hsl(200, 85%, ${L}%)`;
} else if (r < 4) {
setCellState(cell, 'body');
const L = 46 + crest * 26 - r * 3;
cell.el.style.color = `hsl(${208 + crest * 12}, 78%, ${L}%)`;
} else {
setCellState(cell, 'deep');
const L = 34 - r * 1.6 + crest * 12;
cell.el.style.color = `hsl(212, 70%, ${L}%)`;
}
}
/* --- 小船:随浪起伏、按坡度倾斜、被风推着走 --- */
boatX += dt * (14 + wind.amp * 2.4) * wind.dir;
if (boatX > W() + 60) boatX = -BOAT_W - 60;
const cx = boatX + BOAT_W / 2;
const wv = waveY(cx, t);
const slope = (waveY(cx + 34, t) - waveY(cx - 34, t)) / 68;
let rot = Math.atan(slope) * 57.3 * 0.85;
rot = Math.max(-16, Math.min(16, rot));
boat.style.transform =
`translate3d(${boatX}px, ${oceanTop + wv - BOAT_H + 14}px, 0) rotate(${rot}deg)`;
/* --- 船头浪花:浪陡或风大时喷溅字符 --- */
const steep = Math.abs(slope);
if ((steep > 0.35 || wind.amp > 17) && Math.random() < 0.3) spawnSplash(boatX + BOAT_W - 8, oceanTop + wv + 2);
/* --- 风痕 --- */
updateStreaks(dt);
/* --- 风力计 --- */
windArrowsEl.textContent = '→'.repeat(1 + Math.round(wind.amp / 5));
requestAnimationFrame(tick);
}
/* ---------- 船头浪花 ---------- */
function spawnSplash(x, y) {
const s = document.createElement('span');
s.className = 'splash';
s.textContent = pick(['·','˚','°','。','′','水']);
s.style.left = x + 'px';
s.style.top = y + 'px';
document.body.appendChild(s);
anime({
targets: s,
translateX: anime.random(6, 30),
translateY: [-anime.random(14, 42), anime.random(8, 20)],
opacity: [0.95, 0],
duration: anime.random(500, 900),
easing: 'easeOutQuad',
complete: () => s.remove()
});
}
/* ---------- 风痕:阵风大时可见的气流线 ---------- */
const streaks = [];
function buildStreaks() {
for (let i = 0; i < 14; i++) {
const s = document.createElement('span');
s.className = 'streak';
s.textContent = ['〜〜〜','———','~~~~'][(Math.random()*3)|0];
s._x = Math.random() * W();
s._y = H() * 0.15 + Math.random() * H() * 0.5;
s._speed = 1 + Math.random();
document.body.appendChild(s);
streaks.push(s);
}
}
function updateStreaks(dt) {
const vis = Math.max(0, (wind.amp - 10) / 18); // 风力 >10 才开始显形
for (const s of streaks) {
s._x += dt * (60 + wind.amp * 26) * s._speed * wind.dir;
if (s._x > W() + 80) { s._x = -80; s._y = H() * 0.12 + Math.random() * H() * 0.55; }
s.style.transform = `translate3d(${s._x}px, ${s._y}px, 0)`;
s.style.color = `rgba(200,230,255,${(vis * 0.5 * s._speed / 2).toFixed(3)})`;
}
}
/* ---------- 跳鱼 ---------- */
function jumpFish() {
const f = document.createElement('span');
f.className = 'fish';
f.textContent = pick(['><(((°>', '><> ', '鱼', '><(((°>']);
const x = 100 + Math.random() * (W() - 200);
f.style.left = x + 'px';
f.style.top = (oceanTop + waveY(x, t) - 6) + 'px';
document.body.appendChild(f);
anime({
targets: f,
translateY: [{ value: -anime.random(45, 80), duration: 480, easing: 'easeOutQuad' },
{ value: 12, duration: 560, easing: 'easeInQuad' }],
rotate: [{ value: anime.random(-40, 40), duration: 1040 }],
opacity: [{ value: 1, duration: 300 }, { value: 0, duration: 740 }],
complete: () => f.remove()
});
setTimeout(jumpFish, anime.random(3000, 9000));
}
/* ---------- 天空:星星、云、海鸥 ---------- */
function buildSky() {
const sky = document.getElementById('sky');
for (let i = 0; i < 42; i++) {
const s = document.createElement('span');
s.className = 'star';
s.textContent = pick(['·', '˙', '*', '∘', '.']);
s.style.left = Math.random() * 100 + '%';
s.style.top = Math.random() * 34 + '%';
sky.appendChild(s);
anime({
targets: s,
opacity: [0.15, 0.85],
duration: anime.random(1200, 3200),
direction: 'alternate',
loop: true,
easing: 'easeInOutSine',
delay: anime.random(0, 2000)
});
}
// 云
for (let i = 0; i < 3; i++) {
const c = document.createElement('div');
c.className = 'cloud';
c.textContent = pick(['云 云 云', '云 云 云 云', '云 云']);
c.style.top = (8 + i * 9 + Math.random() * 4) + '%';
c.style.left = '-30%';
sky.appendChild(c);
anime({
targets: c,
left: '110%',
duration: anime.random(90000, 160000),
loop: true,
easing: 'linear',
delay: -anime.random(0, 90000)
});
}
// 海鸥
for (let i = 0; i < 3; i++) {
const g = document.createElement('span');
g.className = 'gull';
g.textContent = pick(['︵', '⌒', '︿']);
g.style.top = (18 + Math.random() * 16) + '%';
sky.appendChild(g);
anime({
targets: g,
left: ['-5%', '105%'],
translateY: [{ value: -14, duration: 900, direction: 'alternate', loop: 999, easing: 'easeInOutSine' }],
duration: anime.random(26000, 42000),
loop: true,
easing: 'linear',
delay: -anime.random(0, 20000)
});
}
// 月亮呼吸
anime({
targets: '#moon',
textShadow: [
'0 0 18px rgba(245,233,200,.85), 0 0 46px rgba(245,233,200,.45)',
'0 0 26px rgba(245,233,200,.95), 0 0 70px rgba(245,233,200,.6)'
],
direction: 'alternate', loop: true, duration: 3600, easing: 'easeInOutSine'
});
}
/* ---------- 标题入场 ---------- */
function introTitle() {
const title = document.getElementById('title');
'文字之海'.split('').forEach(ch => {
const s = document.createElement('span');
s.textContent = ch;
title.appendChild(s);
});
anime({
targets: '#title span',
opacity: [0, 1],
translateY: [-46, 0],
rotate: [() => anime.random(-30, 30), 0],
delay: anime.stagger(140),
duration: 1400,
easing: 'easeOutElastic(1, .6)'
});
anime({
targets: '#subtitle',
opacity: [0, 1],
translateY: [14, 0],
delay: 1200,
duration: 1600,
easing: 'easeOutQuad'
});
// 标题随后缓慢呼吸,不抢戏
anime({
targets: '#title',
opacity: [1, 0.55],
delay: 4200,
duration: 3000,
direction: 'alternate',
loop: true,
easing: 'easeInOutSine'
});
}
/* ---------- 船帆抖动(被风吹得扑簌簌) ---------- */
function sailFlutter() {
anime({
targets: '#boatArt .c-sail',
skewX: [0, () => anime.random(-6, 6)],
duration: 700,
direction: 'alternate',
loop: true,
easing: 'easeInOutSine',
delay: anime.stagger(90)
});
}
/* ---------- 自适应 ---------- */
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(buildOcean, 200);
});
/* ---------- 启动 ---------- */
const windArrowsEl = document.getElementById('windArrows');
buildOcean();
buildStreaks();
buildSky();
introTitle();
sailFlutter();
setTimeout(jumpFish, 2500);
requestAnimationFrame(tick);
</script>
</body>
</html>
k3

* 帖子来源Linux.do
附近帖子