移动网页高度自适应最佳实践

移动 Web 开发就要在“螺蛳壳里做道场”。移动设备限于屏幕尺寸,不得已左支右绌,既要多呈现内容,又要保证功能不要缺失。普通内容类网页还好,自然往下滚就行了;开发 Web App 的时候,当我们因为某种原因,需要限制滚动区域的时候,就很难处理。

这篇文章会分享我的一些经验,希望能节省大家摸索的时间。

无用单位:vh, svh, dvh, lvh

很早以前,我们就有了 vwvh 单位,分别指代视窗宽高的 1%。需要注意的是,移动浏览器的视口包含被各种组件占据的部分,所以 vw 就比较好用,因为没有干扰;但是高度上,被通知栏、地址栏等占用的“临时”空间就会成为我们的麻烦。

为了妥善利用屏幕空间,在我们上下滚屏的时候,大多数手机浏览器都会把地址栏、工具栏、或者通知栏隐藏起来,这就导致浏览器的可视面积其实会不断变化。原本就没用的 vh 便更没用了……于是后面新增了 svhdvhlvh 三种 长度单位,但其实帮助不大,因为当我们需要限制容器高度的时候,通常来说就不能让页面自由滚动。

因为这几个长度单位过于没用,所以我就不详细介绍了。感兴趣的同学可以看下 TailwindCSS 里的演示:https://tailwindcss.com/docs/height#viewport-height

虚拟键盘则让这个问题雪上加霜,因为虚拟键盘的显示和隐藏都不会影响这几个长度单位,所以当我们需要手动控制容器高度、位置的时候,就会很难做。

最佳实践:常规页面,交给浏览器

首先我们要信任浏览器,能够留给浏览器处理的,尽量交给浏览器原生处理。

比如,常规页面,长一点,留给浏览器自然滚动。文本框输入的时候,浏览器会自动聚焦和滚动,通常情况下没什么问题,基本体验有保证。

最佳实践:输入框文字不小于 16px

如果文本框 font-size 小于 16px,iOS Safari 下,当文本框获得焦点,Safari 会自动放大整个页面;而失去焦点的时候,页面并不会自动缩小到 100%,所以就很蛋痛。

解决方案有几个:

  1. 取消缩放。会使得可用性评价恶化,不推荐。
  2. blur 时自动恢复 100%。增加特性就是增加 bug 的可能,我觉得能不用就不用。
  3. 保持字体大小。应该大部分时候都更简单有效。

最佳实践:使用 dvh 并解决兼容问题

虽然但是,当我们需要固定高度的时候,表示视窗净高度的 dvh 仍然是我们最佳选择。

不过,在我写文章的现在,dvh 的兼容性不是很好,所以必须做好兼容性配置。我建议用 JS 结合 CSS 变量来做。在 <head> 里插入这段 JS:

head.js
// 首先,判断是否支持 dvh 单位 if (!CSS.supports('height', '100dvh')) { // 如果不支持,就定义 --app-height 为视口高度,即 window.innerHeight document.body.style.setProperty('--app-height', window.innerHeight + 'px'); // 当屏幕缩放时,改变内容高度。因为 resize 事件触发很频繁,所以使用节流减少性能损耗 let timeout; function onResize() { clearTimeout(timeout); timeout = setTimeout(() => { clearTimeout(timeout); document.body.style.setProperty('--app-height', window.innerHeight + 'px'); }, 500); } window.addEventListener('resize', onResize); }

然后定义 CSS 样式:

main.css
:root { --app-height: 100dvh; } .h-dvh-app { height: var(--app-height); }

如果使用 TailwindCSS,那么在配置文件里增加配置即可:

tailwind.config.js
export default { theme: { extend: { spacing: { 'dvh-app': 'var(--app-height)', }, }, }, }

最佳实践:使用 CSS 变量解决虚拟键盘

只是限制高度为 100dvh,当虚拟键盘弹出之后,因为视口缩小,很可能会出现问题。此时 window.resize 事件也不会触发,所以我们应该侦听文本框的 focus 事件,动态改变容器高度;并在文本框 blur 之后,恢复高度。

此时,我们可以借助 CSS 变量的“默认值”功能,即 var(--custom-value, --default-value) 来处理。当我们需要暂时的高度以应对虚拟键盘时,设置 --custom-value;之后,移除 —custom-value,恢复到预定义的 --app-height

首先,修改 css,定义 --input-height: initial,这个值会被认为是空值而忽略。

main.css
:root { --input-height: initial; --app-height: 100dvh; } .h-dvh-app { height: var(--input-height, var(--app-height)); }

然后侦听输入框的 focusblur 事件:

input.js
async function onTextareaFocus(): Promise<void> { // 桌面端忽略这个需求 if (window.innerWidth > 640) return; // 给虚拟键盘弹出一些时间 await sleep(300); const { innerHeight } = window; document.body.style.setProperty('--input-height', `${innerHeight}px`); // 需要的话,可以在这里插入一个滚动 } async function onTextareaBlur(event: FocusEvent): Promise<void> { // 同样,也给虚拟键盘收起留一些时间 await sleep(250); document.body.style.removeProperty('--input-height'); }

总结

至此,遵守以上最佳实践之后,基本上我们可以妥善处理移动网页里的浏览器高度。当然,并不完美,比如,iOS Safari 在输入 position: sticky 里的文本框时,会凭空多出一大块空白,很烦,但是没办法解决。可以绕开,但是我觉得绕开的方案更难用。

希望这篇文章对大家有用。如果你对移动网页开发有什么问题,欢迎留言讨论。

如果您觉得文章内容对您有用,不妨支持我创作更多有价值的分享:


已发布

分类

来自

标签:

评论

《“移动网页高度自适应最佳实践”》 有 1 条评论

  1. Alex 的头像

    还有一种方案是nextjs的classname包含css,每次单独追踪起来容易一些,需要的话就标准件话,不需要每次手搓也没影响😄

欢迎吐槽,共同进步

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理