Fix iOS Safari double-tap zoom issue with comprehensive solution
Implemented multiple layers of protection to prevent iOS Safari from zooming when users quickly tap the score adjustment buttons:
1. Enhanced touch event handling in modify-score.vue:
- Changed from touchend to touchstart for immediate response
- Added .stop.prevent modifiers to all touch events (touchstart, touchmove, touchend, touchcancel)
- Added noop() handlers to absorb unwanted events
- Replaced time-based debouncing with isProcessing flag using requestAnimationFrame
- Ensured all child elements have pointer-events: none
2. Comprehensive index.html protection:
- Added iOS-specific meta tags (apple-mobile-web-app-capable, format-detection)
- Enhanced CSS with touch-action: pan-y for scrolling while preventing zoom
- Implemented 7-layer JavaScript protection:
* Layer 1: Intercept rapid touchstart events with counter
* Layer 2: Block touchend events within 300ms
* Layer 3: Completely disable dblclick events
* Layer 4: Prevent gesture events (gesturestart/change/end)
* Layer 5: Use Pointer Events API for additional blocking
* Layer 6: Filter rapid click events
* Layer 7: Add capture-phase listeners to buttons
- All event listeners use { passive: false, capture: true } for maximum control
This multi-layered approach addresses the root cause: iOS Safari triggers zoom at the browser level before JavaScript can normally intercept it. By using capture phase and preventing events at multiple stages, we ensure the zoom behavior is blocked.
Generated with Claude Code
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
140
index.html
140
index.html
@@ -4,61 +4,159 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<title>武术评分系统</title>
|
||||
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
|
||||
<style>
|
||||
* {
|
||||
touch-action: manipulation;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/* 针对按钮元素禁用所有触摸动作 */
|
||||
button, .control-btn, [class*="btn"] {
|
||||
touch-action: none !important;
|
||||
touch-action: pan-y !important;
|
||||
-webkit-touch-callout: none !important;
|
||||
-webkit-tap-highlight-color: transparent !important;
|
||||
-webkit-user-select: none !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
|
||||
/* 针对按钮元素完全禁用触摸动作 */
|
||||
button, .control-btn, [class*="btn"], [class*="control"] {
|
||||
touch-action: none !important;
|
||||
-webkit-user-select: none !important;
|
||||
user-select: none !important;
|
||||
-webkit-touch-callout: none !important;
|
||||
}
|
||||
|
||||
/* 允许输入框正常交互 */
|
||||
input, textarea {
|
||||
touch-action: manipulation !important;
|
||||
-webkit-user-select: text !important;
|
||||
user-select: text !important;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// 更强力的 iOS Safari 双击缩放禁用方案
|
||||
// 终极 iOS Safari 双击缩放禁用方案
|
||||
(function() {
|
||||
var lastTouchEnd = 0;
|
||||
var lastTouchTarget = null;
|
||||
'use strict';
|
||||
|
||||
// 阻止快速连续触摸导致的缩放
|
||||
var lastTouchEnd = 0;
|
||||
var lastTouchStart = 0;
|
||||
var touchCount = 0;
|
||||
var resetTimer = null;
|
||||
|
||||
// 方案1: 拦截所有快速连续的触摸事件
|
||||
document.addEventListener('touchstart', function(event) {
|
||||
var now = Date.now();
|
||||
|
||||
// 重置计数器
|
||||
if (resetTimer) {
|
||||
clearTimeout(resetTimer);
|
||||
}
|
||||
|
||||
// 如果距离上次触摸结束小于300ms,增加计数
|
||||
if (now - lastTouchEnd < 300) {
|
||||
touchCount++;
|
||||
|
||||
// 如果是第二次或更多次快速触摸,阻止默认行为
|
||||
if (touchCount >= 1) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
} else {
|
||||
touchCount = 0;
|
||||
}
|
||||
|
||||
lastTouchStart = now;
|
||||
|
||||
// 500ms后重置计数器
|
||||
resetTimer = setTimeout(function() {
|
||||
touchCount = 0;
|
||||
}, 500);
|
||||
}, { passive: false, capture: true });
|
||||
|
||||
// 方案2: 拦截touchend事件
|
||||
document.addEventListener('touchend', function(event) {
|
||||
var now = Date.now();
|
||||
var timeSinceLastTouch = now - lastTouchEnd;
|
||||
|
||||
// 如果两次触摸间隔小于 300ms,阻止默认行为
|
||||
// 如果两次触摸间隔小于300ms,阻止默认行为
|
||||
if (timeSinceLastTouch <= 300 && timeSinceLastTouch > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
lastTouchEnd = now;
|
||||
lastTouchTarget = event.target;
|
||||
}, { passive: false });
|
||||
}, { passive: false, capture: true });
|
||||
|
||||
// 阻止双击事件
|
||||
// 方案3: 完全禁用双击事件
|
||||
document.addEventListener('dblclick', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}, { passive: false });
|
||||
event.stopImmediatePropagation();
|
||||
return false;
|
||||
}, { passive: false, capture: true });
|
||||
|
||||
// 阻止手势缩放
|
||||
// 方案4: 禁用手势缩放
|
||||
document.addEventListener('gesturestart', function(event) {
|
||||
event.preventDefault();
|
||||
}, { passive: false });
|
||||
event.stopPropagation();
|
||||
}, { passive: false, capture: true });
|
||||
|
||||
document.addEventListener('gesturechange', function(event) {
|
||||
event.preventDefault();
|
||||
}, { passive: false });
|
||||
event.stopPropagation();
|
||||
}, { passive: false, capture: true });
|
||||
|
||||
document.addEventListener('gestureend', function(event) {
|
||||
event.preventDefault();
|
||||
}, { passive: false });
|
||||
event.stopPropagation();
|
||||
}, { passive: false, capture: true });
|
||||
|
||||
// 方案5: 使用 Pointer Events 拦截
|
||||
if (window.PointerEvent) {
|
||||
var lastPointerUp = 0;
|
||||
|
||||
document.addEventListener('pointerup', function(event) {
|
||||
var now = Date.now();
|
||||
if (now - lastPointerUp < 300) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
lastPointerUp = now;
|
||||
}, { passive: false, capture: true });
|
||||
}
|
||||
|
||||
// 方案6: 监听 click 事件,过滤掉快速连续的点击
|
||||
var lastClickTime = 0;
|
||||
document.addEventListener('click', function(event) {
|
||||
var now = Date.now();
|
||||
var timeSinceLastClick = now - lastClickTime;
|
||||
|
||||
// 如果距离上次点击小于300ms,可能是双击导致的,阻止
|
||||
if (timeSinceLastClick < 300 && timeSinceLastClick > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
lastClickTime = now;
|
||||
}, { passive: false, capture: true });
|
||||
|
||||
// 方案7: 禁用特定元素的默认行为
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 为所有按钮添加额外的事件监听
|
||||
var buttons = document.querySelectorAll('button, .control-btn, [class*="btn"]');
|
||||
buttons.forEach(function(btn) {
|
||||
btn.addEventListener('touchstart', function(e) {
|
||||
e.stopPropagation();
|
||||
}, { passive: false, capture: true });
|
||||
});
|
||||
});
|
||||
|
||||
console.log('iOS Safari 双击缩放防护已启用');
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
Reference in New Issue
Block a user