feat(关于页): 重设计为「时间线 + 数据一览」版式,动效对齐全站 UIKit 范式
- 版式:01 关于我(属性四行 + 时间线四节点 + 三张数据卡 + 分类 Top5 条, 全部构建期实时统计,新增 partial about-intro.html)+ 02 这个博客 + 03 走心 评论 + 04 热力图,间距统一由 .about-sec 管,字号统一四档 - 动效:纯官方 UIKit scrollspy(与归档页同源)—— target 一律后代选择器、 动画类用 uk-animation-slide-*-medium(50px 位移)+ repeat:true 滚回重播、 分节标题 fade 不 repeat;删自研编排器,CSS 只留分类条宽度填充与 prefers-reduced-motion 守卫 - 修复 PJAX 下 UIKit 不重绑的既有 bug:mypjax.js 换页后调 UIkit.update() (评论区/说说的 scrollspy 同样依赖此修复) - 走心评论:骨架屏、8s 超时、sessionStorage 缓存(10 分钟)、重试按钮; 异步渲染后手动 UIkit.update() 让卡片参与入场级联
This commit is contained in:
1 parent
a884180787
commit
654de1fdf0
7 files changed
+692
-88
No files matched your search
@@ -5,14 +5,14 @@
|
||||
<style>
|
||||
.featc-wrap { min-width: 0; }
|
||||
.featc-title {
|
||||
font-size: 1.35rem; font-weight: 700; color: var(--text-title-color);
|
||||
font-size: var(--ab-fs-title, 1.0625rem); font-weight: 700; color: var(--text-title-color);
|
||||
line-height: 1.3; margin: 0 0 10px;
|
||||
}
|
||||
.featc-title .featc-count {
|
||||
font-size: 0.72rem; font-weight: 600; vertical-align: super;
|
||||
font-size: var(--ab-fs-micro, 0.75rem); font-weight: 600; vertical-align: super;
|
||||
margin-left: 3px; color: var(--theme-main, #07c160);
|
||||
}
|
||||
.featc-desc { font-size: 0.86rem; line-height: 1.8; color: var(--text-desc, #777); margin: 0 0 18px; }
|
||||
.featc-desc { font-size: var(--ab-fs-body, 0.875rem); line-height: 1.8; color: var(--text-desc, #777); margin: 0 0 18px; }
|
||||
/* 瀑布流:CSS 多列布局,卡片高度不一时自然错落 */
|
||||
.featc-grid {
|
||||
columns: 3; column-gap: 14px; min-width: 0;
|
||||
@@ -36,24 +36,24 @@
|
||||
.featc-head { display: flex; align-items: center; gap: 9px; min-width: 0; }
|
||||
.featc-avatar { width: 34px; height: 34px; border-radius: 50%; flex: none; object-fit: cover; }
|
||||
.featc-nick {
|
||||
font-size: 0.92rem; font-weight: 700; color: var(--text-title-color, #333);
|
||||
font-size: var(--ab-fs-label, 0.8125rem); font-weight: 700; color: var(--text-title-color, #333);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.featc-date { margin-left: auto; flex: none; font-size: 0.72rem; color: var(--text-muted, #999); }
|
||||
.featc-date { margin-left: auto; flex: none; font-size: var(--ab-fs-micro, 0.75rem); color: var(--text-muted, #999); }
|
||||
.featc-content {
|
||||
font-size: 0.88rem; line-height: 1.7; color: var(--text-main, #333);
|
||||
font-size: var(--ab-fs-body, 0.875rem); line-height: 1.75; color: var(--text-main, #333);
|
||||
margin: 0; word-break: break-word;
|
||||
display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden;
|
||||
}
|
||||
.featc-content img { max-height: 1.2em; vertical-align: sub; }
|
||||
.featc-content p { margin: 0; }
|
||||
.featc-foot {
|
||||
font-size: 0.72rem; color: var(--text-muted, #999); line-height: 1.6;
|
||||
font-size: var(--ab-fs-micro, 0.75rem); color: var(--text-muted, #999); line-height: 1.6;
|
||||
padding-left: 8px; border-left: 2px solid var(--border-main, #eee);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.featc-foot a { color: inherit; }
|
||||
.featc-empty { font-size: 0.86rem; color: var(--text-muted, #999); padding: 8px 0; }
|
||||
.featc-empty { font-size: var(--ab-fs-body, 0.875rem); color: var(--text-muted, #999); padding: 8px 0; }
|
||||
/* 暗色模式:主题对文字色是「按元素覆写」而不是重定义变量(--text-title-color
|
||||
在暗色下仍是深色字),这里必须显式给出暗色文字色,否则标题会隐身 */
|
||||
[data-theme="dark"] .featc-title,
|
||||
@@ -70,13 +70,96 @@
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.featc-card, .featc-card:hover { transition: none; transform: none; }
|
||||
}
|
||||
|
||||
/* ── 骨架屏(等接口返回期间占位)──
|
||||
接口实测(真实 Chrome,非沙箱代理)首屏请求约 1.0–1.5s。
|
||||
这 1 秒多的等待用同构灰块填住,比一行「加载中…」体感好得多,
|
||||
而且尺寸与真实卡片一致 → 数据到位时不会发生布局位移。 */
|
||||
.featc-card--sk {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
.featc-card--sk:hover { transform: none; box-shadow: none; }
|
||||
.featc-sk-avatar {
|
||||
display: block; width: 22px; height: 22px; flex: none;
|
||||
border-radius: 50%;
|
||||
background: var(--bankuai-color, #f1f1f1);
|
||||
}
|
||||
.featc-sk-line {
|
||||
display: block; height: 10px; border-radius: 4px; margin-bottom: 8px;
|
||||
background: var(--bankuai-color, #f1f1f1);
|
||||
/* 缓慢的明暗流动,暗示「正在加载」而不是坏掉了 */
|
||||
background-image: linear-gradient(90deg, transparent, rgba(255,255,255,0.6), transparent);
|
||||
background-size: 220px 100%;
|
||||
background-repeat: no-repeat;
|
||||
animation: featc-sk-shimmer 1.4s ease-in-out infinite;
|
||||
}
|
||||
.featc-card--sk .featc-head { margin-bottom: 12px; }
|
||||
@keyframes featc-sk-shimmer {
|
||||
0% { background-position: -220px 0; }
|
||||
100% { background-position: calc(100% + 220px) 0; }
|
||||
}
|
||||
[data-theme="dark"] .featc-sk-avatar,
|
||||
[data-theme="dark"] .featc-sk-line {
|
||||
background-color: rgba(31, 41, 55, 0.9);
|
||||
background-image: linear-gradient(90deg, transparent, rgba(255,255,255,0.06), transparent);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.featc-sk-line { animation: none; }
|
||||
}
|
||||
/* 超时/失败提示(比骨架屏更明确) */
|
||||
.featc-retry {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
padding: 18px 0 6px;
|
||||
}
|
||||
.featc-retry button {
|
||||
font: inherit;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-content-color, #24292e);
|
||||
background: none;
|
||||
border: 1px solid var(--frame-color, #eee);
|
||||
border-radius: 999px;
|
||||
padding: 5px 16px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
.featc-retry button:hover { border-color: #c7ced8; }
|
||||
[data-theme="dark"] .featc-retry button {
|
||||
color: var(--text-content-dark-color, #d1d5db);
|
||||
border-color: var(--frame-dark-color, #1f2933);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="featc-wrap">
|
||||
<div class="featc-wrap" uk-scrollspy="target: .featc-item; cls: uk-animation-slide-bottom-medium; delay: 80; repeat: true">
|
||||
<div class="featc-title">走心评论<span class="featc-count" id="featc-count" hidden></span></div>
|
||||
<p class="featc-desc">当我看到各位在不同的时间和故事中,发出一份份走心的评论时,一份温暖之意即笼罩在我的心头。</p>
|
||||
<!-- ★ 卡片是异步渲染的(接口 1–1.5s),UIKit 的 MutationObserver 只在
|
||||
属性**新增**时反应,新 insert 的子节点不会被自动 connect。
|
||||
所以脚本渲染完成后要自己调一次 UIkit.update(grid) —— 见下方 render()。
|
||||
骨架屏刻意不带 featc-item 类,避免被 scrollspy 隐藏和 shimmer 打架。 -->
|
||||
<div class="featc-grid" id="featc-grid">
|
||||
<div class="featc-empty" id="featc-loading">加载中…</div>
|
||||
<!-- 骨架屏:接口约 1–1.5s 才返回,用 3 张与真实卡片同构的占位块撑住版面,
|
||||
避免「加载中…」一行字造成的空洞感与布局跳动。 -->
|
||||
<div class="featc-card featc-card--sk" aria-hidden="true">
|
||||
<div class="featc-head"><span class="featc-sk-avatar"></span><span class="featc-sk-line" style="width:52px"></span><span class="featc-sk-line" style="width:78px;margin-left:auto"></span></div>
|
||||
<div class="featc-sk-line" style="width:100%"></div>
|
||||
<div class="featc-sk-line" style="width:82%"></div>
|
||||
<div class="featc-sk-line" style="width:40%;margin-top:12px"></div>
|
||||
</div>
|
||||
<div class="featc-card featc-card--sk" aria-hidden="true">
|
||||
<div class="featc-head"><span class="featc-sk-avatar"></span><span class="featc-sk-line" style="width:44px"></span><span class="featc-sk-line" style="width:78px;margin-left:auto"></span></div>
|
||||
<div class="featc-sk-line" style="width:100%"></div>
|
||||
<div class="featc-sk-line" style="width:64%"></div>
|
||||
<div class="featc-sk-line" style="width:40%;margin-top:12px"></div>
|
||||
</div>
|
||||
<div class="featc-card featc-card--sk" aria-hidden="true">
|
||||
<div class="featc-head"><span class="featc-sk-avatar"></span><span class="featc-sk-line" style="width:60px"></span><span class="featc-sk-line" style="width:78px;margin-left:auto"></span></div>
|
||||
<div class="featc-sk-line" style="width:100%"></div>
|
||||
<div class="featc-sk-line" style="width:90%"></div>
|
||||
<div class="featc-sk-line" style="width:100%"></div>
|
||||
<div class="featc-sk-line" style="width:36%;margin-top:12px"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -106,7 +189,7 @@
|
||||
var avatar = 'https://weavatar.com/avatar/' + esc(c.email_encrypted) + '?d=mp&s=120';
|
||||
var nick = esc(c.nick);
|
||||
html +=
|
||||
'<a class="featc-card" href="' + esc(c.page_url) + '#atk-comment-' + esc(c.id) + '">' +
|
||||
'<a class="featc-card featc-item" href="' + esc(c.page_url) + '#atk-comment-' + esc(c.id) + '">' +
|
||||
'<div class="featc-head">' +
|
||||
'<img class="featc-avatar" src="' + avatar + '" alt="" loading="lazy" referrerpolicy="no-referrer">' +
|
||||
'<span class="featc-nick">' + nick + '</span>' +
|
||||
@@ -117,9 +200,47 @@
|
||||
'</a>';
|
||||
}
|
||||
grid.innerHTML = html;
|
||||
|
||||
// ★ 卡片是 fetch 回来后才插进 DOM 的,UIKit 的 MutationObserver 捕捉不到
|
||||
// (它只对「属性新增」反应,不管新插入的子树)—— 必须手动 boot 一次。
|
||||
// 之后完全交给 scrollspy(repeat: true):
|
||||
// · 这一节在视口内 → 同一批 target 进同一个 update 周期,
|
||||
// delay 依次累加,自动错落浮现;
|
||||
// · 在视口外 → 先藏住,滚到时逐条入场,滚走再滚回会重播。
|
||||
if (typeof window.UIkit !== 'undefined' && typeof window.UIkit.update === 'function') {
|
||||
try { window.UIkit.update(grid); } catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
function load() {
|
||||
// 失败/超时提示:明确告知并给一次重试,比一直挂着骨架屏好
|
||||
function renderRetry(msg) {
|
||||
grid.innerHTML =
|
||||
'<div class="featc-retry"><div class="featc-empty" style="margin-bottom:10px">' + esc(msg) + '</div>' +
|
||||
'<button type="button" id="featc-retry-btn">重新加载</button></div>';
|
||||
var btn = document.getElementById('featc-retry-btn');
|
||||
if (btn) btn.addEventListener('click', function () {
|
||||
grid.innerHTML = '<div class="featc-card featc-card--sk"><div class="featc-head"><span class="featc-sk-avatar"></span><span class="featc-sk-line" style="width:52px"></span></div><div class="featc-sk-line" style="width:100%"></div><div class="featc-sk-line" style="width:82%"></div></div>';
|
||||
load(true);
|
||||
});
|
||||
}
|
||||
|
||||
var CACHE_KEY = 'featc_cache_v1';
|
||||
var CACHE_TTL = 10 * 60 * 1000; // 10 分钟
|
||||
|
||||
function readCache() {
|
||||
try {
|
||||
var raw = sessionStorage.getItem(CACHE_KEY);
|
||||
if (!raw) return null;
|
||||
var o = JSON.parse(raw);
|
||||
if (!o || !o.t || (Date.now() - o.t) > CACHE_TTL) return null;
|
||||
return o.list;
|
||||
} catch (e) { return null; }
|
||||
}
|
||||
function writeCache(list) {
|
||||
try { sessionStorage.setItem(CACHE_KEY, JSON.stringify({ t: Date.now(), list: list })); } catch (e) {}
|
||||
}
|
||||
|
||||
function load(force) {
|
||||
// 排版预览 mock(?featmock=1):接口未部署 / 无精选数据时也能看样式
|
||||
if (/[?&]featmock=1/.test(location.search)) {
|
||||
render([
|
||||
@@ -133,11 +254,33 @@
|
||||
]);
|
||||
return;
|
||||
}
|
||||
fetch(API)
|
||||
|
||||
// 会话内缓存:先进关于页已经拉过一次,再进(含 PJAX 来回)直接命中,
|
||||
// 不用再等那 1 秒多。TTL 10 分钟,兼顾新鲜度。
|
||||
if (!force) {
|
||||
var cached = readCache();
|
||||
if (cached) { render(cached); return; }
|
||||
}
|
||||
|
||||
// ★ 超时兜底:接口实测 1–1.5s,但偶有长尾。
|
||||
// 8s 还没回就不再干等,切成「重试」提示 —— 比无限挂骨架屏诚实。
|
||||
var ctrl = ('AbortController' in window) ? new AbortController() : null;
|
||||
var timer = setTimeout(function () { if (ctrl) ctrl.abort(); }, 8000);
|
||||
|
||||
fetch(API, ctrl ? { signal: ctrl.signal } : undefined)
|
||||
.then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
|
||||
.then(function (data) { render((data && data.comments) || []); })
|
||||
.catch(function () {
|
||||
if (grid) grid.innerHTML = '<div class="featc-empty">还没有走心评论,去和博主聊聊吧~</div>';
|
||||
.then(function (data) {
|
||||
clearTimeout(timer);
|
||||
var list = (data && data.comments) || [];
|
||||
writeCache(list);
|
||||
render(list);
|
||||
})
|
||||
.catch(function (err) {
|
||||
clearTimeout(timer);
|
||||
// 有缓存就先用缓存顶(宁可显示旧数据也别显示错误)
|
||||
var stale = readCache();
|
||||
if (stale) { render(stale); return; }
|
||||
renderRetry('评论暂时没加载出来');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user