背景
移动端表单页面会有较多的 input 输入框,用户输入数据时、用户提交数据时都需要对数据进行校验,如果校验不通过,则不允许用户提交。例如输入姓名时,不能包含表情,特殊字符等,输入年龄时不能为字符串等。
当表单页面较长,点击提交按钮时,校验不通过的表单项已经不在手机的可视区域内了,为了更好的体验,需要自动滚动页面,以便让用户看到首个校验不通过表单项。
分析
针对上述问题,涉及到两个关键元素,首个校验不通过的表单项 和 提交按钮 。首先分析这两个关键元素可能存在的场景:
- 两个元素都在可视区域内 => 不用滚动页面
- 首个校验不通过的表单项不在可视区域内 => 滚动页面
- 提交按钮是一个吸底按钮,且盖住了校验不通过的表单项 => 滚动页面
需要滚动的页面的场景其实就是校验不通过的表单项不在可视区域内,如上可能有两种场景会导致这种情况。
方案
涉及到是否在可视区域内,只需计算首个校验不通过的表单项的位置,然后进行一些比较操作,即可判断出是否需要滚动页面。
当然最简的方案是使用原生的api,在移动端有两个scrollIntoView
和 scrollIntoViewIfNeeded
, 用法页面很简单:
Element.scrollIntoView(true) //元素的顶端将和其所在滚动区的可视区域的顶端对齐
Element.scrollIntoView(false) //元素的底端将和其所在滚动区的可视区域的底端对齐
// scrollIntoViewIfNeeded 会判断元素是否在可视区域内,如果在就不滚动,例如元素在页面中间位置,就不滚动。
Element.scrollIntoViewIfNeeded()
对于表单页面来说,最适合使用scrollIntoViewIfNeeded
实现校验不通过表单项的滚动,可以完美兼容上述分析中的前两种场景。
然而当提交按钮吸底时,这一次变得又不一样了。可视区域包含了 fixed 元素,也就是说,当元素正好被 fixed 元素盖住时,使用scrollIntoViewIfNeeded
会被认为是在可视访问内,也就是不滚动元素了,那这种场景就需要自行计算位置了:
// 获取元素的在可视窗口中的位置
const { bottom } = Element.getBoundingClientRect()
const clientHeight = document.documentElement.clientHeight
// 计算吸底元素是否挡住了该表单项
if (bottom + fixedElementHeight > clientHeight) {
// 挡住了元素, 则往上滚动,具体滚动的位置可以根据实际情况调整
body.scrollTop += bottom + fixedElementHeight - clientHeight
}
当然也可以使用 scrollTo
函数,添加过渡动画。
小结
- 通过
scrollIntoViewIfNeeded
,元素会自行判断是否在可视区域内,如果不在则进行滚动。 - 通过
getBoundingClientRect
可以获取元素在可视区域中的位置。 - 对于 fixed 定位的元素,也会占据可视区域的高度,需要按需处理滚动。