Files
Genarrative/src/mobileViewportZoomLock.ts
T
kdletters 071faa482c 统一 Rust 与 TypeScript 格式化门禁
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口

完成项目 TypeScript/Prettier 与 Rust 全量格式化

修复 Pingora expected executable 门禁的空白敏感误报

同步开发运维文档与 AGC skill pack 格式化忽略规则
2026-09-01 16:28:34 +08:00

75 lines
1.9 KiB
TypeScript

const MOBILE_POINTER_QUERY = '(pointer: coarse)';
const QUICK_TAP_INTERVAL_MS = 300;
function shouldLockMobileViewportZoom() {
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
return false;
}
return (
navigator.maxTouchPoints > 1 ||
window.matchMedia(MOBILE_POINTER_QUERY).matches
);
}
/**
* 中文注释:主站在移动端采用游戏式固定画布,不能让浏览器把整页缩放。
* 单指滚动、点击仍交给页面自身处理,只拦截多指捏合和快速双击触发的页面级缩放。
*/
export function lockMobileViewportZoom() {
if (
typeof document === 'undefined' ||
!shouldLockMobileViewportZoom() ||
document.documentElement.dataset.mobileViewportZoomLocked === 'true'
) {
return;
}
document.documentElement.dataset.mobileViewportZoomLocked = 'true';
const nonPassiveOptions: AddEventListenerOptions = { passive: false };
let lastTouchEndAt = 0;
const preventMultiTouchZoom = (event: TouchEvent) => {
if (event.touches.length > 1) {
event.preventDefault();
}
};
const preventBrowserGestureZoom = (event: Event) => {
event.preventDefault();
};
const preventQuickTapZoom = (event: TouchEvent) => {
const now = Date.now();
if (now - lastTouchEndAt < QUICK_TAP_INTERVAL_MS) {
event.preventDefault();
}
lastTouchEndAt = now;
};
document.addEventListener(
'touchmove',
preventMultiTouchZoom,
nonPassiveOptions,
);
document.addEventListener('touchend', preventQuickTapZoom, nonPassiveOptions);
document.addEventListener(
'gesturestart',
preventBrowserGestureZoom,
nonPassiveOptions,
);
document.addEventListener(
'gesturechange',
preventBrowserGestureZoom,
nonPassiveOptions,
);
document.addEventListener(
'gestureend',
preventBrowserGestureZoom,
nonPassiveOptions,
);
}