解决“输入框被虚拟键盘遮挡”问题的尝试

前言

“输入框被虚拟键盘遮挡” 的问题,不少同学都遇到过。尤其是对于页面尺寸固定,且输入框只能显示在下半部分,甚至边缘位置的情况,遮挡现象会成为阻挡用户获得确定性或安全感的一大障碍。

如果你想要简化问题处理,可以尝试与产品经理沟通,将输入框的呈现位置调整至页面的上半部分即可。但如果你并不想要绕过这个问题,想要和它磕一磕,就需要继续往下看,深入地了解这一问题。

对比

经对比,iPhone 上的输入框在键盘弹出时表现正常,会自动被顶到可见区域,不需要额外处理。需要处理的,是部分安卓手机。

通过对比不同手机的表现,发现键盘大致可以按两种形式弹出:“挤压窗口高度弹出” 和 “悬浮弹出”。

“挤压窗口高度弹出” 将缩小窗口的实际高度(window.innerHeight 变小,position fixed 在底部的元素会跟着键盘被顶上去)。而 “悬浮弹出”时,窗口将保持原高度,键盘以悬浮在窗口上方的形式展现给用户。

挤压窗口高度式弹出

对于 “挤压窗口高度” 式弹出虚拟键盘的手机,例如:红米6pro,除厂商自己实现的安全键盘外,问题并不突出,只需在检测到输入框获得焦点时,调用输入框的 scrollIntoViewIfNeeded() 即可解决遮挡问题;

悬浮式弹出

而对于 “悬浮” 式弹出虚拟键盘的手机,例如:华为荣耀magic2,则在问题的表现上要严重的多,需要着重对待。

如果开发者调用 scrollIntoViewIfNeeded() 时页面没有任何改观,大概率是页面尺寸固定,没有更多拖动空间的原因。

对于这个问题,我最初按这个思路上进行尝试:

捕获 [键盘打开] 及 [键盘关闭] 事件,根据 [键盘高度] 动态调整页面的 paddingBottom,然后再调用输入框的 scrollIntoViewIfNeeded() 方法

但翻阅各种资料,都找不到这两个事件或者等价的其它事件,也找不到任何 API 使得我可以捕获键盘高度,抑或是其它可以探测键盘高度的方法。

接着,改造思路:

  1. 借助 blur()focus() 事件,假定 键盘已关闭 或 键盘已打开
  2. 硬编码一个较大的数值,如:200px,代表猜想的虚拟键盘的高度
  3. 在键盘打开时,动态调整页面的 paddingBottom,使其等于猜想的键盘高度
  4. 调用输入框的 scrollInToViewIfNeeded()
  5. 当输入框焦点失去时,恢复页面的 paddingBottom

经过优化和调测,问题已大致解决。完整代码如下:

/**
 * 解决部分安卓手机上,虚拟键盘会遮挡输入框的问题
 */
;(function(){
	/**
	 * Win32 代表 windows 系统,说明当前是开发者工具调测环境,不需要处理
	 * MacIntel 代表 mac 系统,同样说明当前是开发者工具调测环境,不需要处理
	 * iPhone 上表现正常,不需要处理
	 */
	if(["Win32", "MacIntel", "iPhone"].indexOf(navigator.platform) !== -1)
		return;

	/**
	 * 文档正文是否加载完成
	 * @type {boolean}
	 */
	var isDomContentLoaded = false;

	/**
	 * 没有虚拟键盘时,页面可以展现的最大高度
	 * @type {Number}
	 */
	var maxAvailableHeight = null;

	/**
	 * 猜想的虚拟键盘的高度
	 * @type {number}
	 */
	var suspectedVirtualKeyboardHeight = 200;

	/**
	 * 检测结果:虚拟键盘是否以“悬浮在正文内容上”的形式展现
	 * @type {boolean|null}
	 */
	var ifVirtualKeyboardOverlapsContent = null;

	/**
	 * 焦点捕获/失去动作是否由内部触发,以重新捕获焦点
	 * @type {boolean}
	 */
	var isBlurredToReFocus = false,
		isFocusedToReOpenKeyboard = false;

	/**
	 * 虚拟键盘是否已经打开
	 * @type {boolean}
	 */
	var isVirtualKeyboardOpen = false;

	var bakedPaddingBottom = null;
	var docEle = document.documentElement;


	/**
	 * 判断检测环境是否就绪
	 */
	var isEnvironmentReady = function(){
		return isDomContentLoaded;
	};

	/**
	 * 刷新光标,重新捕获焦点,迫使可能关闭了的键盘重新出现,防止计算失误
	 * @param {Function} callback 重新聚焦后要执行的方法
	 */
	var reOpenKeyboardIfNeeded = function(callback){
		var focusedObj = document.querySelector(":focus");

		if(null == focusedObj){
			if(null === maxAvailableHeight)
				maxAvailableHeight = window.innerHeight;

			return;
		}

		isBlurredToReFocus = true;
		focusedObj.blur();
			if(null === maxAvailableHeight)
				maxAvailableHeight = window.innerHeight;

			isFocusedToReOpenKeyboard = true;
			focusedObj.focus();
			setTimeout(function(){
				isVirtualKeyboardOpen = true;
				if(typeof callback === "function")
					callback(focusedObj);
			}, 200);
	};

	/**
	 * 检查虚拟键盘的展现方式,判断是否是悬浮在正文内容上
	 * 调用前提:虚拟键盘已经呈现
	 *
	 * @returns {boolean|undefined}
	 */
	var checkIfVirtualKeyboardOverlapsContent = function(){
		if(null !== ifVirtualKeyboardOverlapsContent)
			return ifVirtualKeyboardOverlapsContent;

		return ifVirtualKeyboardOverlapsContent = window.innerHeight === maxAvailableHeight;
	};

	/**
	 * 将焦点所在的输入框滚动至可见区域内
	 */
	var scrollFocusedObjIntoView = function(){
		if(!isEnvironmentReady()){
			return;
		}

		reOpenKeyboardIfNeeded(function(focusedObj){
			if(checkIfVirtualKeyboardOverlapsContent()){
				bakedPaddingBottom = docEle.style.paddingBottom;
				docEle.style.paddingBottom = suspectedVirtualKeyboardHeight + "px";

				focusedObj.scrollIntoView();
			}
		});
	};

	document.addEventListener("DOMContentLoaded", function(){
		isDomContentLoaded = true;
	});

	var _scrollFocusedObjIntoView = function(focusedObj){
		if(null == focusedObj)
			return;

		/**
		 * 如果已经检测到“虚拟键盘的弹出不会遮挡正文内容”,则浏览器本身可能就不存在遮挡的问题,
		 * 或者遮挡的问题可以适当的通过手动的滚动页面解决
		 */
		if(ifVirtualKeyboardOverlapsContent === false){
			if(focusedObj.scrollIntoViewIfNeeded)
				focusedObj.scrollIntoViewIfNeeded();
			return;
		}

		scrollFocusedObjIntoView();
	};

	var virtualKeyboardSwitchTimer;

	docEle.addEventListener("focusin", function(e){
		/**
		 * 工作一次就需要失效
		 */
		if(isFocusedToReOpenKeyboard === true){
			isFocusedToReOpenKeyboard = false;
			return;
		}

		clearTimeout(virtualKeyboardSwitchTimer);
		if(isVirtualKeyboardOpen)
			return;

		var focusedObj = e.target;
		_scrollFocusedObjIntoView(focusedObj);
	});

	docEle.addEventListener("focusout", function(e){
		/**
		 * 工作一次就需要失效
		 */
		if(isBlurredToReFocus === true){
			isBlurredToReFocus = false;
			return;
		}

		clearTimeout(virtualKeyboardSwitchTimer);
		virtualKeyboardSwitchTimer = setTimeout(function(){
			isVirtualKeyboardOpen = false;

			/**
			 * 如果键盘不会遮挡正文,则什么都不处理
			 */
			if(!checkIfVirtualKeyboardOverlapsContent())
				return;

			if(null !== bakedPaddingBottom)
				docEle.style.paddingBottom = bakedPaddingBottom;
			bakedPaddingBottom = null;
		}, 10);
	});
})();

虚拟键盘弹出后,如果用户点击页面的空白位置,或以其它形式使得输入框失去焦点,虚拟键盘关闭的同时,页面显示将复原。

但上述代码并不完美:如果用户点击键盘右上角的关闭按钮,页面是没有任何办法感知这一动作的,此时仍然以 “键盘已弹出” 的形态进行工作。因为输入框的焦点仍然还在(这一特性实在不该,应当失去焦点才对)。

虽然并没有完美解决遮挡问题,但一得一失之间,还是值得尝试的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值