style(首页头图): 白色水纹换成有深度的羊水层——背景折射晃动、暗流、景深暗角、远近两层微粒 (v26100422)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 08:12:45 +00:00
parent dca1b69eff
commit 72a7e4b935
4 changed files with 84 additions and 38 deletions

View File

@@ -7,7 +7,7 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>兜爸心选 · 孕育陪伴</title>
<!-- V:26100421 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
<!-- V:26100422 缓存版本标记:改 css/js 时同步 bump 该值(与 js/app.js 的 VER 同源),静态资源按 mtime 走 ETag 自动刷新 -->
<link rel="stylesheet" href="mama-box.css">
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<!-- 共享助手:授权/跳商城/位置上报(分享卡片同步,见 docs/h5-auth-standard.md 第四节) -->
@@ -65,6 +65,8 @@
每阶段一张会动的 GIF(本地程序化形变:呼吸、轻浮、小手、蹬脚;脸不动,所以永远闭眼闭嘴)+ 一张同帧静图打底,
静图先出、GIF 加载完再盖上。孕囊大小 = .hero-baby 的 --k,由 JS 按阶段写。
.hero-fx 是阳光光束 + 水波焦散的画布,叠在宝宝之上、文字之下。 -->
<!-- 2026-10-04 羊水深度层(js/hero-fx.js deep()):背景透过水的折射晃动 + 暗流 + 景深暗角 + 远处微粒,正常混合、在宝宝下面 -->
<canvas class="hero-water" id="heroWater" aria-hidden="true"></canvas>
<div class="hero-baby" id="heroBaby" aria-hidden="true">
<img class="hero-kid hero-kid-still" id="heroStill" alt="" decoding="async">
<img class="hero-kid" id="heroKid" alt="" decoding="async">

View File

@@ -3,7 +3,7 @@
(function () {
'use strict';
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
var VER = '26100421';
var VER = '26100422';
const $ = (s, c) => (c || document).querySelector(s);
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));

View File

@@ -74,19 +74,29 @@
const ctx = cv.getContext('2d');
const RS = 6; // 光束小画布缩小倍数
const rc = document.createElement('canvas'), rx = rc.getContext('2d');
const CW = 44; // 水波低分辨率宽(越低越柔)
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 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);
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;
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);
}
}
// 宝宝保护:孕囊中心附近 → 0,囊边 → 1
function guard(x, y) {
@@ -94,29 +104,60 @@
return d < .62 ? 0 : d > 1.05 ? 1 : (d - .62) / .43; // 宝宝缩到 70% 后大约占孕囊半径的 6 成
}
// 水波:四层不同方向、不同速度的长波叠加,取正半部分 → 一片片柔和的亮斑缓缓流过
function water(t) {
/* 2026-10-04 四版(用户:「整个上半部分要有羊水那种动态的感觉,现在还是偏静态」):
明暗两相 —— 波峰处暖白亮斑、波谷处淡淡的暗影,source-over 叠,看起来是光透过流动的水在晃;
尺度比上一版小一倍多(一屏能看到好几片),流速加快。仍是极低分辨率放大 → 边缘全是软的,不是网。 */
const d = img.data, T = t * .9;
for (let y = 0; y < ch; y++) {
for (let x = 0; x < CW; x++) {
const u = x / CW * 11, v = y / CW * 11;
let w = Math.sin(u * 1.1 + T) + Math.sin(v * 1.3 - T * .8 + u * .5) + Math.sin((u - v) * .9 + T * .7)
+ Math.sin(Math.hypot(u - 8, v - 2) * 1.4 - T * 1.2) * 1.2;
w /= 4.2;
const o = (y * CW + x) * 4, fade = 1 - y / ch * .3;
if (w > 0) { const k = Math.min(1, w * 1.6); d[o] = 255; d[o + 1] = 240; d[o + 2] = 218; d[o + 3] = k * k * 150 * fade; }
else { const k = Math.min(1, -w * 1.6); d[o] = 150; d[o + 1] = 60; d[o + 2] = 40; d[o + 3] = k * k * 55 * fade; }
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);
}
}
oc.putImageData(img, 0, 0);
}
function drawWater() {
ctx.globalCompositeOperation = 'source-over';
ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high';
ctx.drawImage(off, 0, 0, W, H);
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) {
@@ -131,8 +172,8 @@
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(.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);
@@ -150,8 +191,9 @@
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 }));
// 近处光斑(宝宝前面):少、大、很虚、暖色,漂得比远处快 → 和 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) {
@@ -160,10 +202,10 @@
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 = (.15 + .25 * (.5 + .5 * Math.sin(t * p.sp + p.ph))) * (babyMask ? 1 : gd);
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,250,238,' + al.toFixed(3) + ')');
g.addColorStop(1, 'rgba(255,250,238,0)');
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();
}
}
@@ -176,10 +218,7 @@
const t = now / 1000;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, W, H);
/* 水波:2026-10-04 先关后开 —— 用户之前看到的「宝宝脸/腿变形」其实是 GIF 自带的扭动(已停用 GIF),
这层只是叠加的柔光,不做任何形变;而且宝宝那一圈(guard)完全不叠,宝宝表情一点不受影响。 */
if ((tick ^= 1)) water(t);
drawWater();
deep(t, dt); // 羊水深度层(宝宝下面)
rays(t);
dots(t, dt);
/* 宝宝保护:按当前阶段的宝宝轮廓把这一帧的水波/光束/微粒整块挖掉 ——
@@ -199,5 +238,6 @@
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');
if (wcv) { if (bgImg.complete && bgImg.naturalWidth) wcv.classList.add('is-on'); else bgImg.onload = () => wcv.classList.add('is-on'); } // 背景图到了再盖上,免得先出一块纯色
kick();
})();

View File

@@ -4494,3 +4494,7 @@ a.skip:focus{left:16px;top:16px}
.hero-baby{animation:hbFloat 6s ease-in-out infinite}
@keyframes hbFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-4px)}}
@media (prefers-reduced-motion:reduce){.hero-baby{animation:none}}
/* 2026-10-04 羊水深度层:在宝宝下面(z1),正常混合,能压暗也能提亮;画满后盖住 CSS 背景(CSS 背景留作降级/首帧) */
.hero-water{position:absolute;z-index:1;left:0;top:0;width:100%;height:100%;pointer-events:none;opacity:0;transition:opacity 1s ease}
.hero-water.is-on{opacity:1}