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

192 lines
13 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: '经验' };
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>' +
'<span class="g4-cat" role="tablist" aria-label="话题分类"><button type="button" class="on" data-cat="">全部</button><button type="button" data-cat="family">家庭婚姻</button><button type="button" data-cat="work">职场</button><button type="button" data-cat="baby">经验</button></span></div>' +
'<div class="g4-sub"><span class="g4-avs" aria-hidden="true">' + ['p05', 'm07', 'p11', 'm16'].map(function (n) { return '<img src="' + ava(n) + '" alt="" loading="lazy">'; }).join('') + '</span><span>翻一翻,看看其他妈妈的故事</span><button type="button" class="g4-in">逛广场<i>›</i></button></div>' +
'<div class="g4-stage"><div class="g4-deck" aria-live="polite"></div>' +
'<div class="g4-rail"><span class="g4-pg"><b>1</b><i>/8</i></span>' +
'<button type="button" class="g4-arr prev" aria-label="上一张">‹</button><button type="button" class="g4-arr next" aria-label="下一张">›</button></div></div>' +
'<div class="g4-tags"></div>' +
'<div class="g4-foot"><button type="button" class="g4-all">进孕妈广场,看全部 ' + (window.GC_FEED ? window.GC_FEED.length + ' 篇分享' : T.length + ' 个话题') + ' ›</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); });
}
sec.querySelector('.g4-arr.prev').disabled = idx === 0;
}
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); }
});
sec.querySelector('.g4-arr.next').addEventListener('click', next);
sec.querySelector('.g4-arr.prev').addEventListener('click', prev);
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-in').addEventListener('click', function () { go(''); });
sec.querySelector('.g4-cat').addEventListener('click', function (e) {
var b = e.target.closest('button[data-cat]');
if (!b || b.dataset.cat === cur) return; // 「全部」里滑块跟着卡片跳,所以按当前筛选判断,不按高亮判断
[].forEach.call(sec.querySelectorAll('.g4-cat button'), function (x) { x.classList.toggle('on', x === b); });
paint(b.dataset.cat);
});
/* 孕周变了(档案里改了 / 孕期数据晚到)→ 段变了才重排,免得翻到一半被打回第 1 张 */
var lastUB = userBand();
window.addEventListener('mb-preg', function () { var u = userBand(); if (u !== lastUB) { lastUB = u; paint(cur, true); } });
paint('', true);
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=26110225'; fs.onload = fs.onerror = init; document.body.appendChild(fs); }
})();