视口和软键盘对视口的影响

视口(Viewport)是移动 Web 开发中一个非常重要的概念,最早是由苹果公司在推出 iPhone 手机时发明的,其目的是为了让 iPhone 的小屏幕尽可能完整显示整个网页。通过设置视口,不管网页原始的分辨率尺寸有多大,都能将其缩小显示在手机浏览器上,这样保证网页在手机上看起来更像在桌面浏览器中的样子。在苹果公司引入视口的概念后,大多数的移动开发者也都认同了这个做法。

视口(Viewport)简单来说就是浏览器显示页面内容的屏幕区域。但在移动端浏览器涉及三个视口(Viewport)概念 :布局视口(Layout Viewport)、视觉视口(Visual Viewport)和理想视口(Ideal Viewport)。

布局视口、视觉视口、理想视口的区别

布局视口(Layout Viewport)

布局视口(Layout Viewport)是指浏览器在其中绘制网页的视口,它涵盖页面上的所有元素。布局视口的宽度取决于文档内容,不会随用户在屏幕上缩放页面而变化。

获取布局视口:

document.documentElement.clientWidth | document.body.clientWidth

注意: 一般移动端浏览器都默认设置了布局视口的宽度。根据设备的不同,布局视口的默认宽度有可能是 768px、980px 或 1024px等,这个宽度并不适合在手机屏幕中展示。移动端浏览器之所以采用这样的默认设置,是为了解决早期的 PC 端页面在手机上显示的问题。

视觉视口(Visual Viewport)

视觉视口(Visual Viewport)是指是屏幕上实际可见的内容,不包括屏幕键盘、缩放区域之外的区域或任何其他不随页面尺寸缩放的屏幕控件。视觉视口区域的宽度是指浏览器窗口的宽度,而移动设备的浏览器窗口宽度一般也是整个屏幕的宽度。

获取视觉视口:

window.innerWidth

注意: 当用户缩放页面,或者屏幕键盘等用户界面功能开启/关闭时,视觉视口会变化,但布局视口不会改变。

理想视口(Ideal Viewport)

理想视口(Ideal Viewport)是指对设备来讲最理想的视口尺寸,即布局视口和屏幕宽度一致。采用理想视口的方式,可以使网页在移动端浏览器上获得最理想的浏览和阅读的宽度。

设置理想视口:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, minimum-scale=1, user-scalable=no">

获取理想视口:

window.screen.width

软键盘显示模式

软键盘的本质是什么?软键盘其实是一个 Dialog。

InputMethodService 为输入法创建了一个 Dialog,并且将该 Dialog 的 某些参数进行了设置,使之能够在底部或者全屏显示。当点击输入框时,系统对活动主窗口进行调整,从而为输入法腾出相应的空间,然后将该 Dialog 显示在底部,或者全屏显示。

Android 定义了一个属性,名字为 windowSoftInputMode,这个属性用于设置 Activity 主窗口与软键盘的交互模式,用于避免软键盘遮挡内容的问题。可以在 AndroidManifet.xml 中对 Activity 进行设置。如:android:windowSoftInputMode=”stateUnchanged|adjustPan”。该属性可选的值有两部分:一部分为软键盘的状态控制,控制软键盘是隐藏还是显示;另一部分是 Activity 窗口的调整,以便腾出空间展示软键盘。

android:windowSoftInputMode 的属性设置必须是下面中的一个值,或一个 ”state” 值加一个 ”adjust” 值的组合,各个值之间用 | 分开:

  • stateUnspecified:未指定状态。 软件默认采用的就是这种交互方式,系统会根据界面采取相应的软键盘的显示模式。

  • stateUnchanged:不改变状态。 当前界面的软键盘状态,取决于上一个界面的软键盘状态,无论是隐藏还是显示。

  • stateHidden:隐藏状态。 软键盘总是被隐藏,不管是否有输入的需求。

  • stateAlwaysHidden:总是隐藏状态。 和 stateHidden 不同的是,当跳转到下个界面,如果下个页面的软键盘是显示的,而我们再次回来的时候,软键盘就会隐藏起来。

  • stateVisible:可见状态。 软键盘总是可见的,即使在界面上没有输入框的情况下也可以强制弹出来。

  • stateAlwaysVisible:总是显示状态。 和 stateVisible 不同的是,当我们跳转到下个界面,如果下个页面软键盘是隐藏的,而我们再次回来的时候,软键盘就会显示出来。

  • adjustUnspecified:未指定模式。 设置软键盘与软件的显示内容之间的显示关系。这个选项也是默认的设置模式。在这中情况下,系统会根据界面选择不同的模式。

  • adjustResize:调整模式。 该模式下,窗口总是调整屏幕的大小用以保证软键盘的显示空间。

  • adjustPan:默认模式。 该模式下,不会调整屏幕大小来保证软键盘的空间,而是采取了另外一种策略,系统会通过布局的移动,来保证用户要进行输入的输入框肯定在用户的视野范围里面,从而让用户可以看到自己输入的内容。

