fix(viewer): 用 CSS 把切图过渡的起点钉在屏幕中心(保留动画,去掉造成忽有忽无的关过渡补丁)
老师反馈两点:① 反向切换(图都已加载完)时仍有时带过渡、有时不带;② 想要保留过渡
动画,"直接闪出来"效果差。复核后定位到两条:
· 上一版的 1 秒兜底定时器:慢图(>1s 才加载完)会在库读 options.transition 之前先被
恢复,于是过渡又生效 —— 这是"有时有动画"的来源之一。
· 上一版还有一把锁:只有上一次已恢复(saved === null)时才去关过渡。若 1 秒内连续切
(或 viewed 未到),这一次就不关 ⇒ 又带过渡。两条叠加就表现为"忽有忽无"。
既然老师要动画,方向改为"保留库自带的过渡,把起点钉死":
库每切一张新建 <img>,刚插入时用默认 CSS(满宽、贴顶);加载完成后才写内联样式:先写成
"屏幕中心的一个点",再写最终尺寸,靠 CSS 过渡补间。过渡起点 = 浏览器上一次渲染过的样式,
哪一帧被渲染由缓存/网速/主线程决定 —— 渲染到"满宽贴顶"那帧就会从上方飞入。
对策(不动库任何逻辑):用 :not([style]) 只命中"还没有内联样式"的那一瞬,把这一瞬做成
屏幕中心的小点。库一写内联样式该规则立即失效,因此绝不影响最终显示,也不会与库打架;
过渡仍是库自带的,所以动画完整保留。
本次移除上一版"临时关 options.transition"的整段补丁。
本机验收:9 套 jsdom 守卫测试全绿;浏览器六项全过(拖动跟手+回弹、横划 120px 切、
竖划不切、中途不翻页、捏合 351→1615、双击 1615→2751、切图后不误关);
飞入专项按 Claude 的延迟加载法执行;最终显示核对(宽度非 1px、opacity=1、已写内联样式)。
注:本机模拟环境复现不出真机那个飞入,最终以真机为准。
过程记录:本轮先被守卫拦下一次(把 CSS 误放进 <script> 块,测试会把 script 内容当 JS 跑),
已挪到 <style> 块并重跑,未影响线上。
This commit is contained in:
1 parent
5067ed817b
commit
0caef9cac0
3 files changed
+22
-40
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' \
|
RUN printf '%s\n' \
|
||||||
'c0e513ec8e93fd81b34b3c6de5cf5eb8 ./public/js/context-js/context-js.js' \
|
'c0e513ec8e93fd81b34b3c6de5cf5eb8 ./public/js/context-js/context-js.js' \
|
||||||
'c0e513ec8e93fd81b34b3c6de5cf5eb8 ./resources/js/context-js.js' \
|
'c0e513ec8e93fd81b34b3c6de5cf5eb8 ./resources/js/context-js.js' \
|
||||||
'7eecbcee022dfedae87030cce7ff0961 ./resources/views/user/images.blade.php' \
|
'463c8b7f163b7f6c6ea88b0c794a44f9 ./resources/views/user/images.blade.php' \
|
||||||
| md5sum -c - \
|
| md5sum -c - \
|
||||||
&& grep -q "assetVersion('js/context-js/context-js.js')" ./resources/views/user/images.blade.php \
|
&& 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 \
|
&& 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}" \
|
"fork_sha=${FORK_SHA}" \
|
||||||
"lsky_commit=${LSKY_COMMIT}" \
|
"lsky_commit=${LSKY_COMMIT}" \
|
||||||
"context_js_md5=c0e513ec8e93fd81b34b3c6de5cf5eb8" \
|
"context_js_md5=c0e513ec8e93fd81b34b3c6de5cf5eb8" \
|
||||||
"images_blade_md5=7eecbcee022dfedae87030cce7ff0961" \
|
"images_blade_md5=463c8b7f163b7f6c6ea88b0c794a44f9" \
|
||||||
"app_src_md5=${APP_SRC_MD5}" \
|
"app_src_md5=${APP_SRC_MD5}" \
|
||||||
"app_js_md5=${APP_JS_MD5}" \
|
"app_js_md5=${APP_JS_MD5}" \
|
||||||
"app_css_md5=${APP_CSS_MD5}" \
|
"app_css_md5=${APP_CSS_MD5}" \
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
# 三个文件各自的 md5(与 Dockerfile「自证 2」里的期望值一致):
|
# 三个文件各自的 md5(与 Dockerfile「自证 2」里的期望值一致):
|
||||||
# c0e513ec8e93fd81b34b3c6de5cf5eb8 src/resources/js/context-js.js
|
# c0e513ec8e93fd81b34b3c6de5cf5eb8 src/resources/js/context-js.js
|
||||||
# c0e513ec8e93fd81b34b3c6de5cf5eb8 src/public/js/context-js/context-js.js
|
# c0e513ec8e93fd81b34b3c6de5cf5eb8 src/public/js/context-js/context-js.js
|
||||||
# 7eecbcee022dfedae87030cce7ff0961 src/resources/views/user/images.blade.php
|
# 463c8b7f163b7f6c6ea88b0c794a44f9 src/resources/views/user/images.blade.php
|
||||||
#
|
#
|
||||||
# 上游原文件 md5(改动前,供比对):
|
# 上游原文件 md5(改动前,供比对):
|
||||||
# c8e57f6232848ca8277341ddf1a3a7a6 resources/js/context-js.js
|
# c8e57f6232848ca8277341ddf1a3a7a6 resources/js/context-js.js
|
||||||
|
|||||||
@@ -19,6 +19,24 @@
|
|||||||
不再需要 @media (hover: none) 那条「给触摸设备常显操作按钮」的补丁 —— 已删掉,
|
不再需要 @media (hover: none) 那条「给触摸设备常显操作按钮」的补丁 —— 已删掉,
|
||||||
别再写回来:按钮本来就常显,触摸端与桌面端行为一套。--}}
|
别再写回来:按钮本来就常显,触摸端与桌面端行为一套。--}}
|
||||||
<style>
|
<style>
|
||||||
|
/* 让"切图过渡"的起点稳定在屏幕中心。
|
||||||
|
* 库每切一张都新建 <img>:刚插进画布时用的是默认 CSS(满宽、贴顶,来自库自带的
|
||||||
|
* .viewer-container img{width:100%;height:auto} + margin:15px auto)。之后图片加载完成,
|
||||||
|
* 库才写内联样式:先写成"屏幕中心的一个点",再写最终尺寸,靠 CSS 过渡补间。
|
||||||
|
* 而过渡的起点 = 浏览器「上一次真正渲染过的样式」:若只渲染到"满宽贴顶"那一帧,
|
||||||
|
* 动画就从屏幕上方飞进来。哪一帧被渲染由缓存/网速/主线程决定 —— 这就是"忽上忽下"。
|
||||||
|
* 对策:用 :not([style]) 只命中"还没有内联样式"的那一瞬(库一写内联样式立刻失效,
|
||||||
|
* 因此绝不影响最终显示、也不会和库打架),把这一瞬做成"屏幕中心的小点" ——
|
||||||
|
* 无论渲染到哪一帧,起点都是中心。过渡照旧用库自带的,所以动画保留。 */
|
||||||
|
.viewer-canvas > img:not([style]) {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
margin-left: 50vw;
|
||||||
|
margin-top: 50vh;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#album-switch-modal .infinite-scroll {
|
#album-switch-modal .infinite-scroll {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -644,43 +662,7 @@ class="relative inline-flex h-4 w-7 shrink-0 items-center rounded-full bg-brand
|
|||||||
document.addEventListener('touchcancel', () => { navDown = false; }, true);
|
document.addEventListener('touchcancel', () => { navDown = false; }, true);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/* 切图不要过渡动画(Claude 方案 B)。
|
/* 触摸端切图判定(接管 slideOnTouch 后的配套逻辑):
|
||||||
* 库每切一张都新建 <img>:先把新图写成"屏幕中心的点",读完自然尺寸再写最终尺寸,靠
|
|
||||||
* CSS 过渡补间;而过渡的起点是"浏览器上一次真正渲染过的样式",哪一帧渲染过取决于图片
|
|
||||||
* 缓存/网速/主线程忙不忙,于是表现为"有时从中间长出来、有时从屏幕上方飞入"。方案 A
|
|
||||||
* (强制回流)在真机上仍不稳定,索性取消这一下的动画 —— 起点问题就不存在了。
|
|
||||||
* 必须走 options.transition 开关,**不能**用 CSS transition:none 去压:那样 renderImage
|
|
||||||
* 永远等不到 transitionend,viewed 不触发,手势会被卡死。
|
|
||||||
* 路径覆盖:next()/prev() 内部调的都是 this.view,所以手指滑动、缩略图条、键盘、
|
|
||||||
* 底部按钮全都会经过这里。 */
|
|
||||||
(function () {
|
|
||||||
let saved = null;
|
|
||||||
let timer = 0;
|
|
||||||
const restore = () => {
|
|
||||||
if (saved === null) return;
|
|
||||||
viewer.options.transition = saved;
|
|
||||||
saved = null;
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = 0;
|
|
||||||
};
|
|
||||||
// 防御:测试环境里的 Viewer 是替身,可能没有 view / element
|
|
||||||
if (!viewer || typeof viewer.view !== 'function') return;
|
|
||||||
const rawView = viewer.view.bind(viewer);
|
|
||||||
viewer.view = function (index) {
|
|
||||||
const target = (typeof index === 'number') ? index : viewer.index;
|
|
||||||
if (target !== viewer.index && viewer.isShown && saved === null) {
|
|
||||||
saved = viewer.options.transition;
|
|
||||||
viewer.options.transition = false;
|
|
||||||
timer = setTimeout(restore, 1000); // 兜底:万一 viewed 没来也不卡住
|
|
||||||
}
|
|
||||||
return rawView(index);
|
|
||||||
};
|
|
||||||
// viewed 是库派发的 DOM 事件(不是那个不存在的 viewer.on),两处都听稳一点
|
|
||||||
if (viewer.element) viewer.element.addEventListener('viewed', restore);
|
|
||||||
document.addEventListener('viewed', restore, true);
|
|
||||||
})();
|
|
||||||
|
|
||||||
/* 触摸端切图判定(接管 slideOnTouch 后的配套逻辑):
|
|
||||||
* 库里 slideOnTouch:false ⇒ 单指动作永远是 'move',库自己的平移就是跟手拖动,
|
* 库里 slideOnTouch:false ⇒ 单指动作永远是 'move',库自己的平移就是跟手拖动,
|
||||||
* 不再出现 'switch' 动作 ⇒ 1px 判据与"闩锁"都不存在。切图还是回弹由松手时决定。
|
* 不再出现 'switch' 动作 ⇒ 1px 判据与"闩锁"都不存在。切图还是回弹由松手时决定。
|
||||||
* 为什么不拦事件:库用 HAS_POINTER_EVENT ? 'pointermove' : 'touchmove' 选通道,
|
* 为什么不拦事件:库用 HAS_POINTER_EVENT ? 'pointermove' : 'touchmove' 选通道,
|
||||||
|
|||||||
Reference in new issue
Block a user