摘要:在混合开发中,软键盘弹出时的界面适配一直是个老大难问题。本文记录了在 HarmonyOS Next 上,如何解决 WebView 中底部输入框被软键盘遮挡、页面布局剧烈抖动以及收起键盘后页面留白等一系列"连环坑"。

🤕 1. 问题现场

在我们的 ToDo List 模块中,底部有一个固定的输入栏用于添加新任务。

.input-bar {
    position: fixed;
    bottom: 0;
    width: 100%;
}

当用户点击输入框尝试输入时,发生了诡异的现象:

  1. 遮挡:软键盘弹起,直接覆盖了输入栏,用户完全盲打。
  2. 抖动:在某些机型上,页面整体被顶起,顶部的导航栏被挤出了屏幕可视区域。
  3. 留白:收起键盘后,Webview 底部有时候会留下一块软键盘高度的空白区域,几秒后才恢复。

🔍 2. 机制探究

要解决这个问题,首先得理解 Android/鸿蒙系统对软键盘的两种处理模式:

  1. Resize 模式:软键盘弹起时,应用窗口高度减小。这会触发 Web 的 window.resize 事件,fixed 到底部的元素会随之跳动。
  2. Pan 模式:应用窗口高度不变,整体内容向上平移。这能保证焦点可见,但会把顶部导航栏顶出去。

在鸿蒙 ArkUI 中,默认的行为似乎介于两者之间,且 Web 组件内部还有自己的偏移逻辑。

ArkUI Resize
CSS Reflow
ArkUI Pan
Navbar Hidden
用户点击输入框
键盘弹出
Webview高度变小
布局错乱/抖动
Webview整体上移
顶部导航消失

🛠️ 3. 组合拳解决方案

单纯靠前端 CSS 或单纯靠原生配置都无法完美解决,我们需要一套"组合拳"。

3.1 第一拳:原生配置 (ArkTS)

首先,我们在 module.json5EntryAbility 中,需要明确指定窗口的软键盘避让模式。

EntryAbility.etsonWindowStageCreate 中:

import window from '@ohos.window';

onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.getMainWindow().then((win) => {
        // 设置键盘避让模式为 Resize,让 Webview 感知高度变化
        win.getUIContext().setKeyboardAvoidMode(window.KeyboardAvoidMode.RESIZE);
        console.info('Succeeded in setting keyboard avoid mode.');
    });
}

注:虽然 Pan 模式更简单,但它会导致顶部 TitleBar 消失,对于沉浸式应用体验不佳,所以我们选择 Resize。

3.2 第二拳:Web 前端视口适配

在 Resize 模式下,position: fixed; bottom: 0 的元素会随着视口变小而自动跳到键盘上方。这本来是好事,但在 iOS 和部分 Android WebView 上,键盘弹起动画和 Web 重绘不同步,导致输入栏"闪烁"或"瞬移"。

我们采用 Flex 布局 + 中间滚动区域 的方案来替代 fixed 定位:

/* 优化前:Body 滚动,输入栏 Fixed */
/* 优化后:Body 禁止滚动,主容器 Flex */
html, body {
    height: 100%;
    overflow: hidden; /* 关键 */
}

.app-container {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.header {
    flex: 0 0 50px;
}

.scroll-content {
    flex: 1;
    overflow-y: auto; /* 内容区域独立滚动 */
    -webkit-overflow-scrolling: touch;
}

.input-bar {
    flex: 0 0 60px;
    /* 不再使用 position: fixed */
}

3.3 第三拳:JS 焦点监听与补偿

为了防止输入框在极少数情况下依然被遮挡(例如第三方输入法自带工具栏),我们添加一个 JS 监听器,在键盘弹出后强制将输入框滚动到可视区域。

const input = document.getElementById('taskInput');

// 监听 Focus 而不是 Click,适配 Tab 键切换焦点
input.addEventListener('focus', () => {
    // 延迟 300ms 等待键盘动画完成
    setTimeout(() => {
        input.scrollIntoView({
            behavior: 'smooth',
            block: 'center'
        });
    }, 300);
});

// 监听 Window Resize 确保布局归位
window.addEventListener('resize', () => {
    if (document.activeElement.tagName === 'INPUT') {
        document.activeElement.scrollIntoViewIfNeeded();
    }
});

🐛 4. 遇到的深坑:虚拟导航栏

在部分没有实体按键的鸿蒙手机上,底部还有一层"虚拟导航栏"(手势提示条)。

当软键盘收起时,window.innerHeight 发生变化,但有时候 Webkit 内核计算有误,认为键盘还在,导致底部留白。

修复代码

// 监听软键盘收起事件(通过 resize 变大来判断)
let lastHeight = window.innerHeight;

window.addEventListener('resize', () => {
    const currentHeight = window.innerHeight;
    
    // 此时视口变大了,说明键盘收起了
    if (currentHeight > lastHeight) {
        // 强制触发一次重排,修复留白
        document.body.style.height = currentHeight + 1 + 'px';
        setTimeout(() => {
            document.body.style.height = '100%';
        }, 50);
    }
    lastHeight = currentHeight;
});

✅ 5. 最终效果

经过上述改造:

  1. 稳定性:输入栏紧贴软键盘上方,无跳动。
  2. 完整性:顶部导航栏始终可见,不会被顶出屏幕。
  3. 兼容性:适配了华为自带输入法、搜狗输入法等多种高度不一的键盘。

📚 6. 总结

软键盘适配核心在于 “控制权” 的争夺。

  • 如果完全交给系统(Pan 模式),Web 就失去了布局控制权,只能被动位移。
  • 我们选择 Resize 模式,虽然需要写更多 CSS/JS 来适配高度变化,但能换来最精确的 UI 控制和最接近原生的体验。

提示:在 manifest.json 中配置 softInputMode 在鸿蒙侧可能不生效,建议优先使用 ArkTS API 进行控制。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