iOS 的软键盘模式

iOS 默认使用 adjustPan 模式。键盘弹出时,visual viewport 尺寸有变化,但 layout viewport 尺寸不变,部分顶部的元素可能被遮挡或挤出可视区域。

在这个自动调整中,layout viewport 尺寸未变,但可能引发 doucment、layout viewport、visual viewport 两两间相对位置的变化。如果 viewport meta 中未指定 user-scalable=no,还会引发 zoom。另外实测显式,软键盘收起时,(由于软键盘弹出)引发的 zoom、滚动变化并不会还原,而仅仅是 visual viewport 尺寸还原。

Android 的软键盘模式

Andriod 默认使用 adjustResize 模式(受版本或其他因素影响,也有可能未指定模式)。键盘弹出时,layout viewport 与 visual viewport 同步缩小。

该模式仍可能引发 document 滚动以便使 focus input 出现在「可见且显眼」的地方。

视口尺寸变更

visual viewport 在如下场景中会变更尺寸:

  • 用户缩放及移动(pan)视图。

  • 设备 orientation 变更。

  • 浏览器工具栏、状态栏等自动隐藏/出现。

  • 设备软键盘弹出/隐藏。

注意: visual viewport 尺寸变更时会触发 visualViewport resize 事件。

layout viewport 在页面初始渲染确定大小后,仅在如下场景下可能改变:

  • 设备 orientation 变更。

  • 浏览器工具栏、状态栏等自动隐藏/出现。

  • 设备软键盘弹出/隐藏。

其中前两个场景,可以视作 visual viewport 尺寸变更后,浏览器为了适配「在缩放比1.0,左上角完全贴合时,layout viewport 能与 visual viewport 完全重合」而做的「让展现符合布局预期」的工作。而软键盘弹出场景,调整策略却依不同浏览器 / WebView 有不同行为。

注意: iOS WebView 中只有 layout viewport 尺寸变化才触发 window resize 事件。

检测软键盘弹出和收起

Web 并无标准 API 检测软键盘是否弹出。除非原生 WebView 增加 bridge API。

目前可靠且兼容性较好的方式是 iOS 中监听 visualViewport resize 事件,而(不支持 visualViewport API 的)Android 中监听 window 的 resize 事件(必需指定相应的软键盘显示模式,否则 viewport 尺寸不会变化)。

注意: 在苹果手机上,当软键盘收起时输入框会失去焦点。因此,也可以在苹果手机上可以通过输入框失去和得到焦点事件来判断。

注意: Android 软键盘快速工具栏右侧的「收起」按钮,点击后会收起软键盘,但 input 标签却并不失焦,此时 blur 事件未触发(不能以此为据获知软键盘收起)。

const ua = window.navigator.userAgent;
const isIOS = /iPhone|iPod|iPad/i.test(ua)
const isAndroid = /Android/i.test(ua)

// iOS端:需要排查 input、textarea 之外的标签(如:radio、checkbox)触发 focusin、focusout
window.addEventListener('focusin', () => {
	console.log('键盘弹出')
});
window.addEventListener('focusout', () => {
	console.log('键盘收起')
})

// Android端:横屏功能也会触发 resize(可通过宽度是否改变判断)
const innerHeight = window.innerHeight
window.addEventListener('resize', () => {
  const newInnerHeight = window.innerHeight;
  if (innerHeight > newInnerHeight) {
		console.log('键盘弹出');     
  } else {
		console.log('键盘收起');
  }
})

相关问题

视口单位(Viewport units)

视口: PC 端指的是浏览器的可视区域;移动端指的就是 Viewport 中的 Layout Viewport。

根据 CSS3 规范,视口单位主要包括以下4个:

  • vw:1vw 等于视口宽度的1%。

  • vh:1vh 等于视口高度的1%。

  • vmin:选取 vw 和 vh 中最小的那个。

  • vmax:选取 vw 和 vh 中最大的那个。

vw 和 vh 是相对于视口的高度和宽度,而不是父元素的(CSS 百分比是相对于包含它的最近的父元素的高度和宽度)。1vh 等于1/100的视口高度,1vw 等于1/100的视口宽度。

vmax 相对于视口的宽度或高度中较大的那个,其中最大的那个被均分为 100 单位的 vmax。

vmin 相对于视口的宽度或高度中较小的那个,其中最小的那个被均分为 100 单位的 vmin。

参考资料

jianshu - 关于Android中的软键盘

viewport 与软键盘对布局的影响

最后更新于