Files
mama-box/js/gch/v4.js

236 lines
17 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.
/* 11-02 首页「孕妈广场」效果稿 V4 · 故事卡堆(翻一翻)
一叠微微错开的真实照片卡,左右拖/轻扫/点箭头翻页,最后一张是「轮到你了」投稿卡;下方一行 #话题 入口。
只在 ?gcv=4 时由 index.html 加载;笔记是从 guangchang/data.js 摘的精选(占位示例)。 */
(function () {
var sec = document.getElementById('gcHome'), T = window.GC_TOPICS;
if (!sec || !Array.isArray(T) || !T.length) return;
function init() {
var esc = function (s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); };
var kfmt = function (n) { return n >= 10000 ? (n / 10000).toFixed(1).replace(/\.0$/, '') + '万' : n >= 1000 ? (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k' : String(n); };
var ava = function (n) { return 'assets/img/mom/' + n + '.jpg'; };
var reduce = window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches;
var CAT_CN = { family: '家庭婚姻', work: '职场', baby: '经验分享' }; /* 11-03 卡上话题标签同广场:经验 → 经验分享 */
var TOP = {}; T.forEach(function (t) { TOP[t.id] = t; });
/* 11-02 用户:广场帖子跟「其他妈妈也在搜」一样按孕周 6 段走 —— 多数是同阶段孕妈的帖子,穿插过来人(已生完)的。
帖子清单在 guangchang/feed.js(由 data.js 生成的轻量索引 + 排序函数 GC_ORDER),孕周取 app.js 广播的 window.__mbPreg。 */
var MOMS = ['m03', 'p02', 'm07', 'p05', 'm11', 'p09', 'm14', 'm16', 'p12', 'm19', 'm21', 'p07'];
function userBand() {
var pg = window.__mbPreg || {};
return pg.postpartum ? 'pp' : (pg.week && window.GC_BAND ? window.GC_BAND('孕' + pg.week + '周') : -1);
}
function notes(cat) {
if (!window.GC_ORDER) return [];
var F = window.GC_FEED || [], ub = userBand();
var l = window.GC_ORDER(F.filter(function (n) { return n.photo && (!cat || (TOP[n.t] || {}).cat === cat); }), ub);
if (!cat) { /* 「全部」:同一分类最多 3 张,三类穿插着来 */
var cnt = {}, out = [];
l.forEach(function (n) { var c = (TOP[n.t] || {}).cat; if (out.length < 7 && (cnt[c] || 0) < 3) { cnt[c] = (cnt[c] || 0) + 1; out.push(n); } });
l = out;
}
return l.slice(0, 7).map(function (n, i) { return { t: n.t, id: n.id, title: n.title, author: n.author, stage: n.stage, likes: n.likes, photo: n.photo, av: MOMS[(i * 5 + (cat ? cat.length : 0)) % MOMS.length] }; });
}
function topics(cat) {
var l = T.filter(function (t) { return !cat || t.cat === cat; });
var hot = l.filter(function (t) { return t.hot; });
return (cat ? hot : hot.filter(function (_, i) { return i % 3 === 0; })).concat(l.filter(function (t) { return !t.hot; })).slice(0, 3);
}
/* 11-02 用户:「进广场首页太深,只能点详情再返回」→ 点帖子只带 ?n=(返回落在广场首页,不落在单个话题);话题标签才带 ?t= */
function go(id, nid) {
location.href = 'guangchang/' + (nid ? '?n=' + encodeURIComponent(nid) : id ? '?t=' + encodeURIComponent(id) : '') + location.hash;
}
sec.className = sec.className.replace(/\bgv\d\b/g, '') + ' gv4';
sec.innerHTML =
'<div class="g4-head"><h2 class="g4-title"><span>孕妈广场</span></h2>' +
/* 11-03 用户:首页孕妈广场右上角的分类(全部 / 家庭婚姻 / 职场 / 经验)取消,换一句小 slogan */
'<span class="g4-slg"><i></i>孕期路上,有人陪你聊</span></div>' +
/* 11-02 用户:「逛广场」单独挂在右边突兀 → 整行做成入口条(浅粉底,整条可点),右侧实心「进广场」 */
'<div class="g4-sub" role="button" tabindex="0"><span class="g4-avs" aria-hidden="true">' + ['p05', 'm07', 'p11', 'm16', 'p02', 'm03', 'p09', 'm12', 'p13', 'm19'].map(function (n) { return '<img src="' + ava(n) + '" alt="" loading="lazy">'; }).join('') + '</span><span class="g4-st">看看<b>其他妈妈</b>的故事</span><span class="g4-in">进广场<i>›</i></span></div>' +
'<div class="g4-stage"><div class="g4-deck" aria-live="polite"></div>' +
/* 11-03 用户:左右翻的按钮去掉,「1/8」挪到原来按钮的位置(卡堆右下) —— 翻页靠左右滑 / 点后面露出的卡角 */
'<div class="g4-rail"><span class="g4-pg"><b>1</b><i>/8</i></span></div></div>' +
'<div class="g4-tags"></div>' +
'<div class="g4-foot"><button type="button" class="g4-all">进孕妈广场 ›</button><span class="g4-mark">效果稿 V4</span></div>';
sec.classList.remove('hide');
var deck = sec.querySelector('.g4-deck'), pgB = sec.querySelector('.g4-pg b'), pgI = sec.querySelector('.g4-pg i'), tags = sec.querySelector('.g4-tags');
var cards = [], idx = 0, cur = '';
function storyCard(n) {
var tp = TOP[n.t] || {}, cat = tp.cat || 'family';
return '<div class="g4-card st c-' + cat + '" data-t="' + esc(n.t) + '" data-n="' + esc(n.id) + '" role="button" tabindex="0">' +
'<img class="g4-ph" src="' + esc(n.photo) + '" alt="" loading="lazy" decoding="async" draggable="false">' +
'<span class="g4-k">' + CAT_CN[cat] + '</span>' +
'<span class="g4-bot"><b class="g4-tt">' + esc(n.title) + '</b>' +
'<span class="g4-by"><img src="' + ava(n.av) + '" alt="" loading="lazy" draggable="false"><em>' + esc(n.author) + '</em><i>' + esc(n.stage) + '</i>' +
'<span class="g4-like"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20.5s-7.5-4.6-7.5-10.2A4.3 4.3 0 0112 7.6a4.3 4.3 0 017.5 2.7c0 5.6-7.5 10.2-7.5 10.2z"/></svg>' + kfmt(n.likes) + '</span></span></span></div>';
}
function submitCard() {
return '<div class="g4-card sub" data-sub="1" role="button" tabindex="0">' +
'<span class="g4-s-k">兜爸心选 · 官方征集</span>' + /* 11-02 品牌露出 */
'<b class="g4-s-t">轮到你了</b>' +
'<span class="g4-s-d">你的孕期故事,正是别的妈妈想听的</span>' +
'<span class="g4-s-r"><span><b>¥30</b><i>现金</i></span><em>+</em><span><b>500</b><i>兜豆</i></span></span>' +
'<span class="g4-s-n">被选中即可获得</span>' +
'<span class="g4-s-go">去投稿 ›</span>' +
'<span class="g4-s-avs" aria-hidden="true">' + ['m02', 'p04', 'm09', 'p13', 'm18'].map(function (n) { return '<img src="' + ava(n) + '" alt="" loading="lazy" draggable="false">'; }).join('') + '<i>本周已有 326 位妈妈投稿</i></span></div>';
}
function paint(cat, first) {
cur = cat; idx = 0;
deck.innerHTML = notes(cat).map(storyCard).join('') + submitCard();
cards = [].slice.call(deck.children);
if (!first && !reduce) {
deck.classList.add('deal');
cards.forEach(function (c, i) { c.style.transitionDelay = (Math.min(i, 3) * 60) + 'ms'; });
layout();
requestAnimationFrame(function () { requestAnimationFrame(function () { deck.classList.remove('deal'); layout(); setTimeout(function () { cards.forEach(function (c) { c.style.transitionDelay = ''; }); }, 500); }); });
} else layout();
tags.innerHTML = '<span class="g4-tl">大家在聊</span>' + topics(cat).map(function (t) { return '<button type="button" data-t="' + esc(t.id) + '">#' + esc(t.name) + '#</button>'; }).join('');
tags.scrollLeft = 0;
}
/* depth:0 顶层;1、2 在后面错开;>2 藏在后面;<0 已翻走(左侧飞出) */
function layout() {
cards.forEach(function (c, i) {
var d = i - idx;
c.setAttribute('data-d', d < 0 ? 'out' : d > 3 ? 3 : d);
c.style.zIndex = 20 - Math.abs(d);
c.style.transform = '';
c.setAttribute('aria-hidden', d === 0 ? 'false' : 'true');
c.tabIndex = d === 0 ? 0 : -1;
});
pgB.textContent = idx + 1; pgI.textContent = '/' + cards.length;
/* 11-02 用户:滑到哪张图,右上角分类小滑块跟着跳到它的归属类(只在「全部」里跳;投稿卡回到「全部」;选了具体分类时不动) */
if (cur === '') {
var cc = cards[idx] && (cards[idx].className.match(/\bc-(\w+)/) || [])[1] || '';
[].forEach.call(sec.querySelectorAll('.g4-cat button'), function (x) { x.classList.toggle('on', x.dataset.cat === cc); });
}
}
function next() {
if (idx < cards.length - 1) { idx++; layout(); return; }
/* 最后一张再往后 → 整叠收回,从第 1 张重新开始 */
idx = 0; deck.classList.add('reset'); layout();
setTimeout(function () { deck.classList.remove('reset'); }, 380);
}
function prev() { if (idx > 0) { idx--; layout(); } else bounce(1); }
function bounce(dir) {
var c = cards[idx]; if (!c || reduce) return;
c.style.transform = 'translateX(' + dir * 14 + 'px) rotate(' + dir * 1.5 + 'deg)';
setTimeout(function () { c.style.transform = ''; }, 160);
}
/* 拖动:方向锁 —— 先判断横 / 竖,竖向直接放行给页面滚动,横向才拦截(touchmove passive:false 才能 preventDefault) */
var drag = null, moved = false;
function start(x, y) { var c = cards[idx]; if (!c) return; drag = { x: x, y: y, dx: 0, lock: '', t: Date.now(), c: c }; moved = false; }
function move(x, y, e) {
if (!drag) return;
var dx = x - drag.x, dy = y - drag.y;
if (!drag.lock) {
if (Math.abs(dx) < 6 && Math.abs(dy) < 6) return;
drag.lock = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y';
if (drag.lock === 'y') { drag = null; return; }
drag.c.classList.add('drag'); deck.classList.add('dragging');
}
if (e && e.cancelable) e.preventDefault();
if (e) e.stopPropagation();
moved = true;
if ((idx === 0 && dx > 0)) dx = dx * 0.3; // 第一张往右拖:阻尼
drag.dx = dx;
drag.c.style.transform = 'translateX(' + dx + 'px) translateY(' + Math.abs(dx) * 0.04 + 'px) rotate(' + dx / 18 + 'deg)';
}
function end() {
if (!drag) return false;
var d = drag; drag = null;
d.c.classList.remove('drag'); deck.classList.remove('dragging');
if (!d.lock) return false;
var v = Math.abs(d.dx) / Math.max(1, Date.now() - d.t);
if (d.dx < -70 || (d.dx < -24 && v > 0.45)) next();
else if ((d.dx > 70 || (d.dx > 24 && v > 0.45)) && idx > 0) prev();
else d.c.style.transform = '';
return true;
}
deck.addEventListener('touchstart', function (e) { if (e.touches.length === 1) start(e.touches[0].clientX, e.touches[0].clientY); else drag = null; }, { passive: true });
deck.addEventListener('touchmove', function (e) { if (drag && e.touches.length === 1) move(e.touches[0].clientX, e.touches[0].clientY, e); }, { passive: false });
/* 首页在 document 上有「左右滑切底部 tab」的手势(app.js touchend):卡堆横拖结束时截住 touchend,别让它把页面切走 */
deck.addEventListener('touchend', function (e) { if (end()) e.stopPropagation(); });
deck.addEventListener('touchcancel', end);
deck.addEventListener('mousedown', function (e) {
if (e.button) return; start(e.clientX, e.clientY); e.preventDefault();
var mm = function (ev) { move(ev.clientX, ev.clientY, ev); }, mu = function () { end(); document.removeEventListener('mousemove', mm); document.removeEventListener('mouseup', mu); };
document.addEventListener('mousemove', mm); document.addEventListener('mouseup', mu);
});
deck.addEventListener('click', function (e) {
if (moved) { moved = false; return; }
var c = e.target.closest('.g4-card');
if (!c) return;
if (c !== cards[idx]) { next(); return; } // 点到后面露出的卡角 → 翻到下一张
go(c.dataset.sub ? '' : c.dataset.t, c.dataset.sub ? '' : c.dataset.n);
});
deck.addEventListener('keydown', function (e) {
if (e.key === 'ArrowRight') next(); else if (e.key === 'ArrowLeft') prev();
else if (e.key === 'Enter') { var c = cards[idx]; if (c) go(c.dataset.sub ? '' : c.dataset.t, c.dataset.sub ? '' : c.dataset.n); }
});
tags.addEventListener('click', function (e) { var b = e.target.closest('button[data-t]'); if (b) go(b.dataset.t); });
sec.querySelector('.g4-all').addEventListener('click', function () { go(''); });
sec.querySelector('.g4-sub').addEventListener('click', function () { go(''); });
/* 孕周变了(档案里改了 / 孕期数据晚到)→ 段变了才重排,免得翻到一半被打回第 1 张 */
var lastUB = userBand();
window.addEventListener('mb-preg', function () { var u = userBand(); if (u !== lastUB) { lastUB = u; paint(cur, true); } });
paint('', true);
/* 11-03 用户:「首次进入首页时,孕妈广场下面的图片给一个左右滑动的动效,提示用户可以滑」
→ 卡堆第一次露出大半屏时:顶上那张往左探 2 下再回来 + 小手 + 「左右滑动看更多」;只在第一次进首页演一次(mmbox.gc4.hint.v1),
用户自己滑过 / 点过卡堆就不再演;演的过程中一碰就停。系统开了「减少动态效果」不演。 */
var HK = 'mmbox.gc4.hint.v1', hinted = false;
try { hinted = !!localStorage.getItem(HK); } catch (e) {}
function hintDone() { hinted = true; try { localStorage.setItem(HK, '1'); } catch (e) {} }
if (!hinted && !reduce && window.IntersectionObserver && Element.prototype.animate) {
var stage = sec.querySelector('.g4-stage'), hintEl = null, anims = [], hintT = 0;
var stopHint = function () {
clearTimeout(hintT); anims.forEach(function (a) { try { a.cancel(); } catch (e) {} }); anims = [];
if (hintEl) { var h = hintEl; hintEl = null; h.classList.add('off'); setTimeout(function () { h.remove(); }, 260); }
};
['touchstart', 'mousedown'].forEach(function (ev) { deck.addEventListener(ev, function () { if (!hinted) hintDone(); stopHint(); }, { passive: true }); });
/* 卡堆露出 60% 以上(扣掉底部 tab 栏那条)且首页引导 coach.js 的遮罩已关、页面在前台,停 0.5 秒再演;引导还开着就等它关 */
var seen = false, waitT = 0;
function tryPlay() {
clearTimeout(waitT);
if (hinted || !seen) return;
if (document.querySelector('.mb-coach') || document.hidden) { waitT = setTimeout(tryPlay, 700); return; }
hintT = setTimeout(function () {
if (!seen || hinted || document.querySelector('.mb-coach')) { tryPlay(); return; }
io.disconnect();
var c = cards[idx]; if (!c || hinted) return;
hintDone();
stage.insertAdjacentHTML('beforeend', '<div class="g4-hint" aria-hidden="true"><span class="g4-hand"><svg viewBox="0 0 24 24"><path d="M9.5 11.2V5.3a1.5 1.5 0 013 0v5.2m0-.7V9a1.5 1.5 0 013 0v1.6m0-.4a1.5 1.5 0 013 0v4.3c0 3.4-2.4 6-5.7 6h-.9c-1.8 0-3.2-.8-4.3-2.2l-3-4a1.5 1.5 0 012.3-1.9l1.6 1.7V11.2"/></svg></span><b>左右滑动 看更多</b></div>');
hintEl = stage.lastElementChild;
var ez = { duration: 2600, easing: 'ease-in-out', iterations: 1 };
var kf = [
{ transform: 'none', offset: 0 }, { transform: 'translateX(-46px) rotate(-5deg)', offset: .2 }, { transform: 'none', offset: .38 },
{ transform: 'translateX(-46px) rotate(-5deg)', offset: .6 }, { transform: 'none', offset: .78 }, { transform: 'none', offset: 1 }
];
anims.push(c.animate(kf, ez));
var n1 = cards[idx + 1]; /* 后面那张跟着往前凑一点,像要接上来 */
if (n1) anims.push(n1.animate([
{ transform: 'translate(12px,4px) rotate(4deg) scale(.95)', offset: 0 }, { transform: 'translate(4px,2px) rotate(1.5deg) scale(.97)', offset: .2 },
{ transform: 'translate(12px,4px) rotate(4deg) scale(.95)', offset: .38 }, { transform: 'translate(4px,2px) rotate(1.5deg) scale(.97)', offset: .6 },
{ transform: 'translate(12px,4px) rotate(4deg) scale(.95)', offset: .78 }, { transform: 'translate(12px,4px) rotate(4deg) scale(.95)', offset: 1 }
], ez));
var hd = hintEl.querySelector('.g4-hand');
anims.push(hd.animate([
{ transform: 'translateX(0)', opacity: 0, offset: 0 }, { transform: 'translateX(0)', opacity: 1, offset: .04 }, { transform: 'translateX(-60px)', opacity: 1, offset: .2 },
{ transform: 'translateX(0)', opacity: 1, offset: .38 }, { transform: 'translateX(-60px)', opacity: 1, offset: .6 }, { transform: 'translateX(0)', opacity: 1, offset: .78 },
{ transform: 'translateX(0)', opacity: 0, offset: 1 }
], ez));
hintT = setTimeout(stopHint, 2900);
}, 500);
}
var io = new IntersectionObserver(function (es) { seen = es[0].isIntersecting; if (seen) tryPlay(); }, { threshold: .6, rootMargin: '0px 0px -110px 0px' });
io.observe(deck);
}
sec.dispatchEvent(new Event('gc4-ready')); /* V6 合并版等这个再往里插「其他妈妈也在搜」 */
}
/* feed.js 首页默认不加载(index.html 只引了 topics.js),这里按需拉一次 */
if (window.GC_FEED) init();
else { var fs = document.createElement('script'); fs.src = 'guangchang/feed.js?v=26110273'; fs.onload = fs.onerror = init; document.body.appendChild(fs); }
})();