前言

这是一个存在很久的历史问题了,对于这样一个具有普遍性的问题浏览器偏偏没有给出解决方案,what?没有方案还聊个什么?

别急,别急,接下来我们一起来扒一扒关于软键盘高度和 input 的问题

我们先来看一个短片认识一下这个问题

问题描述:当操作者进行输入操作的时候,弹起的软键盘把原本的输入框遮挡了,导致操作者看不到操作结果

以往的解决方案

以往的解决方案:

  1. 修改网站的页面布局,比如本例中 twitter 尽量把 input 放置在中部以上的位置,从布局上尽量避免此类问题

  2. 在一些指定设备和浏览器中异步获取 window.innerHeight 进行前后对比而得出键盘高度

再来看一下另一种常见输入框的页面布局:

在这个场景里,输入框定位在页面的最底部,当软键盘弹起时整个视图窗口页面向上卷动,到达最底部时停止。恰巧当我们用 h5 来模拟这个效果的时候刚好勉强做到。

这是因为当你首次 fouse 到输入框的时候软键盘弹出,浏览器会使页面会向上滚动,以确保 input 是可见的,该特性和 document.body.scrollIntoViewIfNeeded 方法是一致的,但是当你 body 的可滚动高度超过窗口高度时还会产生另一个问题:固定元素将随页面滚动 如下图

因此浏览器关心的只是 input 是否被覆盖?实际上是 input 中的光标位置!那么这就解释了为什么输入框在底部的时候刚好勉强完成了,因为 input 在页面的底部时,软键盘弹出势必会遮挡住 input,因而浏览器会向上滚动至输入框可见的位置。

但是如下图的效果这样就无法做到了,因为在输入框的下面还有一行工具栏,也就是说输入框并非在最底部的位置,那么浏览器在滚动到可视位置时只会确保到 input 可见,而对于工具栏是否可见则并不在浏览器的考虑范围内。

IOING 的解决方案分析

综合来看上面两种布局方案的问题,都不能完美解决输入被键盘遮挡和底部 footer 不能被顶起的问题,那是不是就没得法子了?

当然号称可以让 HTML5 表现更接近 Native 的 IOING 引擎一定是有解决方案的

我们先来看一段 input 在 IOING 中的表现

我们可以看到在输入过程中页面通过滚动来始终保持光标位于可视区域的中心位置,因此在这里我们需要提一个知识点:获取输入光标的实时位置,当然这也是一个曲折的过程,在这里我就不扩算话题了,继续来讲原话题

前面说了三个主要的传统解决方案:

  1. 第一个是通过把 input 布局尽量放在页面顶部,显然这个不是我们想要的,否决掉

  2. 把 input 放在最底部,用来完成 footer 固定的效果,但是要局限页面高度不超过窗口高度,我们可以通过自制滚动控件来解除这个限制,那现在需要解决的技术点就变为实现一个模拟滚动控件

  3. 通过比对软键盘弹出前后的 window.innerHeight 的高度差来得到键盘高度,从而根据这个高度来实现底部定位和输入剧中,但是该方法局限于不同设备平台的支持

    综上所述我们总结一下我们要解决的思路和步骤

    先来看一下下面的图片

当键盘弹出时,键盘高度 = 不可见窗口高度
这个等式是有条件的,只有当 input 在对底部时该等式才成立 (这是上面讲过的 scrollIntoViewIfNeeded 的原因)

思考:如果我们能让该等式成立,且能够获取不可见位置高度,是否就能得出键盘高度了呢

我们整理好思路一步一步来实现

1.需要将内容放置在虚拟滚动中,在 IOING 像下面这样就可以创建一个虚拟滚动区域了

<scroll>
<scrolling>页面内容
</scrolling>
</scroll>

传统页面可以使用 WebKit 私有属性“-webkit-overflow-scrolling: touch” 来允许独立的滚动区域和触摸回弹,或者使用 iScroll.js 等第三方库来完成,但是需要注意对 iScroll 使用不当可能会造成性能问题

