Files
mama-box/js/hero-fx.js
Karlcarlo 0ddd6348fb style(首页): 孕囊里的宝宝缩小到 85%,孕囊不变;去掉水波光影 (v26100416)
- 9 个阶段静图 + GIF 重做:抠出宝宝缩小后贴回空孕囊,孕囊外形/大小不动
- GIF 每帧底图用静图,羊水不晃,只有宝宝自己在动;帧差透明压缩,1~3MB → 250~490KB
- hero-fx.js 不再画水波层(光束、微粒保留)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 05:49:48 +00:00

188 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ================= 首页头图:按孕周换宝宝 + 阳光水波(2026-10-04) =================
用户:「宝宝按孕周换、孕囊跟着宝宝大小变;顶部往下落的花瓣去掉,换成阳光和水波纹」。
① 换宝宝:孕周来自 app.js 的 mb-preg 事件(syncPregnancy 跑完广播,首屏一次、远程档案回来再一次)。
9 个阶段各一张「羊水囊 + 宝宝」整幅图:静图(首帧)先出,GIF 加载完再盖上淡入。
没建档 / 产后 / 事件迟迟不来 → 用第 6 阶段(原来那个 25 周的宝宝)。
孕囊大小 = .hero-baby 的 --k(相对原 25 周囊的倍数),CSS 里围绕同一圆心缩放、带过渡。
调试:URL 带 kidweek=12 强制按 12 周显示。
② 阳光水波:#heroFx 一张画布,screen 混合叠在宝宝上、文字下。
柔光束 + 柔和水波光影 + 悬浮微粒,宝宝区域避开(详见下面那段)。
不在首页 / 页面隐藏 / 头图滚出视口时停画;系统「减少动态效果」下整块不画(CSS 也隐藏)。 */
(function () {
'use strict';
// [起始孕周, 静图, GIF, 孕囊倍数 k]
const STAGES = [
[0, 'd86caa477171.jpg', '482d0a3043b9.gif', .42], // 1 4–7 周 胚芽 + 卵黄囊
[8, '677339874dd7.jpg', '30163971ab67.gif', .52], // 2 8–10 周 小豆芽胚胎
[11, 'eed131630ed3.jpg', 'a59fd5aa0f28.gif', .62], // 3 11–13 周 刚有人形
[14, 'fdcdef9ad731.jpg', 'b6fdd2c43538.gif', .72], // 4 14–17 周 身子拉长、吮手
[18, '18022a7d97d3.jpg', 'ece5fb854939.gif', .86], // 5 18–23 周 比例接近新生儿、偏瘦
[24, '87dac10fb33c.jpg', '55c1e3d20b6d.gif', 1], // 6 24–27 周 原来那个宝宝
[28, 'd8eb9eb12eee.jpg', 'a0e633273ab4.gif', 1.01], // 7 28–31 周 大一圈、有胎发
[32, '99cf27ee651e.jpg', '9d1908a2aff3.gif', 1.02], // 8 32–35 周 肉嘟嘟、抱腿
[36, '6bd1f87ff5ab.jpg', 'c878649c8fee.gif', 1.03] // 9 36 周起 足月、撑满羊水囊(头朝上:10-04 用户说头朝下看着不舒服)
];
const DEFAULT = 5;
const IMG = 'assets/img/';
const box = document.getElementById('heroBaby');
const still = document.getElementById('heroStill');
const kid = document.getElementById('heroKid');
if (!box || !still || !kid) return;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const force = (location.search.match(/[?&]kidweek=(\d+)/) || [])[1];
function stageOf(week) {
if (week == null || isNaN(week)) return DEFAULT;
let i = 0;
while (i + 1 < STAGES.length && week >= STAGES[i + 1][0]) i++;
return i;
}
let cur = -1, seq = 0;
function show(i) {
if (i === cur) return;
cur = i;
const [, png, gif, k] = STAGES[i], my = ++seq;
box.style.setProperty('--k', k);
// 先把旧图淡掉再换,避免新旧宝宝叠在一起闪一下
kid.classList.remove('is-on'); still.classList.remove('is-on');
const s = new Image();
s.onload = () => { if (my !== seq) return; still.src = s.src; still.classList.add('is-on'); };
s.src = IMG + png;
if (reduce) return;
const g = new Image();
g.onload = () => { if (my !== seq) return; kid.src = g.src; kid.classList.add('is-on'); };
g.src = IMG + gif;
}
function onPreg(d) { show(stageOf(force != null ? +force : (d && d.week))); }
addEventListener('mb-preg', e => onPreg(e.detail));
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;
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');
const CW = 32; // 水波低分辨率宽(越低越柔)
const off = document.createElement('canvas'), oc = off.getContext('2d');
let tick = 0, W = 0, H = 0, dpr = 1, ch = 0, img = null, seen = true, raf = 0, last = 0, prev = 0;
let SX = 0, SY = 0, SR = 1; // 孕囊圆心/半径(CSS px)
function size() {
const r = hero.getBoundingClientRect(), b = box.getBoundingClientRect();
dpr = Math.min(devicePixelRatio || 1, 2);
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));
ch = Math.max(8, Math.round(CW * H / W));
off.width = CW; off.height = ch; img = oc.createImageData(CW, ch);
SX = b.left - r.left + b.width / 2; SY = b.top - r.top + b.height / 2; SR = Math.max(b.width, 60) / 2;
}
// 宝宝保护:孕囊中心附近 → 0,囊边 → 1
function guard(x, y) {
const d = Math.hypot(x - SX, y - SY) / SR;
return d < .45 ? 0 : d > .95 ? 1 : (d - .45) / .5;
}
// 水波:四层不同方向、不同速度的长波叠加,取正半部分 → 一片片柔和的亮斑缓缓流过
function water(t) {
const d = img.data, T = t * .45;
for (let y = 0; y < ch; y++) {
for (let x = 0; x < CW; x++) {
const u = x / CW * 6, v = y / CW * 6;
let w = Math.sin(u * 1.1 + T) + Math.sin(v * 1.3 - T * .8 + u * .4) + Math.sin((u - v) * .9 + T * .6) + Math.sin(Math.hypot(u - 4.5, v - 1) * 1.6 - T * 1.1);
w = w / 4; w = w > 0 ? w * w : 0; // 只留亮的那半,平方让过渡更软
const X = x / CW * W, Y = y / ch * H;
const g = .15 + .85 * guard(X, Y);
const o = (y * CW + x) * 4;
d[o] = 255; d[o + 1] = 238; d[o + 2] = 214; d[o + 3] = w * 200 * g * (1 - y / ch * .35);
}
}
oc.putImageData(img, 0, 0);
}
function drawWater() {
ctx.globalCompositeOperation = 'lighter';
ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high';
ctx.globalAlpha = .55;
ctx.drawImage(off, 0, 0, W, H);
ctx.globalAlpha = 1;
}
// 柔光束:光源在头图上方偏右画外;每道是一条很宽的渐变扇形,画在小画布上再放大
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) => {
const a = Math.PI * .6 + a0 + Math.sin(t * .09 + i * 1.9) * .03;
const k = al * (.6 + .4 * Math.sin(t * .23 + 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,246,224,' + (.34 * k).toFixed(3) + ')');
gr.addColorStop(.65, 'rgba(255,240,215,' + (.15 * 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);
}
// 悬浮微粒:慢慢往上漂、左右游,忽明忽暗;孕囊中心附近不画,免得落在宝宝脸上
const DOTS = Array.from({ length: 26 }, () => ({ x: Math.random(), y: Math.random(), r: .5 + Math.random() * 1.2,
vx: (Math.random() - .5) * .006, vy: -(.004 + Math.random() * .008), ph: Math.random() * 6.3, sp: .6 + Math.random() * 1.4 }));
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) continue;
const al = (.15 + .25 * (.5 + .5 * Math.sin(t * p.sp + p.ph))) * gd;
const R = p.r * 2.4, g = ctx.createRadialGradient(X, Y, 0, X, Y, R);
g.addColorStop(0, 'rgba(255,250,238,' + al.toFixed(3) + ')');
g.addColorStop(1, 'rgba(255,250,238,0)');
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(X, Y, R, 0, 6.2832); ctx.fill();
}
}
function frame(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);
// 2026-10-04 用户:「水波纹那一层先去掉,动的是宝宝自己(腿脚、嘴角),不是水」→ 不再画水波
rays(t);
dots(t, dt);
}
function kick() { if (!raf) raf = requestAnimationFrame(frame); }
size();
addEventListener('resize', () => { size(); kick(); });
box.addEventListener('transitionend', size); // 换阶段后孕囊尺寸变了,保护区跟着挪
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);
cv.classList.add('is-on');
kick();
})();