From 56c1320e406c9e61fe61c37eea56a66e5f1155e8 Mon Sep 17 00:00:00 2001 From: DevOps Date: Wed, 24 Dec 2025 01:13:55 +0800 Subject: [PATCH] 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 --- index.html | 140 +++++++++++++++++++++++----- pages/modify-score/modify-score.vue | 101 +++++++++++++------- 2 files changed, 185 insertions(+), 56 deletions(-) diff --git a/index.html b/index.html index d092c50..ed26777 100644 --- a/index.html +++ b/index.html @@ -4,61 +4,159 @@ + + + 武术评分系统 diff --git a/pages/modify-score/modify-score.vue b/pages/modify-score/modify-score.vue index 81c2aa0..a4f32b9 100644 --- a/pages/modify-score/modify-score.vue +++ b/pages/modify-score/modify-score.vue @@ -52,8 +52,11 @@ -0.001 @@ -66,8 +69,11 @@ +0.001 @@ -123,8 +129,7 @@ export default { note: '', minScore: 5.0, maxScore: 10.0, - lastTouchTime: 0, - touchDebounceDelay: 100 + isProcessing: false } }, @@ -209,42 +214,43 @@ export default { uni.navigateBack() }, - handleTouchStart(e) { - // 阻止默认行为,防止触发双击缩放 - e.preventDefault() - e.stopPropagation() + // 空操作函数,用于阻止事件 + noop() { + // 什么都不做 }, - handleDecreaseTouch(e) { - // 阻止默认行为和事件冒泡 - e.preventDefault() - e.stopPropagation() - - // 防抖处理 - const now = Date.now() - if (now - this.lastTouchTime < this.touchDebounceDelay) { + handleDecrease(e) { + // 防止重复处理 + if (this.isProcessing) { return } - this.lastTouchTime = now + + this.isProcessing = true // 执行减分逻辑 this.decreaseScore() + + // 使用 requestAnimationFrame 确保在下一帧重置状态 + requestAnimationFrame(() => { + this.isProcessing = false + }) }, - handleIncreaseTouch(e) { - // 阻止默认行为和事件冒泡 - e.preventDefault() - e.stopPropagation() - - // 防抖处理 - const now = Date.now() - if (now - this.lastTouchTime < this.touchDebounceDelay) { + handleIncrease(e) { + // 防止重复处理 + if (this.isProcessing) { return } - this.lastTouchTime = now + + this.isProcessing = true // 执行加分逻辑 this.increaseScore() + + // 使用 requestAnimationFrame 确保在下一帧重置状态 + requestAnimationFrame(() => { + this.isProcessing = false + }) }, decreaseScore() { @@ -507,11 +513,19 @@ export default { } .control-btn { - touch-action: none; - -webkit-tap-highlight-color: transparent; - user-select: none; - -webkit-user-select: none; - -webkit-touch-callout: none; + /* 关键:完全禁用所有触摸行为 */ + touch-action: none !important; + -webkit-tap-highlight-color: transparent !important; + -webkit-touch-callout: none !important; + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; + /* 防止长按菜单 */ + -webkit-touch-callout: none !important; + /* 防止文本选择 */ + pointer-events: auto !important; + width: 140rpx; height: 140rpx; display: flex; @@ -521,6 +535,19 @@ export default { background-color: #F5F5F5; border-radius: 12rpx; cursor: pointer; + position: relative; + overflow: hidden; +} + +.control-btn::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: transparent; + z-index: 1; } .control-btn.decrease { @@ -534,7 +561,9 @@ export default { .btn-symbol { font-size: 48rpx; font-weight: 300; - pointer-events: none; + pointer-events: none !important; + user-select: none !important; + -webkit-user-select: none !important; } .control-btn.decrease .btn-symbol { @@ -548,7 +577,9 @@ export default { .btn-value { font-size: 24rpx; margin-top: 8rpx; - pointer-events: none; + pointer-events: none !important; + user-select: none !important; + -webkit-user-select: none !important; } .control-btn.decrease .btn-value {