2.获取光标位于屏幕中的位置

3.当光标 fouce 时,键盘弹起,若 input 被遮挡页面会进行滚动,但滚动量不确定,因此我们可以强制滚动到底端,即键盘完全弹出后主动使窗口向上滚动窗口高度的距离,而实际上窗口只能向上滚动到最底部位置后就不能再向上滚动了,此时获取页面的 top.scrollY 即为实际键盘高度

得出公式:

可视区域的中心位置 = 键盘高度 + (窗口高 - 键盘高度)/2
应滚动距离 = 可视区域的中心位置 - 光标offsetTop - (光标被遮挡 ?键盘高度 :0)

当然实际操作需要更多的细节,po 出 IOING 中该部分逻辑实现的源代码:

// IOING 中部分源代码
// dom 为 input 元素
// scroll 为滚动容器的 Scroll 对象function scrollTo (y, _y, t, s, r) {r = r == undefined ? 1 : ry = y == undefined ? top.scrollY : yif ( r == 1 ? y > _y : y < _y) returns = s == undefined ? Math.abs((_y - y) / t * 17.6) : srAF(function () {top.scrollTo(0, y += r*s)scrollTo(y, _y, t, s, r)})
}function visibility () {if ( this.moving || this.wheeling ) {var top = dom.offset().topvar height = dom.offsetHeightvar viewTop = keyboardHeight + scrollOffsetTopvar viewBottom = factWindowHeight - scrollOffsetBottomif ( top + height <= viewTop || top >= viewBottom ) {dom.blur()}}
}function refreshCursor () {rAF(function () {dom.getSelectionRangeInsert('')})
}function getScroll () {var scroller = reactScroller || dom.closest('scroll')scroll = scroller ? scroller.scrollEvent : nullif ( type == 1 ) {minScrollY = scroll.minScrollY}
}function getViewOffset () {// android : (top.scrollY == 0 ? keyboardHeight : 0)viewOffset = viewCenter - rangeOffset.top - (top.scrollY == 0 ? keyboardHeight : 0) + (that.module.config.sandbox ? keyboardHeight : 0)return viewOffset
}function keyboardUp (e) {getScroll(1)if ( !scroll ) return// refresh cursor {{if ( device.os.ios && device.os.iosVersion < 12 ) {scroll.on('scroll scrollend', refreshCursor)}// }}if ( normal ) returnfunction upend (e) {window.keyboard.height = keyboardHeight = top.scrollY || factWindowHeight - top.innerHeight// change minScrollYscroll.minScrollY = minScrollY + keyboardHeightscroll.options.minScrollY = scroll.minScrollY// 光标位置rangeOffset = dom.getSelectionRangeOffset()// 可见视图的中心viewWrapper = factWindowHeight - keyboardHeight - scrollOffsetTop - scrollOffsetBottomviewCenter = keyboardHeight + viewWrapper / 2scroll.scrollBy(0, getViewOffset(), 600, null, false)// 滚动到不可见区域时 blurscroll.on('scroll', visibility)window.trigger('keyboardup', { height : keyboardHeight })if ( reactResize ) {scrollTo(null, 0, 300, null, -1)}}setTimeout(function () {top.one('scrollend', upend)// no scrollsetTimeout(function () {if ( keyboardHeight == 0 ) upend() }, 300)// ``` oldvar offset = 0if ( device.os.mobileSafari && device.os.iosVersion < 12 ) {offset = 24 * viewportScale}// scroll to bottomscrollTo(null, viewportHeight - offset, 300, null, 1)}, 300)
}function keyboardDown () {getScroll()if ( !scroll ) return// ``` old : refresh cursor {{if ( device.os.ios && device.os.iosVersion < 11 ) {scroll.off('scroll scrollend', refreshCursor)}// }}if ( normal ) returnif ( keyboardHeight == 0 ) return falsetop.scrollTo(0, 0)scroll.wrapper.scrollTop = 0// change minScrollYscroll.minScrollY = minScrollYscroll.options.minScrollY = minScrollYscroll.off('scroll', visibility)scroll._refresh()window.keyboard.height = keyboardHeight = 0
}function selectionRange (e) {getScroll()if ( !scroll ) return// 非箭头按键取消if ( e.type == 'keyup' && ![8, 13, 37, 38, 39, 40].consistOf(e.keyCode) ) return// 重置光标位置if ( reactOffset ) {rangeOffset = dom.getSelectionRangeOffset()} else if ( reactPosition ) {rangeOffset = dom.getSelectionRangePosition()}if ( reactOrigin && rangeOffset ) {rangeOffset.each(function (i, v) {scope.setValueOfHref(reactOrigin + '.' + i, v)})}if ( normal ) return// 光标居中if ( e.type == 'input' && e.timeStamp - timeStamp < 2000 ) returnif ( !scroll || !viewCenter ) returnif ( !reactOffset ) {rangeOffset = dom.getSelectionRangeOffset()}timeStamp = e.timeStampscroll.scrollBy(0, getViewOffset(), 400, null, false)
}dom.on('click', checkChange)
dom.on('focus', keyboardUp)
dom.on('blur', keyboardDown)
dom.on('focus keyup input paste mouseup', selectionRange)
})

