首页头图:换参考页 WebGL 羊水+光束,孕囊改透明底 PNG (v26100427)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
|
||||
var VER = '26100426';
|
||||
var VER = '26100427';
|
||||
const $ = (s, c) => (c || document).querySelector(s);
|
||||
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
|
||||
|
||||
|
||||
299
js/hero-fx.js
299
js/hero-fx.js
@@ -6,22 +6,22 @@
|
||||
没建档 / 产后 / 事件迟迟不来 → 用第 6 阶段(原来那个 25 周的宝宝)。
|
||||
孕囊大小 = .hero-baby 的 --k(相对原 25 周囊的倍数),CSS 里围绕同一圆心缩放、带过渡。
|
||||
调试:URL 带 kidweek=12 强制按 12 周显示。
|
||||
② 阳光水波:#heroFx 一张画布,screen 混合叠在宝宝上、文字下。
|
||||
② 羊水 + 光束:#heroWater 一张 WebGL 画布(搬自参考页 womb-water.js),在孕囊下面。
|
||||
柔光束 + 柔和水波光影 + 悬浮微粒,宝宝区域避开(详见下面那段)。
|
||||
不在首页 / 页面隐藏 / 头图滚出视口时停画;系统「减少动态效果」下整块不画(CSS 也隐藏)。 */
|
||||
(function () {
|
||||
'use strict';
|
||||
// [起始孕周, 静图, GIF(null = 只用静图), 孕囊倍数 k, 宝宝轮廓遮罩(水波/光挖掉宝宝用)] 2026-10-04:宝宝在囊里缩到原图的 70%,孕囊不变
|
||||
// [起始孕周, 静图, GIF(null = 只用静图), 孕囊倍数 k] 静图是透明底 PNG(囊壁外渐隐),叠在羊水层上 2026-10-04:宝宝在囊里缩到原图的 70%,孕囊不变
|
||||
const STAGES = [
|
||||
[0, 'd953571ad48d.jpg', null, .42, '91f1472ee58a.png'], // 1 4–7 周 胚芽 + 卵黄囊
|
||||
[8, 'dd756c516072.jpg', null, .52, '5b39ca21a42e.png'], // 2 8–10 周 小豆芽胚胎
|
||||
[11, 'a6f52893ad1e.jpg', null, .62, '0dcddb83b038.png'], // 3 11–13 周 刚有人形
|
||||
[14, '251ec778dc15.jpg', null, .72, 'd63b80896e8e.png'], // 4 14–17 周 身子拉长、吮手
|
||||
[18, 'dcf2ae48e0ec.jpg', null, .86, '0022b97f8cc0.png'], // 5 18–23 周 比例接近新生儿、偏瘦
|
||||
[24, 'b0e24b10aaee.jpg', null, 1, 'd75b2a87e805.png'], // 6 24–27 周 原来那个宝宝
|
||||
[28, '85da5aaebb4f.jpg', null, 1.01, '507e24e95ef0.png'], // 7 28–31 周 大一圈、有胎发
|
||||
[32, '0780b4698253.jpg', null, 1.02, '681b0bdb885e.png'], // 8 32–35 周 肉嘟嘟、抱腿
|
||||
[36, 'ff112c222b1e.jpg', null, 1.03, '494179795c67.png'] // 9 36 周起 足月、撑满羊水囊(头朝上:10-04 用户说头朝下看着不舒服)
|
||||
[0, 'c685f94d437c.png', null, .42], // 1 4–7 周 胚芽 + 卵黄囊
|
||||
[8, '6c5d82c9b093.png', null, .52], // 2 8–10 周 小豆芽胚胎
|
||||
[11, '450581c2f05f.png', null, .62], // 3 11–13 周 刚有人形
|
||||
[14, 'dc4c7eea1c50.png', null, .72], // 4 14–17 周 身子拉长、吮手
|
||||
[18, '7bf792b04123.png', null, .86], // 5 18–23 周 比例接近新生儿、偏瘦
|
||||
[24, 'f140f4367b48.png', null, 1], // 6 24–27 周 原来那个宝宝
|
||||
[28, '51e99b3060e2.png', null, 1.01], // 7 28–31 周 大一圈、有胎发
|
||||
[32, 'a5dbb24c1ef2.png', null, 1.02], // 8 32–35 周 肉嘟嘟、抱腿
|
||||
[36, '610c4d92c16d.png', null, 1.03] // 9 36 周起 足月、撑满羊水囊(头朝上:10-04 用户说头朝下看着不舒服)
|
||||
];
|
||||
const DEFAULT = 5;
|
||||
const IMG = 'assets/img/';
|
||||
@@ -38,12 +38,11 @@
|
||||
while (i + 1 < STAGES.length && week >= STAGES[i + 1][0]) i++;
|
||||
return i;
|
||||
}
|
||||
let cur = -1, seq = 0, babyMask = null; // babyMask:当前阶段宝宝轮廓(黑 + alpha),见 frame() 末尾
|
||||
let cur = -1, seq = 0;
|
||||
function show(i) {
|
||||
if (i === cur) return;
|
||||
cur = i;
|
||||
const [, png, gif, k, mk] = STAGES[i], my = ++seq;
|
||||
const mi = new Image(); mi.onload = () => { if (my === seq) babyMask = mi; }; mi.src = IMG + mk;
|
||||
const [, png, gif, k] = STAGES[i], my = ++seq;
|
||||
box.style.setProperty('--k', k);
|
||||
// 先把旧图淡掉再换,避免新旧宝宝叠在一起闪一下
|
||||
kid.classList.remove('is-on'); still.classList.remove('is-on');
|
||||
@@ -98,182 +97,118 @@
|
||||
if (force != null || window.__mbPreg) onPreg(window.__mbPreg);
|
||||
else setTimeout(() => { if (cur < 0) show(DEFAULT); }, 2500);
|
||||
|
||||
/* ---------------- 阳光 + 水波 ----------------
|
||||
2026-10-04 三版(用户:「光束太硬,要柔柔地射进来;水波纹可以有,但宝宝的脸不能变;不要白色网状水波纹」):
|
||||
· 光束:画在 1/6 分辨率的小画布上再放大,边缘天然糊开 → 柔光;宽、淡、慢慢摆、忽明忽暗
|
||||
· 水波:极低分辨率算几层正弦叠出的大片明暗,放大后是柔和的、缓缓流动的光影(不是网)
|
||||
· 宝宝区域(孕囊中心 70% 半径内)水波压到很淡、光束减半;不再给孕囊挂任何形变滤镜,宝宝的脸一像素都不动
|
||||
· 悬浮微粒:羊水里的小颗粒,慢慢漂、闪一下 */
|
||||
const cv = document.getElementById('heroFx');
|
||||
if (!cv || reduce) return;
|
||||
/* ---------------- 羊水 + 光束(2026-10-04 七版) ----------------
|
||||
用户:「按我给的网址改头部,主要是它的光束和羊水,右上角的羊水比较好,用那一部分放到现在的头部」
|
||||
参考页:beian3.douba.top/YANG-bak-10041632/?v=2(js/womb-water.js,WebGL)。
|
||||
这里**原样搬了它的 fragment shader**:羊水底色 + 整体慢晃 + 折射波纹、右半边斜射的光束和焦散光纹、
|
||||
背后会「呼吸」的暖光、上浮颗粒、暗角。只改两处:
|
||||
· 暖光中心对齐到我们的孕囊(uC,每帧读 .hero-baby 的位置,跟着孕囊浮动走)
|
||||
· 孕周从 mb-preg 事件拿(参考页读 #heroWeek 文字)
|
||||
画布 #heroWater 在 .hero-baby 下面(z1),孕囊 + 宝宝(透明底 PNG)叠在上面 → 宝宝一像素都不受影响;
|
||||
LOGO / 文案(z3)在最上面。之前自己写的 2D 水层、光束、光斑整段删掉(不叠两套)。
|
||||
没有 WebGL → 什么都不画,CSS 背景照常;系统「减少动态效果」→ 只画一帧。 */
|
||||
const cv = document.getElementById('heroWater');
|
||||
if (!cv) return;
|
||||
const hero = cv.parentNode, home = document.getElementById('screen-home');
|
||||
const ctx = cv.getContext('2d');
|
||||
const RS = 6; // 光束小画布缩小倍数
|
||||
const rc = document.createElement('canvas'), rx = rc.getContext('2d');
|
||||
let tick = 0, W = 0, H = 0, dpr = 1, seen = true, raf = 0, last = 0, prev = 0;
|
||||
let SX = 0, SY = 0, SR = 1; // 孕囊圆心/半径(CSS px)
|
||||
/* ---- 羊水深度层(2026-10-04 五版,用户:「要羊水那种有深度的感觉,不是白色纹路来回跑」) ----
|
||||
#heroWater 在宝宝**下面**、正常混合(#heroFx 是 screen 混合,只能提亮 —— 上一版的暗部根本显不出来,只剩白纹)。
|
||||
① 折射:背景图按水平细条逐条错位(两组慢正弦),像隔着一层液体看过去,整片在缓缓荡
|
||||
② 暗流:极低分辨率的大团深红褐色,慢慢涌动(只压暗、不发白),给出「水有厚度」的感觉
|
||||
③ 景深:四周压深、上方略透亮(光从上面来),中间留给孕囊
|
||||
④ 远处微粒:小而清楚、慢,在宝宝后面;近处的大而虚的光斑在 #heroFx 上、宝宝前面 → 前后有层次 */
|
||||
const wcv = document.getElementById('heroWater'), wx = wcv && wcv.getContext('2d');
|
||||
const bgImg = new Image(); bgImg.src = IMG + '6a268e7269b7.jpg';
|
||||
const DW = 20, dcv = document.createElement('canvas'), dctx = dcv.getContext('2d');
|
||||
let dh = 0, dimg = null;
|
||||
function size() {
|
||||
const r = hero.getBoundingClientRect(), b = box.getBoundingClientRect();
|
||||
dpr = Math.min(devicePixelRatio || 1, 2);
|
||||
const gl = cv.getContext('webgl', { antialias: false, alpha: false }) || cv.getContext('experimental-webgl');
|
||||
if (!gl) return;
|
||||
const VS = 'attribute vec2 a;void main(){gl_Position=vec4(a,0.,1.);}';
|
||||
const FS = [
|
||||
'precision mediump float;',
|
||||
'uniform vec2 uRes,uC;uniform float uT,uDpr,uW;',
|
||||
'float h1(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}',
|
||||
'float vn(vec2 p){vec2 i=floor(p),f=fract(p);f=f*f*(3.-2.*f);',
|
||||
' return mix(mix(h1(i),h1(i+vec2(1,0)),f.x),mix(h1(i+vec2(0,1)),h1(i+vec2(1,1)),f.x),f.y);}',
|
||||
'float fbm(vec2 p){float v=0.,a=.5;for(int i=0;i<4;i++){v+=a*vn(p);p=p*2.03+vec2(1.7,9.2);a*=.5;}return v;}',
|
||||
'void main(){',
|
||||
' vec2 p=vec2(gl_FragCoord.x,uRes.y*uDpr-gl_FragCoord.y)/uDpr;',
|
||||
' vec2 q=p/uRes;float A=uRes.x/uRes.y;float t=uT;',
|
||||
// 水在晃:整体慢漂 + 折射波纹
|
||||
' vec2 sw=vec2(sin(t*.55)*.018+sin(t*.23)*.008,cos(t*.43)*.014);',
|
||||
' vec2 fl=vec2(fbm(q*vec2(A,1.)*1.6+vec2(t*.12,0.)),fbm(q*vec2(A,1.)*1.6+vec2(5.2,-t*.10)))-.5;',
|
||||
' vec2 qd=q+sw+.028*fl+.009*vec2(sin(q.y*14.+t*1.8),cos(q.x*A*10.+t*1.5));',
|
||||
// 羊水底色:头图同色系,上浅下深;左边(文案区)再压一点暖色
|
||||
' vec3 c=mix(vec3(.93,.64,.52),vec3(.79,.43,.36),smoothstep(-.15,1.05,qd.y));',
|
||||
' c=mix(c,vec3(.84,.49,.40),.35*(1.-smoothstep(.0,.6,q.x)));',
|
||||
' c+=.07*(fbm(qd*vec2(A,1.)*3.+vec2(t*.09,-t*.07))-.5);',
|
||||
// 光只在右半边:左上角是 LOGO + slogan,那里一点光都不给
|
||||
' float lk=smoothstep(.38,.75,q.x);',
|
||||
' vec2 cp=vec2(qd.x*A*3.,qd.y*7.);',
|
||||
' float a1=fbm(cp+vec2(t*.30,t*.18)),a2=fbm(cp*1.3-vec2(t*.26,-t*.22)+4.);',
|
||||
' float ca=1.-smoothstep(0.,.06,abs(a1-a2));',
|
||||
' c+=vec3(1.,.90,.74)*ca*pow(clamp(1.-q.y/.5,0.,1.),2.)*.22*lk;',
|
||||
// 阳光:隐隐约约的几道,慢慢起伏
|
||||
' vec2 r=qd-vec2(.74,-.45);float an=atan(r.x*A,r.y);',
|
||||
' float ry=vn(vec2(an*7.,t*.28))*vn(vec2(an*17.,-t*.33)+3.);',
|
||||
' ry=smoothstep(.15,.75,ry)*smoothstep(1.6,.2,length(r))*lk;',
|
||||
' float sun=.75+.25*sin(t*.6);',
|
||||
' c+=vec3(1.,.87,.66)*ry*.30*sun;',
|
||||
// ── 宝宝剪影已去掉(2026-10-04 用户要求),只留原来剪影背后那团会「呼吸」的暖光,位置和浮动照旧 ──
|
||||
' float m=smoothstep(14.,40.,uW);',
|
||||
' vec2 bc=uC+vec2(0.,uRes.y*.03*m);',
|
||||
' float bob=uRes.y*.022*sin(t*.55+.8);',
|
||||
' float gd=length((p-bc-vec2(0.,bob))/uRes.y);',
|
||||
' float br=.8+.2*sin(t*1.1);',
|
||||
' c+=vec3(1.,.83,.62)*(.22*exp(-gd*3.2)+.10*exp(-gd*1.2))*br;',
|
||||
// 上浮颗粒(只要很少几颗)
|
||||
' for(int i=0;i<2;i++){float Lr=float(i);',
|
||||
' vec2 g=q*vec2(A,1.)*(9.+Lr*7.)+vec2(sin(t*.4+Lr)*.3,t*(.30+Lr*.22));',
|
||||
' vec2 id=floor(g),f=fract(g)-.5;float rn=h1(id+Lr*7.);',
|
||||
' vec2 o=vec2(h1(id+3.1),h1(id+5.7))-.5;float dd=length(f-o*.6);',
|
||||
' float tw=.6+.4*sin(t*2.+rn*30.);',
|
||||
' c+=vec3(1.,.94,.84)*smoothstep(.05+.03*rn,0.,dd)*step(.80,rn)*(.35-Lr*.15)*tw;}',
|
||||
' c*=1.-.12*pow(length((q-vec2(.55,.4))*vec2(1.,1.3)),2.);',
|
||||
' gl_FragColor=vec4(c,1.);}'
|
||||
].join('\n');
|
||||
function sh(type, src) {
|
||||
const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
|
||||
return s;
|
||||
}
|
||||
let pr;
|
||||
try {
|
||||
pr = gl.createProgram();
|
||||
gl.attachShader(pr, sh(gl.VERTEX_SHADER, VS)); gl.attachShader(pr, sh(gl.FRAGMENT_SHADER, FS));
|
||||
gl.linkProgram(pr);
|
||||
if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(pr));
|
||||
} catch (err) { console.warn('[hero-water]', err); return; }
|
||||
gl.useProgram(pr);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
||||
const aLoc = gl.getAttribLocation(pr, 'a');
|
||||
gl.enableVertexAttribArray(aLoc); gl.vertexAttribPointer(aLoc, 2, gl.FLOAT, false, 0, 0);
|
||||
const U = {}; ['uRes', 'uC', 'uT', 'uDpr', 'uW'].forEach(n => { U[n] = gl.getUniformLocation(pr, n); });
|
||||
const dpr = Math.min(devicePixelRatio || 1, 2), t0 = performance.now();
|
||||
let W = 0, H = 0, raf = 0, seen = true, week = 20;
|
||||
addEventListener('mb-preg', e => { const w = e.detail && e.detail.week; week = w > 0 ? w : 20; });
|
||||
if (window.__mbPreg && window.__mbPreg.week > 0) week = window.__mbPreg.week;
|
||||
function resize() {
|
||||
const r = hero.getBoundingClientRect(); if (!r.width) return;
|
||||
W = r.width; H = r.height;
|
||||
cv.width = Math.round(W * dpr); cv.height = Math.round(H * dpr);
|
||||
rc.width = Math.max(8, Math.round(W / RS)); rc.height = Math.max(8, Math.round(H / RS));
|
||||
SX = b.left - r.left + b.width / 2; SY = b.top - r.top + b.height / 2; SR = Math.max(b.width, 60) / 2;
|
||||
if (wcv) {
|
||||
wcv.width = cv.width; wcv.height = cv.height;
|
||||
dh = Math.max(8, Math.round(DW * H / W)); dcv.width = DW; dcv.height = dh; dimg = dctx.createImageData(DW, dh);
|
||||
}
|
||||
gl.viewport(0, 0, cv.width, cv.height);
|
||||
}
|
||||
// 宝宝保护:孕囊中心附近 → 0,囊边 → 1
|
||||
function guard(x, y) {
|
||||
const d = Math.hypot(x - SX, y - SY) / SR;
|
||||
return d < .62 ? 0 : d > 1.05 ? 1 : (d - .62) / .43; // 宝宝缩到 70% 后大约占孕囊半径的 6 成
|
||||
function draw(now) {
|
||||
const hr = hero.getBoundingClientRect(), b = box.getBoundingClientRect();
|
||||
gl.uniform2f(U.uRes, W, H); gl.uniform1f(U.uDpr, dpr);
|
||||
gl.uniform2f(U.uC, b.left - hr.left + b.width / 2, b.top - hr.top + b.height / 2);
|
||||
gl.uniform1f(U.uW, Math.min(42, Math.max(5, week)));
|
||||
gl.uniform1f(U.uT, reduce ? 2 : (now - t0) / 1000);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
||||
}
|
||||
// 水波:四层不同方向、不同速度的长波叠加,取正半部分 → 一片片柔和的亮斑缓缓流过
|
||||
function deep(t, dt) {
|
||||
if (!wx) return;
|
||||
const c = wx;
|
||||
c.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
c.globalCompositeOperation = 'source-over'; c.globalAlpha = 1;
|
||||
// 底色(同 CSS)
|
||||
const base = c.createLinearGradient(0, 0, W, 0);
|
||||
base.addColorStop(0, '#CE8069'); base.addColorStop(.3, '#D8886F'); base.addColorStop(1, '#DB9073');
|
||||
c.fillStyle = base; c.fillRect(0, 0, W, H);
|
||||
// ① 折射:背景图逐条错位(图高 = 头图高 - 12,即 CSS 里钉死的 250 + 安全区)
|
||||
if (bgImg.complete && bgImg.naturalWidth) {
|
||||
const A = 3.2, Hs = hero.classList.contains('no-letter') ? H - 12 : H;
|
||||
const sc = Hs / bgImg.naturalHeight, dw = bgImg.naturalWidth * sc + A * 2, x0 = W - dw + A, step = 2;
|
||||
for (let y = 0; y < Hs; y += step) {
|
||||
const dx = A * Math.sin(y * .034 + t * .7) + A * .6 * Math.sin(y * .012 - t * .43);
|
||||
c.drawImage(bgImg, 0, y / sc, bgImg.naturalWidth, step / sc, x0 + dx, y, dw, step + .5);
|
||||
}
|
||||
}
|
||||
const ov = c.createLinearGradient(0, 0, W, 0);
|
||||
ov.addColorStop(0, 'rgba(150,55,35,.20)'); ov.addColorStop(.38, 'rgba(150,55,35,.07)'); ov.addColorStop(.58, 'rgba(150,55,35,0)');
|
||||
c.fillStyle = ov; c.fillRect(0, 0, W, H);
|
||||
// ② 暗流:大团、慢、只压暗;偶尔一点琥珀色(不是白)
|
||||
if ((tick ^= 1)) {
|
||||
const d = dimg.data, T = t * .28;
|
||||
for (let y = 0; y < dh; y++) for (let x = 0; x < DW; x++) {
|
||||
const u = x / DW * 4, v = y / DW * 4;
|
||||
const w = (Math.sin(u * 1.2 + T) + Math.sin(v * 1.5 - T * .7 + u * .6) + Math.sin((u + v) * .8 + T * .5)) / 3;
|
||||
const o = (y * DW + x) * 4;
|
||||
if (w < 0) { d[o] = 120; d[o + 1] = 36; d[o + 2] = 24; d[o + 3] = Math.min(1, -w * 1.5) * 70; }
|
||||
else { d[o] = 255; d[o + 1] = 196; d[o + 2] = 140; d[o + 3] = w * w * 34; }
|
||||
}
|
||||
dctx.putImageData(dimg, 0, 0);
|
||||
}
|
||||
c.imageSmoothingEnabled = true; c.imageSmoothingQuality = 'high';
|
||||
c.drawImage(dcv, 0, 0, W, H);
|
||||
// ③ 景深:四周压深、上方略透亮
|
||||
const vg = c.createRadialGradient(SX, SY, SR * .6, SX, SY, Math.max(W, H) * .95);
|
||||
vg.addColorStop(0, 'rgba(110,30,18,0)'); vg.addColorStop(1, 'rgba(110,30,18,.32)');
|
||||
c.fillStyle = vg; c.fillRect(0, 0, W, H);
|
||||
const tg = c.createLinearGradient(0, 0, 0, H);
|
||||
tg.addColorStop(0, 'rgba(255,214,170,.10)'); tg.addColorStop(.45, 'rgba(255,214,170,0)'); tg.addColorStop(1, 'rgba(105,28,16,.16)');
|
||||
c.fillStyle = tg; c.fillRect(0, 0, W, H);
|
||||
// ④ 远处微粒:小、清楚、慢
|
||||
for (const p of FAR) {
|
||||
p.x += (p.vx + Math.sin(t * .3 + p.ph) * .0012) * dt; p.y += p.vy * dt;
|
||||
if (p.y < -.03) { p.y = 1.03; p.x = Math.random(); }
|
||||
if (p.x < -.03) p.x = 1.03; else if (p.x > 1.03) p.x = -.03;
|
||||
const al = .22 + .22 * Math.sin(t * p.sp + p.ph);
|
||||
c.fillStyle = 'rgba(255,232,205,' + al.toFixed(3) + ')';
|
||||
c.beginPath(); c.arc(p.x * W, p.y * H, p.r, 0, 6.2832); c.fill();
|
||||
}
|
||||
}
|
||||
const FAR = Array.from({ length: 34 }, () => ({ x: Math.random(), y: Math.random(), r: .45 + Math.random() * .7,
|
||||
vx: (Math.random() - .5) * .003, vy: -(.002 + Math.random() * .004), ph: Math.random() * 6.3, sp: .5 + Math.random() }));
|
||||
// 柔光束:光源在头图上方偏右画外;每道是一条很宽的渐变扇形,画在小画布上再放大
|
||||
const RAYS = [[-.12, .07, .8], [0, .05, .7], [.11, .08, 1], [.23, .06, .7]];
|
||||
function rays(t) {
|
||||
const w = rc.width, h = rc.height, ox = w * .8, oy = -h * .9, L = h * 2.8;
|
||||
rx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
rx.clearRect(0, 0, w, h);
|
||||
rx.globalCompositeOperation = 'lighter';
|
||||
RAYS.forEach(([a0, ww, al], i) => {
|
||||
// 2026-10-04 用户:「射下来的光束可以动一动」→ 摆幅 .03→.07、摆速加快,明暗起伏更明显
|
||||
const a = Math.PI * .6 + a0 + Math.sin(t * .16 + i * 1.9) * .07;
|
||||
const k = al * (.45 + .55 * Math.sin(t * .35 + i * 2.1));
|
||||
const wv = ww * (1 + .2 * Math.sin(t * .17 + i));
|
||||
const gr = rx.createLinearGradient(ox, oy, ox + Math.cos(a) * L * .8, oy + Math.sin(a) * L * .8);
|
||||
gr.addColorStop(0, 'rgba(255,248,228,0)');
|
||||
gr.addColorStop(.35, 'rgba(255,226,190,' + (.30 * k).toFixed(3) + ')');
|
||||
gr.addColorStop(.65, 'rgba(255,218,180,' + (.13 * k).toFixed(3) + ')');
|
||||
gr.addColorStop(1, 'rgba(255,235,205,0)');
|
||||
rx.fillStyle = gr;
|
||||
rx.beginPath(); rx.moveTo(ox, oy);
|
||||
rx.lineTo(ox + Math.cos(a - wv) * L, oy + Math.sin(a - wv) * L);
|
||||
rx.lineTo(ox + Math.cos(a + wv) * L, oy + Math.sin(a + wv) * L);
|
||||
rx.closePath(); rx.fill();
|
||||
});
|
||||
// 宝宝区域光束减半:在小画布上挖一个软边的「淡区」
|
||||
rx.globalCompositeOperation = 'destination-out';
|
||||
const sx = SX / RS, sy = SY / RS, sr = SR / RS, g = rx.createRadialGradient(sx, sy, 0, sx, sy, sr * .9);
|
||||
g.addColorStop(0, 'rgba(0,0,0,.5)'); g.addColorStop(.6, 'rgba(0,0,0,.35)'); g.addColorStop(1, 'rgba(0,0,0,0)');
|
||||
rx.fillStyle = g; rx.beginPath(); rx.arc(sx, sy, sr, 0, 6.2832); rx.fill();
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high';
|
||||
ctx.drawImage(rc, 0, 0, W, H);
|
||||
}
|
||||
// 悬浮微粒:慢慢往上漂、左右游,忽明忽暗;孕囊中心附近不画,免得落在宝宝脸上
|
||||
// 近处光斑(宝宝前面):少、大、很虚、暖色,漂得比远处快 → 和 deep() 里的远处微粒拉开前后
|
||||
const DOTS = Array.from({ length: 9 }, () => ({ x: Math.random(), y: Math.random(), r: 2.2 + Math.random() * 3.5,
|
||||
vx: (Math.random() - .5) * .01, vy: -(.006 + Math.random() * .01), ph: Math.random() * 6.3, sp: .4 + Math.random() * .8 }));
|
||||
function dots(t, dt) {
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
for (const p of DOTS) {
|
||||
p.x += (p.vx + Math.sin(t * .5 + p.ph) * .002) * dt; p.y += p.vy * dt;
|
||||
if (p.y < -.03) { p.y = 1.03; p.x = Math.random(); }
|
||||
if (p.x < -.03) p.x = 1.03; else if (p.x > 1.03) p.x = -.03;
|
||||
const X = p.x * W, Y = p.y * H, gd = guard(X, Y);
|
||||
if (gd <= 0 && !babyMask) continue;
|
||||
const al = (.06 + .08 * (.5 + .5 * Math.sin(t * p.sp + p.ph))) * (babyMask ? 1 : gd);
|
||||
const R = p.r * 2.4, g = ctx.createRadialGradient(X, Y, 0, X, Y, R);
|
||||
g.addColorStop(0, 'rgba(255,226,196,' + al.toFixed(3) + ')');
|
||||
g.addColorStop(1, 'rgba(255,226,196,0)');
|
||||
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(X, Y, R, 0, 6.2832); ctx.fill();
|
||||
}
|
||||
}
|
||||
function frame(now) {
|
||||
function loop(now) {
|
||||
raf = 0;
|
||||
if (!(seen && !document.hidden && (!home || home.classList.contains('is-active')))) { prev = 0; return; }
|
||||
raf = requestAnimationFrame(frame);
|
||||
if (now - last < 33) return; // ≈30fps 足够,省电
|
||||
const dt = prev ? Math.min((now - prev) / 1000, .1) : 0; prev = now; last = now;
|
||||
const t = now / 1000;
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
deep(t, dt); // 羊水深度层(宝宝下面)
|
||||
rays(t);
|
||||
dots(t, dt);
|
||||
/* 宝宝保护:按当前阶段的宝宝轮廓把这一帧的水波/光束/微粒整块挖掉 ——
|
||||
水在宝宝四周流,宝宝本身一像素都不叠(用户:「水波纹不能影响宝宝的表情,脸不能变」)。
|
||||
孕囊在轻轻上下浮(CSS hbFloat),所以每帧现读位置。 */
|
||||
if (babyMask) {
|
||||
const hr = hero.getBoundingClientRect(), b = box.getBoundingClientRect();
|
||||
ctx.globalCompositeOperation = 'destination-out';
|
||||
ctx.drawImage(babyMask, b.left - hr.left, b.top - hr.top, b.width, b.height);
|
||||
}
|
||||
if (!(seen && !document.hidden && (!home || home.classList.contains('is-active')))) return;
|
||||
draw(now);
|
||||
if (!reduce) raf = requestAnimationFrame(loop);
|
||||
}
|
||||
function kick() { if (!raf) raf = requestAnimationFrame(frame); }
|
||||
size();
|
||||
addEventListener('resize', () => { size(); kick(); });
|
||||
box.addEventListener('transitionend', size); // 换阶段后孕囊尺寸变了,保护区跟着挪
|
||||
function kick() { if (!raf) raf = requestAnimationFrame(loop); }
|
||||
resize(); kick();
|
||||
addEventListener('resize', () => { resize(); kick(); });
|
||||
document.addEventListener('visibilitychange', kick);
|
||||
if (home) new MutationObserver(kick).observe(home, { attributes: true, attributeFilter: ['class'] });
|
||||
if ('IntersectionObserver' in window) new IntersectionObserver(es => { seen = es[0].isIntersecting; kick(); }).observe(hero);
|
||||
if ('IntersectionObserver' in window) new IntersectionObserver(es => { seen = es[0].isIntersecting; if (seen) { resize(); kick(); } }).observe(hero);
|
||||
if (reduce) addEventListener('mb-preg', kick);
|
||||
cv.addEventListener('webglcontextlost', () => { cancelAnimationFrame(raf); cv.classList.remove('is-on'); });
|
||||
cv.classList.add('is-on');
|
||||
if (wcv) { if (bgImg.complete && bgImg.naturalWidth) wcv.classList.add('is-on'); else bgImg.onload = () => wcv.classList.add('is-on'); } // 背景图到了再盖上,免得先出一块纯色
|
||||
kick();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user