一、问题的由来
有时候我们希望元素被focus的时候页面不发生滚动,例如我们点击一个按钮打开一个弹框,此时点击弹框中的关闭按钮隐藏弹框后,希望键盘的焦点回到之前的按钮上,我们就会执行如下JavaScript代码:
button.focus();
但是有时候会带来另外一个比较严重的体验问题,那就是如果弹框显示之后我们页面发生了滚动,原本点击的按钮跑到了屏幕显示区域之外,这个时候,按钮再次focus的时候就会触发按钮元素scrollIntoView重定位,浏览器发生滚动,表现为突然的跳动,体验很不好。
我们预期的目标是:按钮focus,然后无论按钮在不在屏幕显示区内,都不会发现浏览器的滚动重定位。
如何实现呢?
二、全新的API参数preventScroll
如果想要聚焦同时不发生滚动,其实很简单,使用一个全新的API参数preventScroll就可以了,例如:
button.focus({ preventScroll: true });
preventScroll
是一个可选参数,默认值是false
,表示不阻止聚焦滚动。如果preventScroll
参数值是true
则表示只聚焦不滚动。
我们来看一个例子,您可以狠狠地点击这里:JS focus preventScroll参数使用demo
demo页面有两个按钮,一个按钮点击直接focus,另外一个是设置了{preventScroll:true}
。
结果最终的效果如下GIF录屏所示:
兼容性
preventScroll
参数兼容性如下表:
64+ ✔ | 68+ ✔ | ✘ | ✘ | 51+ ✔ | ❓ |
手头没有Safari浏览器,因此兼容性不详,欢迎大家反馈。
三、IE浏览器下的处理
IE浏览器不支持这个非常棒的API,如果我们要让这个浏览器支持怎么办呢?
方法一:放弃IE/Edge
IE/Egde连自己都放弃自己投奔Chrome了,我们还有什么理由支持他呢!
方法二:polyfill
这里有个人写了个focus的polyfill:https://github.com/calvellido/focus-options-polyfill
然而,根据我在IE浏览器下的实地使用,有坑,效果很奇怪,因此,上面这个polyfill不建议使用。
方法三:临时补丁
拿垂直滚动距离,我们可以这样JS处理:
var y = window.pageYOffset; button.focus({ preventScroll: true }); // IE浏览器的处理 if (window.pageYOffset != y) { setTimeout(function () { document.documentElement.scrollTop = y; }, 0) }
但此方法并不完美,有一定概率页面会抖一下。
我在实际项目中的处理方法就是方法一,直接忽略IE浏览器,即使项目要兼容IE浏览器,毕竟是体验增强的东西,使用IE浏览器的用户能够忍受的糟糕体验要比我们预想的大的多,因为IE浏览器本身就是个体验糟糕的产品。
四、短文也要结束语
focus()
方法的API在文档中都是写作HTMLElement.focus(),说明所有的HTML元素都有focus方法,因为虽然普通元素默认是不能被focus的,但是如果我们设置合适的tabindex
属性值,情况就会发生变化,可以被focus,更多更深入更有趣知识可以参见这篇文章:“HTML tabindex属性与web网页键盘无障碍访问”。
因此,我们直接下面这样使用都是不会报错的:
document.body.focus(); document.createElement('div').focus();
但是如果是其他方法,例如submit
就会报错:
// 会出错,不支持 document.body.submit(); document.createElement('div').submit();
好,就说这么多,比原本预计的多写了半小时。