|
|
@@ -42,7 +42,7 @@ const installViewportHeightFix = () => {
|
|
|
};
|
|
|
|
|
|
// 强制锁定视口高度(立即执行,无防抖)
|
|
|
- const forceLockViewportHeight = () => {
|
|
|
+ const forceLockViewportHeight = (reason = 'unknown') => {
|
|
|
if (initialHeight === null) {
|
|
|
// 使用 window.innerHeight 作为初始高度(更稳定)
|
|
|
initialHeight = window.innerHeight;
|
|
|
@@ -50,6 +50,7 @@ const installViewportHeightFix = () => {
|
|
|
isLocked = true;
|
|
|
const vh = initialHeight * 0.01;
|
|
|
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
|
|
+ console.log(`[ViewportFix] 🔒 锁定视口高度: initialHeight=${initialHeight}px, --vh=${vh}px, reason=${reason}, isInputFocused=${isInputFocused}`);
|
|
|
};
|
|
|
|
|
|
// 设置视口高度(带防抖)
|
|
|
@@ -57,7 +58,15 @@ const installViewportHeightFix = () => {
|
|
|
const updateHeight = () => {
|
|
|
// 如果已锁定,完全不更新(防止键盘弹出时界面缩小)
|
|
|
if (isLocked && initialHeight !== null) {
|
|
|
- forceLockViewportHeight();
|
|
|
+ console.log(`[ViewportFix] 🔧 setViewportHeight: 已锁定,跳过更新, initialHeight=${initialHeight}px`);
|
|
|
+ forceLockViewportHeight('setViewportHeight-locked');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果输入框处于焦点状态,即使未锁定也应该锁定(防止键盘弹出时界面缩小)
|
|
|
+ if (isInputFocused && initialHeight !== null) {
|
|
|
+ console.log(`[ViewportFix] 🔧 setViewportHeight: 输入框聚焦中,强制锁定, initialHeight=${initialHeight}px`);
|
|
|
+ forceLockViewportHeight('setViewportHeight-input-focused');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -67,23 +76,35 @@ const installViewportHeightFix = () => {
|
|
|
// 检测键盘是否弹出(高度突然大幅减小,通常是键盘弹出)
|
|
|
if (!skipKeyboardCheck && initialHeight !== null) {
|
|
|
const heightDiff = initialHeight - currentHeight;
|
|
|
- // 降低阈值到 100px,覆盖更多设备
|
|
|
- if (heightDiff > 100) {
|
|
|
+ // 降低阈值到 50px,更早检测到键盘弹出(特别是 inputMode="numeric" 等快速键盘)
|
|
|
+ if (heightDiff > 50) {
|
|
|
// 键盘弹出,立即锁定
|
|
|
- forceLockViewportHeight();
|
|
|
+ console.log(`[ViewportFix] 🔧 setViewportHeight: 检测到键盘弹出(heightDiff>50), 立即锁定`);
|
|
|
+ forceLockViewportHeight('setViewportHeight-keyboard-detected');
|
|
|
return;
|
|
|
} else if (heightDiff < -50) {
|
|
|
// 高度增加,可能是键盘收起
|
|
|
// 只有在输入框未聚焦时才解锁
|
|
|
if (!isInputFocused) {
|
|
|
+ console.log(`[ViewportFix] 🔧 setViewportHeight: 键盘收起,解锁, heightDiff=${heightDiff}px`);
|
|
|
isLocked = false;
|
|
|
- initialHeight = currentHeight;
|
|
|
+ // 关键修复:只有在高度明显增加时才更新 initialHeight(避免使用错误的高度值)
|
|
|
+ if (currentHeight > initialHeight + 50) {
|
|
|
+ console.log(`[ViewportFix] 🔧 setViewportHeight: 更新初始高度(键盘收起), old=${initialHeight}px, new=${currentHeight}px`);
|
|
|
+ initialHeight = currentHeight;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 记录初始高度(首次加载或键盘收起后)
|
|
|
- if (initialHeight === null || (!isLocked && !skipKeyboardCheck && !isInputFocused)) {
|
|
|
+ // 记录初始高度(仅在首次加载时,或明确要求更新时)
|
|
|
+ // 关键修复:不要在键盘收起时自动更新 initialHeight,避免使用错误的高度值
|
|
|
+ if (initialHeight === null) {
|
|
|
+ console.log(`[ViewportFix] 🔧 setViewportHeight: 记录初始高度, baseHeight=${baseHeight}px`);
|
|
|
+ initialHeight = baseHeight;
|
|
|
+ } else if (skipKeyboardCheck && !isInputFocused) {
|
|
|
+ // 只有在明确跳过键盘检查且输入框未聚焦时,才允许更新 initialHeight(用于横竖屏切换等场景)
|
|
|
+ console.log(`[ViewportFix] 🔧 setViewportHeight: 更新初始高度(明确要求), baseHeight=${baseHeight}px`);
|
|
|
initialHeight = baseHeight;
|
|
|
}
|
|
|
|
|
|
@@ -92,6 +113,7 @@ const installViewportHeightFix = () => {
|
|
|
const vh = height * 0.01;
|
|
|
// 全局 1vh 的真实像素值
|
|
|
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
|
|
+ console.log(`[ViewportFix] 🔧 setViewportHeight: 更新 --vh, height=${height}px, --vh=${vh}px, isLocked=${isLocked}, skipKeyboardCheck=${skipKeyboardCheck}`);
|
|
|
};
|
|
|
|
|
|
// 防抖处理(除非立即执行)
|
|
|
@@ -112,11 +134,12 @@ const installViewportHeightFix = () => {
|
|
|
}
|
|
|
isInputFocused = true;
|
|
|
// 立即锁定,不使用防抖
|
|
|
- forceLockViewportHeight();
|
|
|
+ forceLockViewportHeight('focusin');
|
|
|
};
|
|
|
|
|
|
// 解锁视口高度(输入框失去焦点时,延迟恢复)
|
|
|
const unlockViewportHeight = () => {
|
|
|
+ console.log(`[ViewportFix] 🔓 unlockViewportHeight: 输入框失去焦点, 延迟300ms后解锁, currentInitialHeight=${initialHeight}px`);
|
|
|
isInputFocused = false;
|
|
|
// 清除之前的恢复定时器
|
|
|
if (blurRecoveryTimer) clearTimeout(blurRecoveryTimer);
|
|
|
@@ -124,7 +147,15 @@ const installViewportHeightFix = () => {
|
|
|
blurRecoveryTimer = setTimeout(() => {
|
|
|
// 更新初始高度为当前高度(键盘已收起)
|
|
|
const currentHeight = getCurrentHeight();
|
|
|
- initialHeight = currentHeight;
|
|
|
+ const oldInitialHeight = initialHeight;
|
|
|
+ // 关键修复:只有在当前高度明显大于之前的初始高度时,才更新(说明键盘已收起)
|
|
|
+ // 避免在键盘弹出过程中错误更新 initialHeight
|
|
|
+ if (initialHeight !== null && currentHeight > initialHeight + 50) {
|
|
|
+ console.log(`[ViewportFix] 🔓 unlockViewportHeight: 键盘已收起,更新初始高度, old=${oldInitialHeight}px, new=${currentHeight}px`);
|
|
|
+ initialHeight = currentHeight;
|
|
|
+ } else {
|
|
|
+ console.log(`[ViewportFix] 🔓 unlockViewportHeight: 键盘可能未完全收起,保持初始高度, initialHeight=${initialHeight}px, currentHeight=${currentHeight}px`);
|
|
|
+ }
|
|
|
isLocked = false;
|
|
|
setViewportHeight(false, true);
|
|
|
}, 300);
|
|
|
@@ -133,23 +164,26 @@ const installViewportHeightFix = () => {
|
|
|
// 页面加载完成后立即记录初始高度
|
|
|
const initializeHeight = () => {
|
|
|
// 立即记录初始高度(使用 window.innerHeight,更稳定)
|
|
|
- const recordInitialHeight = () => {
|
|
|
+ const recordInitialHeight = (source: string) => {
|
|
|
if (initialHeight === null) {
|
|
|
initialHeight = window.innerHeight;
|
|
|
- setViewportHeight(false, true);
|
|
|
+ // 立即设置 --vh,确保在自动聚焦前就已完成初始化
|
|
|
+ const vh = initialHeight * 0.01;
|
|
|
+ document.documentElement.style.setProperty('--vh', `${vh}px`);
|
|
|
+ console.log(`[ViewportFix] 🚀 初始化高度: source=${source}, initialHeight=${initialHeight}px, --vh=${vh}px, readyState=${document.readyState}`);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 如果 DOM 已加载完成,立即初始化
|
|
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
|
- recordInitialHeight();
|
|
|
+ recordInitialHeight('readyState-complete/interactive');
|
|
|
} else {
|
|
|
// 立即执行一次(尽早记录)
|
|
|
- recordInitialHeight();
|
|
|
+ recordInitialHeight('immediate');
|
|
|
|
|
|
// 同时等待 DOM 加载完成,再次确认
|
|
|
const handleDOMReady = () => {
|
|
|
- recordInitialHeight();
|
|
|
+ recordInitialHeight('DOMContentLoaded');
|
|
|
document.removeEventListener('DOMContentLoaded', handleDOMReady);
|
|
|
};
|
|
|
document.addEventListener('DOMContentLoaded', handleDOMReady);
|
|
|
@@ -164,7 +198,9 @@ const installViewportHeightFix = () => {
|
|
|
requestAnimationFrame(() => {
|
|
|
if (initialHeight === null) {
|
|
|
initialHeight = window.innerHeight;
|
|
|
- setViewportHeight(false, true);
|
|
|
+ const vh = initialHeight * 0.01;
|
|
|
+ document.documentElement.style.setProperty('--vh', `${vh}px`);
|
|
|
+ console.log(`[ViewportFix] 🚀 初始化高度: source=requestAnimationFrame, initialHeight=${initialHeight}px, --vh=${vh}px`);
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
@@ -180,14 +216,35 @@ const installViewportHeightFix = () => {
|
|
|
};
|
|
|
|
|
|
// 监听输入框触摸开始事件(在 focusin 之前触发,更早锁定)
|
|
|
+ // 增强版:检查触摸目标及其父元素,确保能捕获到所有输入框场景
|
|
|
const handleTouchStart = (e: TouchEvent) => {
|
|
|
const target = e.target as HTMLElement;
|
|
|
+
|
|
|
+ // 检查触摸目标是否为输入框
|
|
|
if (isInputElement(target)) {
|
|
|
+ console.log(`[ViewportFix] 👆 touchstart: 检测到输入框, tagName=${target.tagName}, type=${(target as HTMLInputElement).type}, inputMode=${(target as HTMLInputElement).inputMode}, initialHeight=${initialHeight}, isLocked=${isLocked}`);
|
|
|
// 立即锁定,防止键盘弹出时界面缩小
|
|
|
if (initialHeight === null) {
|
|
|
initialHeight = window.innerHeight;
|
|
|
}
|
|
|
- forceLockViewportHeight();
|
|
|
+ forceLockViewportHeight('touchstart');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查触摸目标的父元素(某些情况下输入框可能被其他元素包裹)
|
|
|
+ let parent = target.parentElement;
|
|
|
+ let depth = 0;
|
|
|
+ while (parent && depth < 3) {
|
|
|
+ if (isInputElement(parent)) {
|
|
|
+ console.log(`[ViewportFix] 👆 touchstart: 在父元素(depth=${depth})检测到输入框, tagName=${parent.tagName}, initialHeight=${initialHeight}, isLocked=${isLocked}`);
|
|
|
+ if (initialHeight === null) {
|
|
|
+ initialHeight = window.innerHeight;
|
|
|
+ }
|
|
|
+ forceLockViewportHeight('touchstart-parent');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ parent = parent.parentElement;
|
|
|
+ depth++;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -195,6 +252,8 @@ const installViewportHeightFix = () => {
|
|
|
const handleFocusIn = (e: FocusEvent) => {
|
|
|
const target = e.target as HTMLElement;
|
|
|
if (isInputElement(target)) {
|
|
|
+ const input = target as HTMLInputElement;
|
|
|
+ console.log(`[ViewportFix] 🎯 focusin: 输入框获得焦点, tagName=${target.tagName}, type=${input.type}, inputMode=${input.inputMode}, initialHeight=${initialHeight}, isLocked=${isLocked}, currentHeight=${getCurrentHeight()}`);
|
|
|
lockViewportHeight();
|
|
|
}
|
|
|
};
|
|
|
@@ -217,40 +276,87 @@ const installViewportHeightFix = () => {
|
|
|
|
|
|
// 监听 visualViewport 变化(移动端浏览器地址栏显示/隐藏时会触发)
|
|
|
// 关键优化:在 visualViewport.resize 时,如果检测到高度大幅减小,立即锁定
|
|
|
+ // 增强版:更早检测键盘弹出,修复 inputMode="numeric" 等快速键盘场景
|
|
|
if (window.visualViewport) {
|
|
|
+ // 记录上一次的高度,用于检测高度变化趋势
|
|
|
+ let lastHeight: number | null = null;
|
|
|
+
|
|
|
window.visualViewport.addEventListener('resize', () => {
|
|
|
+ const currentHeight = getCurrentHeight();
|
|
|
+ const activeElement = document.activeElement as HTMLElement;
|
|
|
+ const isActiveInput = isInputElement(activeElement);
|
|
|
+
|
|
|
// 如果已锁定,完全不处理(防止任何更新)
|
|
|
if (isLocked && initialHeight !== null) {
|
|
|
- forceLockViewportHeight();
|
|
|
+ console.log(`[ViewportFix] 📐 visualViewport.resize: 已锁定,跳过处理, currentHeight=${currentHeight}px, initialHeight=${initialHeight}px`);
|
|
|
+ forceLockViewportHeight('already-locked');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- const currentHeight = getCurrentHeight();
|
|
|
+ // 如果 initialHeight 还未初始化,先记录初始高度
|
|
|
+ // 这是关键修复:防止在初始化完成前就触发键盘导致错误更新
|
|
|
+ if (initialHeight === null) {
|
|
|
+ initialHeight = window.innerHeight;
|
|
|
+ console.log(`[ViewportFix] 📐 visualViewport.resize: initialHeight未初始化,记录初始高度, window.innerHeight=${window.innerHeight}px, currentHeight=${currentHeight}px`);
|
|
|
+ // 如果当前高度明显小于 window.innerHeight,说明键盘可能已经弹出
|
|
|
+ // 此时应该使用 window.innerHeight 作为基准,并立即锁定
|
|
|
+ if (currentHeight < window.innerHeight - 50) {
|
|
|
+ console.log(`[ViewportFix] 📐 visualViewport.resize: 检测到键盘已弹出(初始化时), 立即锁定`);
|
|
|
+ forceLockViewportHeight('resize-init-keyboard-detected');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 否则正常设置高度
|
|
|
+ setViewportHeight(false, true);
|
|
|
+ lastHeight = currentHeight;
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
// 检查当前活跃元素是否为输入框(即使 focusin 还没触发)
|
|
|
- const activeElement = document.activeElement as HTMLElement;
|
|
|
- const isActiveInput = isInputElement(activeElement);
|
|
|
+ const heightDiff = initialHeight - currentHeight;
|
|
|
+
|
|
|
+ console.log(`[ViewportFix] 📐 visualViewport.resize: currentHeight=${currentHeight}px, initialHeight=${initialHeight}px, heightDiff=${heightDiff}px, isInputFocused=${isInputFocused}, isActiveInput=${isActiveInput}, activeElement=${activeElement?.tagName}, isLocked=${isLocked}`);
|
|
|
|
|
|
// 如果输入框处于焦点状态,或当前活跃元素是输入框,强制锁定
|
|
|
if (isInputFocused || isActiveInput) {
|
|
|
- if (initialHeight === null) {
|
|
|
- initialHeight = window.innerHeight;
|
|
|
- }
|
|
|
- forceLockViewportHeight();
|
|
|
+ console.log(`[ViewportFix] 📐 visualViewport.resize: 检测到输入框焦点,立即锁定`);
|
|
|
+ forceLockViewportHeight('resize-input-focused');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 检测键盘是否弹出(高度突然大幅减小)
|
|
|
- if (initialHeight !== null) {
|
|
|
- const heightDiff = initialHeight - currentHeight;
|
|
|
- // 如果高度减少超过 100px,认为是键盘弹出,立即锁定
|
|
|
- if (heightDiff > 100) {
|
|
|
- forceLockViewportHeight();
|
|
|
+ // 降低阈值到 50px,更早检测到键盘弹出(特别是 inputMode="numeric" 等快速键盘)
|
|
|
+
|
|
|
+ // 如果高度减小超过 50px,立即锁定(键盘弹出)
|
|
|
+ if (heightDiff > 50) {
|
|
|
+ console.log(`[ViewportFix] 📐 visualViewport.resize: 检测到键盘弹出(heightDiff>50), 立即锁定`);
|
|
|
+ forceLockViewportHeight('resize-height-diff-50');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果高度减小超过 20px 且当前高度明显小于初始高度,也可能是键盘弹出(更早检测)
|
|
|
+ // 这对于 inputMode="numeric" 等快速键盘特别重要
|
|
|
+ if (heightDiff > 20 && currentHeight < initialHeight * 0.9) {
|
|
|
+ console.log(`[ViewportFix] 📐 visualViewport.resize: 检测到键盘弹出(heightDiff>20且<90%), 立即锁定`);
|
|
|
+ forceLockViewportHeight('resize-height-diff-20-90');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检测高度快速下降趋势(即使单次变化不大,但连续下降也可能表示键盘弹出)
|
|
|
+ if (lastHeight !== null) {
|
|
|
+ const rapidHeightDrop = lastHeight - currentHeight;
|
|
|
+ // 如果高度在短时间内快速下降超过 30px,可能是键盘弹出
|
|
|
+ if (rapidHeightDrop > 30 && currentHeight < initialHeight - 30) {
|
|
|
+ console.log(`[ViewportFix] 📐 visualViewport.resize: 检测到快速下降趋势(rapidDrop>30), 立即锁定, lastHeight=${lastHeight}px, rapidDrop=${rapidHeightDrop}px`);
|
|
|
+ forceLockViewportHeight('resize-rapid-drop');
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 更新记录的高度
|
|
|
+ lastHeight = currentHeight;
|
|
|
+
|
|
|
// 其他情况正常更新(地址栏显示/隐藏等)
|
|
|
+ console.log(`[ViewportFix] 📐 visualViewport.resize: 正常更新高度, currentHeight=${currentHeight}px`);
|
|
|
setViewportHeight();
|
|
|
});
|
|
|
|