其它的小细节和注意事项:

  1. safari 会受到浏览器底部导航栏的影响,会产生20多像素误差,需要针对考虑

  2. safari 中的 input 光标在执行 transform 3d变换的时候会出现光标停滞的现象,需要执行光标刷新操作

  3. 当 input 被操作者主动滑出可视区域外时应处罚键盘收起操作,否则在输入时 scrollIntoViewIfNeeded 效应将导致窗口滚动出现空白的问题

最后总结:

获取键盘高度只是我们的表象,真正解决 html5 带来的各种问题才是我们的研究课题,也只有扫清这些布局杀手 h5 才能在追赶 Native 的道路上更近一步!


结尾

最后的最后我来 po 一下在 IOING 中完成这一步我们需要做什么?

<input placeholder=写点啥>

就是这么简单,IOING 中 input 默认就能拥有自动居中特性

如果你要取消这个特性,就像下面这样写

<input nomal placeholder=写点啥>

当然也可以设置居中相对底部/相对于顶部的偏移位置

<input scroll-offset-top=50 placeholder=写点啥>
<input scroll-offset-bottom=50 placeholder=写点啥>

在输入过程中能够实时输出光标位置,且将位置信息赋值给数据源对象

<textarea react-position="test.range" resize="none"></textarea>
<p>当前光标位置:left: {test.range.left}, top: {test.range.top}</p>
<!-- test.range 为一个数据源对象 -->
<!-- react-position 指令将把该输入框的光标状态传递给 test.range 对象  -->

用js 获取键盘高度的方法

//键盘弹起时为键盘高度,未弹起时为0
console.log(window.keyboard.height)
// 通过键盘弹起事件获取
window.on('keyboardup', function (e) {console.log(e.height)
})
// 键盘收起事件
window.on('keyboarddown', function (e) {console.log(e.height) // 0
})

详细文档传送门:http://ioing.com/#docs-dom-input
GitHub 传送门:
https://github.com/ioing/IOING

