feat(viewer): 手机端切图阈值接管(老师定的三条规则)—— 不再"稍动就切"

老师反馈:手机端切图手势过于灵敏(滑短短一下就切),想拖动基本都误触;而且"放大后能拖、
缩小后一拖就切图",阈值区间摸不透。

根源(源码核对):库把切图阈值硬编码成 1px(others.js 的 change():
absoluteOffsetX > 1 && > |offsetY|),而且"这一下是切图还是拖动"在 pointerdown 就按
"图片有没有超出屏幕"定死了 ⇒ 未放大时几乎必然判成切图、放大后又必然判成拖动 ——
规则本身不自洽,所以摸不透。

老师定的三条规则(实现如下,全部挂在外面,不改 viewer.min.js):
  · 未放大:横划跟手(库自带的行为保留),松手时 |dx| ≥ 40px 且 |dx| > |dy|*1.2 才切图,
    否则把已切过去的那张 view() 回来 —— 观感就是弹回原位;
  · 放大后:拖动看图照旧(库的 ACTION_MOVE),横划 ≥ 80px 才由本层补一次 view();
  · 竖直滑永远不切图(方向判据同时生效)。
判定用 pointer 事件(与库同一套),范围限定 .viewer-canvas 内,不碰缩略图条。

本机实测(390x844 手机模拟,CDP 真触摸):
  未放大 横划 20px  → 没切 ✓(期望不切)
  未放大 竖划 80px  → 没切 ✓(方向判据生效)
  未放大 横划 120px → 切了 ✓
  放大后 50px/140px 两条:无头环境里"放大"这个动作没成功(工具栏点击与滚轮都试过),
  所以那两条实际跑在未放大状态,未取得有效数据。放大后的 80px 阈值待老师真机确认手感。

本机 9 套 jsdom 测试 409 项全绿。
This commit is contained in:
mole404 committed 2026-10-03 12:31:50 +08:00
1 parent 58e24766fa
commit 49ef183a8e
3 files changed
+61 -3

No files matched your search

+2 -2
View File
@@ -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' \
'eeea8d21cbfb849fd3c624dc06512d95 ./resources/views/user/images.blade.php' \
'e03d452d272d7d5e3767d6d3385a1e65 ./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=eeea8d21cbfb849fd3c624dc06512d95" \
"images_blade_md5=e03d452d272d7d5e3767d6d3385a1e65" \
"app_src_md5=${APP_SRC_MD5}" \
"app_js_md5=${APP_JS_MD5}" \
"app_css_md5=${APP_CSS_MD5}" \
+1 -1
View File
@@ -52,7 +52,7 @@
# 三个文件各自的 md5(与 Dockerfile「自证 2」里的期望值一致):
# c0e513ec8e93fd81b34b3c6de5cf5eb8 src/resources/js/context-js.js
# c0e513ec8e93fd81b34b3c6de5cf5eb8 src/public/js/context-js/context-js.js
# eeea8d21cbfb849fd3c624dc06512d95 src/resources/views/user/images.blade.php
# e03d452d272d7d5e3767d6d3385a1e65 src/resources/views/user/images.blade.php
#
# 上游原文件 md5(改动前,供比对):
# c8e57f6232848ca8277341ddf1a3a7a6 resources/js/context-js.js
+58
View File
@@ -642,6 +642,64 @@ class="relative inline-flex h-4 w-7 shrink-0 items-center rounded-full bg-brand
document.addEventListener('touchcancel', () => { navDown = false; }, true);
})();
/* 触摸端切图阈值接管(老师定的三条规则):
* 未放大:横划跟手,松手时 |dx| ≥ 40px 且明显横向才切图,否则弹回原位;
* 放大后:拖动看图照旧(库自己的 ACTION_MOVE),横划 ≥ 80px 且明显横向才切图;
* 竖直滑永远不切图。
* 为什么要在外面接管:库把阈值硬编码成 1px(others.js 的 change():
* absoluteOffsetX > 1 && > |offsetY|),而且"这一下是切图还是拖动"在 pointerdown
* 就按"图片有没有超出屏幕"定死了 ⇒ 未放大时几乎必然判成切图(稍动就切)、
* 放大后又必然判成拖动(切不了)。老师反馈"阈值区间摸不透"就是这个不自洽。
* 做法:库的"跟手滑动"保留不动,只在松手时若不够格就把切过去的那张 view() 回来
*(观感就是弹回原位);放大后库不切,由本层按 80px 判定补一次 view()。
* 注意用 pointer 事件(与库同一套),判断范围限定在 .viewer-canvas 内,不碰缩略图条。 */
(function () {
const SWIPE_NOT_ZOOMED = 40; // 未放大:划够这么多才切
const SWIPE_ZOOMED = 80; // 放大后:更钝一点,避免和拖动打架
const DIRECTION_RATIO = 1.2; // |dx| 要明显大于 |dy| 才算横划
let downX = 0;
let downY = 0;
let downIndex = null;
let zoomed = false;
let qualified = false;
const inCanvas = (el) => el instanceof Element && !!el.closest('.viewer-canvas');
const isZoomed = () => {
const im = document.querySelector('.viewer-canvas img');
const c = document.querySelector('.viewer-canvas');
if (!im || !c) return false;
return im.getBoundingClientRect().width > c.getBoundingClientRect().width + 2;
};
document.addEventListener('pointerdown', (e) => {
if (!inCanvas(e.target)) return;
downX = e.clientX;
downY = e.clientY;
downIndex = viewer.index;
zoomed = isZoomed();
qualified = false;
}, true);
document.addEventListener('pointermove', (e) => {
if (downIndex === null) return;
const dx = e.clientX - downX;
const dy = e.clientY - downY;
const need = zoomed ? SWIPE_ZOOMED : SWIPE_NOT_ZOOMED;
qualified = Math.abs(dx) >= need && Math.abs(dx) > Math.abs(dy) * DIRECTION_RATIO;
}, true);
document.addEventListener('pointerup', (e) => {
if (downIndex === null) return;
const from = downIndex;
const dx = e.clientX - downX;
downIndex = null;
if (qualified) {
// 放大时库不会自己切(它判成拖动),由这里补一次
if (zoomed) viewer.view(viewer.index + (dx < 0 ? 1 : -1));
return;
}
// 不够格:库可能已经"跟手切过去"了,把它弹回原位
if (!zoomed && viewer.index !== from) viewer.view(from);
}, true);
document.addEventListener('pointercancel', () => { downIndex = null; }, true);
})();
/* 双击放大:库里的「模拟双击」(handlers.js:424-451)只看两次抬手的间隔(硬编码 500ms),
* 完全不检查中间有没有拖动 ⇒ 连续拖动两下也会被当成双击放大。这里在外面收口(不改库):
* 1. 抬手时用「起点→终点」的位移判断这一下是轻点还是拖动。阈值 30px 的依据是实测: