fix(viewer): 切图动画起点固定为屏幕中心(强制回流),并删掉上一版无效的摘类补丁
老师反馈上一版没修好(附真机截图)。按 Claude 的静态分析复核后确认,根因是 view() 的时序竞态:
· view() 每次都新建一个 <img>:先写成"屏幕中心的点"(宽高 0 + margin 居中),读自然尺寸后
再加 viewer-transition 写最终尺寸,靠 CSS 过渡补间。
· CSS 过渡的起点 = 浏览器"上一次真正渲染过的样式"。第 1 步默认样式是"满宽贴顶",第 2 步是
"中心点",第 3 步是最终值。哪一帧被渲染过决定动画从哪开始:第 2、3 步之间有帧 ⇒ 从中心
长出来;只渲染到第 1 步 ⇒ 从屏幕上方飞入;一帧都没渲染 ⇒ 无动画直接出现。取决于图片是否
缓存、网络快慢、主线程是否空闲 —— 这就是"没规律"的来源。
· 上一版在 pointerup 里 viewer.image.classList.remove('viewer-transition') 无效:view() 新建
的是另一个 <img>,且 load() 会按 options.transition 给新图重新加类。已删除该段及错误注释
(原注释"从上一张图 margin 滑过来"也不成立)。
修法(Claude 方案 A):包一层 viewer.initImage,在它读自然尺寸之前强制回流一次
(void viewer.image.offsetWidth),把"中心点"提交为已渲染样式 ⇒ 起点恒为屏幕中心。
覆盖手指滑动、缩略图条、键盘、底部按钮等所有切图路径(都在 view() 内)。
本机实测(延迟图片响应 + 限速,模拟"未缓存的大图",即 Claude 给的复现法):
切换瞬间 top 与稳定后 top 的差值:A 组 0px / 0px,C 组重跑 0px / 0px ✓
对照组(剥掉补丁)未取得有效数据(生成对照版本时脚本把页面切坏),"无补丁会飞"留待真机确认。
本机 9 套 jsdom 测试全绿。
This commit is contained in:
1 parent
bcbdbd765c
commit
4085642695
3 files changed
+21
-10
No files matched your search
+2
-2
@@ -165,7 +165,7 @@ RUN php -r "file_exists('.env') || copy('.env.example', '.env');" \
|
||||
RUN printf '%s\n' \
|
||||
'c0e513ec8e93fd81b34b3c6de5cf5eb8 ./public/js/context-js/context-js.js' \
|
||||
'c0e513ec8e93fd81b34b3c6de5cf5eb8 ./resources/js/context-js.js' \
|
||||
'69e2e947c24af2795bee2e6323723145 ./resources/views/user/images.blade.php' \
|
||||
'15878ee87c65d46137249dde8e40931f ./resources/views/user/images.blade.php' \
|
||||
| md5sum -c - \
|
||||
&& grep -q "assetVersion('js/context-js/context-js.js')" ./resources/views/user/images.blade.php \
|
||||
&& grep -q 'isIOSWebKit' ./public/js/context-js/context-js.js \
|
||||
@@ -208,7 +208,7 @@ RUN APP_SRC_MD5=$(find app config routes -type f -print0 2>/dev/null | sort -z |
|
||||
"fork_sha=${FORK_SHA}" \
|
||||
"lsky_commit=${LSKY_COMMIT}" \
|
||||
"context_js_md5=c0e513ec8e93fd81b34b3c6de5cf5eb8" \
|
||||
"images_blade_md5=69e2e947c24af2795bee2e6323723145" \
|
||||
"images_blade_md5=15878ee87c65d46137249dde8e40931f" \
|
||||
"app_src_md5=${APP_SRC_MD5}" \
|
||||
"app_js_md5=${APP_JS_MD5}" \
|
||||
"app_css_md5=${APP_CSS_MD5}" \
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
# 三个文件各自的 md5(与 Dockerfile「自证 2」里的期望值一致):
|
||||
# c0e513ec8e93fd81b34b3c6de5cf5eb8 src/resources/js/context-js.js
|
||||
# c0e513ec8e93fd81b34b3c6de5cf5eb8 src/public/js/context-js/context-js.js
|
||||
# 69e2e947c24af2795bee2e6323723145 src/resources/views/user/images.blade.php
|
||||
# 15878ee87c65d46137249dde8e40931f src/resources/views/user/images.blade.php
|
||||
#
|
||||
# 上游原文件 md5(改动前,供比对):
|
||||
# c8e57f6232848ca8277341ddf1a3a7a6 resources/js/context-js.js
|
||||
|
||||
@@ -644,7 +644,24 @@ class="relative inline-flex h-4 w-7 shrink-0 items-center rounded-full bg-brand
|
||||
document.addEventListener('touchcancel', () => { navDown = false; }, true);
|
||||
})();
|
||||
|
||||
/* 触摸端切图判定(接管 slideOnTouch 后的配套逻辑):
|
||||
/* 切图动画起点固定为"屏幕中心"(Claude 方案 A)。
|
||||
* 库每切一张都新建 <img>:先写成屏幕中心的点(宽高 0 + margin 居中),读完自然尺寸
|
||||
* 再加 viewer-transition 写最终宽高与 margin,靠 CSS 过渡补间。CSS 过渡的起点是
|
||||
* 浏览器"上一次真正渲染过的样式" —— 中间那帧有没有渲染出来取决于图片缓存/网速/
|
||||
* 主线程忙不忙,于是表现为"有时从中间长出来、有时从屏幕上方飞进来"。
|
||||
* 这里在 initImage 之前强制回流一次,把"中心点"提交为已渲染样式 ⇒ 起点永远是中心。
|
||||
* 覆盖手指滑动、缩略图条、键盘与底部按钮等所有切图路径(都在 view() 里)。
|
||||
* 注:早先那版在 pointerup 里摘旧图的 viewer-transition 是无效的 —— view() 新建的是
|
||||
* 另一个 <img>,且 load() 会按 options.transition 给新图重新加类。 */
|
||||
if (viewer && typeof viewer.initImage === 'function') {
|
||||
const rawInitImage = viewer.initImage.bind(viewer);
|
||||
viewer.initImage = function (cb) {
|
||||
if (viewer.image) void viewer.image.offsetWidth; // 强制回流,提交"中心点"
|
||||
return rawInitImage(cb);
|
||||
};
|
||||
}
|
||||
|
||||
/* 触摸端切图判定(接管 slideOnTouch 后的配套逻辑):
|
||||
* 库里 slideOnTouch:false ⇒ 单指动作永远是 'move',库自己的平移就是跟手拖动,
|
||||
* 不再出现 'switch' 动作 ⇒ 1px 判据与"闩锁"都不存在。切图还是回弹由松手时决定。
|
||||
* 为什么不拦事件:库用 HAS_POINTER_EVENT ? 'pointermove' : 'touchmove' 选通道,
|
||||
@@ -680,12 +697,6 @@ class="relative inline-flex h-4 w-7 shrink-0 items-center rounded-full bg-brand
|
||||
const far = Math.abs(dx) > Math.max(FAR_MIN, window.innerWidth * FAR_RATIO);
|
||||
const fast = Math.abs(dx) / dt > FAST_SPEED && Math.abs(dx) > 30;
|
||||
if ((far || fast) && Math.abs(dx) > Math.abs(dy) * 1.5) {
|
||||
/* 切图不要"飞入":库的过渡是"从上一张图的 margin 滑到新图的 margin",
|
||||
* 两张图高度不同(横图↔竖图)时,新图就会从偏上/偏下的位置滑进来。
|
||||
* 这一段临时摘掉过渡类 ⇒ 新图直接出现在它该在的位置(= "从中间出来")。
|
||||
* 过渡类是库自己在管的(action 为 move/zoom 时它自己也会摘),不属于
|
||||
* 那个碰不得的 action 状态机,且同一帧即由 renderImage 恢复。 */
|
||||
viewer.image.classList.remove('viewer-transition');
|
||||
if (dx < 0) viewer.next(false); else viewer.prev(false);
|
||||
guardClick = Date.now(); // 标记:刚刚切过图
|
||||
} else if (dx !== 0 || dy !== 0) {
|
||||
|
||||
Reference in new issue
Block a user