如何用 js 获取虚拟键盘高度?(适用所有平台)相关推荐

  1. html 获得div的高度,如何用js获取div不确定的自适应高度(currentStyle与offsetHeight)...

    设计网页时,一些元素(如div)因显示的内容多少事先不确定,所以不能固定高度,也就是让它根据内容的多少自动调整高度,即自适应高度. 对于div元素不确定的自适应高度,如何用js获取当前高度呢?一般有两 ...

  2. js获取window窗口高度(页面滚动条可滚动高度) - 代码篇

    js中获取窗口高度的方法 取窗口滚动条滚动高度 (如下文 · 截图) 一. javascript 和 jquery代码: //原生 javascript 代码: let scrollHeight = ...

  3. JS获取DIV动态高度,并赋值到其style样式中

    <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312&qu ...

  4. android获得键盘高度,Android获取软键盘高度

    在 Android 里我们是无法直接获取软键盘高度的,但是在某些场景下,我们又需要获取软键盘的高度.我们可以使用 ViewTreeObserver.OnGlobalLayoutListener来监听窗 ...

  5. html div 动态赋值,JS获取DIV动态高度,并赋值到其style样式中

    JS获取DIV动态高度,并赋值到其style样式中 function $(id){ return document.getElementById(id) } function getHeight() ...

  6. js获取div元素高度和宽度的方法

    前端web页面中,js可以很方便的获取div元素的高度和宽度,那么这篇文章就说一说原生JS与JQ如何快速的获取DIV元素的高度和宽度的方法. js获取div元素高度与宽度的方法 js获取div元素的高 ...

  7. 原生js获取html元素高度,原生JS获取元素宽高实践详解

    开篇的话 任何不是亲身实践中求得的知识,都不是属于你的. 任何求得的知识不去时常温习运用,也不是属于你的. 记录由来 在做个上拉广告功能中遇到了一个"理所当然"觉得对的用法,慢慢才 ...

  8. android 虚拟键 高度,Android,获取虚拟键盘的高度

    在很多时候,很多需求下,我们都需要得知键盘的高度啊!实在不能理解为什么安卓不提供这样方便的API.虽然没有很好的监听事件,但是我们还是可以制造一个的.想想虚拟键盘弹起来之后界面发生了什么变化?对的,有 ...

  9. html桌面插件,js桌面虚拟键盘插件A-Keyboard

    A-Keyboard是一款js虚拟键盘插件.该插件可以在桌面端模拟普通键盘,移动端键盘和数字键盘.并且内置了几种可选用的主题效果. 使用方法 内置主题的CSS文件. 初始化插件 通过模块化的方式来使用 ...

最新文章

  1. Angular应用开发中遇到的问题
  2. css 字体大小_用一个 CSS 属性打造自适应网站
  3. C++ 命名空间 实战(二)之 直接数组访问迭代器访问
  4. rust实现wss访问_Rust的所有权,第2部分
  5. Mac安装oracleVM VMware安装失败,解决方案
  6. Python可以这样学(第六季:SQLite数据库编程)-董付国-专题视频课程
  7. JavaEE学习02--HTTP协议
  8. matlab 生成dbc文件,simulink中使用dbc文件实现CAN消息发送与代码生成
  9. Python 绘制属于你的世界地图
  10. python面板数据分析代码_【译】用python做计量之面板数据模型
  11. 中职学校计算机基础设施建设,以信息化推动中职学校计算机专业建设.doc
  12. theano学习--theano.function
  13. 502 Bad Gateway错误
  14. 只有手机号或者身份证能查出来绑定QQ号码?大神请赐教
  15. 华勤技术股份有限公司
  16. SpringBoot新项目配置
  17. 2022最新网络安全行业前景分析,附学习路线图
  18. 知乎日报APP:API接口分析
  19. DevOps团队绩效考核重点
  20. 前端使用jsencrypt的rsa加密算法加密信息后,在openresty搭建的网关处进行密文解密遇到的坑

热门文章

  1. ZOJ 3447 Doraemon's Number Game(优先队列+高精度运算)
  2. 基于阿里云ECS搭建云上博客!超详细图文步骤!
  3. PLC控制气缸,如何使用二位五通和三位五通控制电磁阀控制
  4. 31省份推出40万亿投资蓝图 新基建、公共卫生成亮点
  5. [Dubbo新闻]--Dubbo正式进入Apache孵化器,开启开源新时代
  6. 用matplotlib作图时,如何将坐标轴设置成对数坐标?
  7. 分布式执行框架——Ray简单使用
  8. 聚划算99划算节,再次验证下沉市场大变局
  9. python【数据结构与算法】流水线作业调度Johnson启发式算法
  10. 谢家华在德州扑克学到​的商场成功之道