perf(lightbox): 点开图片不再白屏干等,先糊后清 #10
用户反馈:点一张图之后先是白背景渲染出来,图片迟迟不出现,感觉特别慢。 实测归因(_v40_slow.py,自建慢速 HTTP 服务,3.2 Mbps): · 遮罩可见 31ms —— 纯 CSS 底色,一帧就画完,从来不是瓶颈 · 图片有像素 6 秒后仍然没有 线上原图最大 3.2MB(PUA的两面性与界定-scaled.png),3Mbps 下要 8 秒以上, 这段时间屏幕上是整片白 + 一个 0x0 的空 img。 三层改造: 1. 缩略图占位(blur-up):点开瞬间把正文里已经解码过的 currentSrc 顶上, 0 延迟就有像素;原图到了再淡入覆盖。 2. 原图淡入:is-ready 后 opacity 0→1,避免看到渐进渲染的半张图。 3. 空闲预取:requestIdleCallback 里串行预取视口上下 1.5 屏内的原图, 用 new Image() 不塞 DOM;尊重 saveData / slow-2g。 CSS 的 .yang-lb-stage 从 flex 改 grid,占位层/原图/转圈都 grid-area:1/1 叠在同一个格子(flex 会让子元素并排,absolute 又不参与居中)。 实测(_v40_opt2.py,5/0): 立刻点(最坏) 遮罩 25ms · 首帧有像素 25ms · 原图 15012ms 读 12 秒后点 遮罩 25ms · 首帧有像素 25ms · 原图 25ms(预取命中) 服务端各收到 1 次请求,预取一次命中、没有重复下载。 验证:交互 245/0(新增 J 段占位层与加载态 45 条)· 服务端 55/0(8.4+8.5) · 演示页 34/0 · 反向验证 27/27(新增 U11~U14) Refs #10
This commit is contained in:
+97
-8
@@ -104,16 +104,43 @@ body.yang-lb-lock {
|
||||
/* ── 图片舞台 ─────────────────────────────────────────────── */
|
||||
|
||||
.yang-lb-stage {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* 🔴 这里从 flex 改成 grid:要让「占位图」和「原图」**叠在同一个格子**里。
|
||||
用 flex 的话两个子元素会并排(一个在左一个在右),必须绝对定位才能叠,
|
||||
而 absolute 就不参与居中 → 又要手动算偏移,脆且难调。
|
||||
grid-area 叠放是最省事的:两图都居中,cell 尺寸由两者较大者决定。 */
|
||||
display: grid;
|
||||
place-items: center;
|
||||
/* 给左右箭头留出空间,避免大图被箭头压住 */
|
||||
width: calc(100% - 132px);
|
||||
height: calc(100% - 96px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ── 两层图片:占位层 + 原图层 ─────────────────────────────
|
||||
*
|
||||
* ── 为什么要两层 ────────────────────────────────────────────
|
||||
* 用户反馈:「点一张图之后,先把这个白色背景渲染出来,然后图再出来,
|
||||
* 感觉速度特别慢」。实测(_v40_slow.py,自建慢速 HTTP 服务):
|
||||
*
|
||||
* 遮罩可见 31 ms ← 纯 CSS 底色,一帧就画完,从来不是瓶颈
|
||||
* 图片有像素 6 秒后仍然没有
|
||||
*
|
||||
* 线上原图实测最大 3.2 MB(`PUA的两面性与界定-scaled.png`),
|
||||
* 3 Mbps 下要 8 秒以上。这 8 秒里屏幕上是**整片白 + 一个 0×0 的空 img**,
|
||||
* 用户看到的就是「白背景先出来,图后到」。
|
||||
*
|
||||
* 解法:点开的瞬间先把**正文里那张已经在缓存里的缩略图**放上去。
|
||||
* 它早就下载并解码过了,0 延迟就有像素 —— 屏幕立刻「有东西」,
|
||||
* 只是糊一点;原图到了再淡入覆盖。这是业界通行的 blur-up,
|
||||
* 视觉上是「先糊后清」的连续过程,而不是「先空后突然出现」。
|
||||
*
|
||||
* ⚠️ 占位图必须 `pointer-events: none`:它只是一层视觉垫底,
|
||||
* 点击应该落在原图上(点图不关闭),不能被垫底挡住。
|
||||
*/
|
||||
.yang-lb-ph,
|
||||
.yang-lb-img {
|
||||
/* 叠在同一个格子里 —— 这是两层方案能成立的关键一行 */
|
||||
grid-area: 1 / 1;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
@@ -121,19 +148,72 @@ body.yang-lb-lock {
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.yang-lb-ph {
|
||||
/* 垫底:放大后自然发糊,不额外加 blur 滤镜
|
||||
(滤镜在大图上要额外一次全图离屏合成,慢网下雪上加霜) */
|
||||
pointer-events: none;
|
||||
/* 没拿到缩略图时(比如外链图床)不留一个空框 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.yang-lb-ph.is-on {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.yang-lb-img {
|
||||
position: relative; /* 压住占位层,确保点击落在原图上 */
|
||||
z-index: 1;
|
||||
box-shadow: var(--lb-shadow);
|
||||
border-radius: 2px;
|
||||
background-color: transparent;
|
||||
/* 图片本身要能接收点击(点图不关闭),但舞台是 none,
|
||||
所以这里必须显式改回来 */
|
||||
pointer-events: auto;
|
||||
/* 大图加载时的淡入,避免「啪」一下出现 */
|
||||
animation: yang-lb-fade 0.2s ease;
|
||||
/* 原图未就绪时先隐藏:直接显示会看到「半张图」的渐进渲染过程。
|
||||
用 opacity 而不是 display,是为了让它**参与布局**,
|
||||
该占的尺寸提前占住,避免 ready 那一刻整层跳动。 */
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes yang-lb-fade {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
/* 原图已解码完成 → 淡入覆盖占位图 */
|
||||
.yang-lb-img.is-ready {
|
||||
opacity: 1;
|
||||
transition: opacity 0.18s ease;
|
||||
}
|
||||
|
||||
/* ⚠️ 占位图在原图就绪后不能立刻 display:none —— 那样 cell 尺寸会
|
||||
瞬间由「占位图尺寸」变成「原图尺寸」,如果两者不等就有一次跳动。
|
||||
让它淡出,淡出期间 cell 不会变(两个都在布局里)。 */
|
||||
.yang-lb-ph.is-fade {
|
||||
opacity: 0;
|
||||
transition: opacity 0.18s ease;
|
||||
}
|
||||
|
||||
/* ── 加载指示:慢网下必须让用户知道「在加载」而不是「坏了」─────
|
||||
* 3.2 MB 的原图在 3 Mbps 下要 8 秒。这 8 秒里如果只是「糊图慢慢等」,
|
||||
* 用户会怀疑是不是点坏了。一个很轻的脉冲点就够表达「还在来」。
|
||||
* 用 animationend 之外的纯 CSS,不额外占 JS。 */
|
||||
.yang-lb-spin {
|
||||
grid-area: 1 / 1;
|
||||
z-index: 2;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--lb-ctl-bd);
|
||||
border-top-color: var(--lb-fg);
|
||||
animation: yang-lb-rot 0.8s linear infinite;
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.yang-lb.is-loading .yang-lb-spin {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes yang-lb-rot {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ── 左右箭头 ─────────────────────────────────────────────── */
|
||||
@@ -313,12 +393,21 @@ body.yang-lb-lock {
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.yang-lb,
|
||||
.yang-lb-img,
|
||||
.yang-lb-ph,
|
||||
.yang-lb-nav,
|
||||
.yang-lb-close {
|
||||
transition: none;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
/* 转圈是唯一的「还在加载」提示,减少动效下不能整个删掉,
|
||||
否则慢网用户看不到任何反馈。改成静止的文字色圆环(不那么晃眼)。 */
|
||||
.yang-lb-spin {
|
||||
animation: none;
|
||||
border-top-color: var(--lb-ctl-bd);
|
||||
border-right-color: var(--lb-fg);
|
||||
}
|
||||
|
||||
.yang-lb-close:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user