大家可能都知道 dom 有一个 scrollIntoview方法,它可以轻易的让目标元素滚动到可视范围之内,而无需手动计算偏移量

dom.scrollIntoview()

效果如下

d94ec7f22e7b84538ae6211dda546191.gif

这样跳转比较生硬,因此可能还知道有这样一个参数

dom.scrollIntoview({
  behavior: 'smooth'
})

这样就能平滑滚动了

f68ff2a7bda8ba335c7201ac00eba701.gif

仅仅只有这些了吗?其实还可以做很多

一、重新学习 scrollIntoView 语法

打开 MDN 官网

https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollIntoView

语法其实很简单,有三种形式

scrollIntoView()
scrollIntoView(alignToTop)
scrollIntoView(scrollIntoViewOptions)

首先看第 2 种形式,就一个参数「alignToTop」 布尔值

默认为 true,表示是否沿着元素的顶端和滚动容器对齐,否则和元素底端对齐。

dom.scrollIntoView()
dom.scrollIntoView(true)

这两种效果是相同的

425d0edce40a46f8d3d124975eb5229a.gif

如果设置为false,那么会居底部对齐

dom.scrollIntoview(false)

效果如下

05ec1561319fb0c840ae3434896ec1d2.gif

注意,「alignToTop」自适应于「垂直方向」上的滚动,如果是「水平方向」的滚动,设置了没有任何区别。

// 水平滚动下,以下 3 种写法作用相同
dom.scrollIntoview()
dom.scrollIntoview(true)
dom.scrollIntoview(false)

效果都是一样的,如下

ebdc3274b38518ce76621dd214c22e29.gif

🤔为啥是居右侧对齐呢?同时为了满足两个方向上的灵活控制,出现了 「scrollIntoViewOptions」 参数。

这个稍微复杂一点,接着往下看。

二、详解 scrollIntoViewOptions 参数

「scrollIntoViewOptions」是一个对象,包含 3 个属性,分别是 「behavior」「block」「inline」

dom.scrollIntoView({ 
  behavior: "smooth", 
  block: "end", 
  inline: "nearest" 
});

首先来看「behavior」属性,这是用来定义滚动动画的,有 3 个关键词

  • 「smooth」:平滑滚动

  • 「instant」:无动画,直接跳转

  • 「auto」:默认值,滚动行为由 scroll-behavior[1] 的计算值决定

默认情况下是 「auto」,也就是由 CSS scroll-behavior 属性决定,如果我们给滚动容器添加了这个属性

.list{
  scroll-behavior: smooth;
}

这样,在不传递任何参数的情况下,也能实现平滑滚动

dom.scrollIntoView()

效果如下

c421c337e5053eec17911f6066a81f20.gif

一般情况下推荐使用 CSS 方式。

至于「instant」,实测和 「auto」 效果一致,如果设置了 CSS 平滑滚动,即使设置了scroll-behavior: instant仍然是平滑滚动,并不是我想象中的直接跳转❓。

d056bd23b914c6d6d2b0bf806ab376aa.gif

接下来看第 2 个属性「block」,这是用来定义「垂直方向」上的对齐方式,有 4 个关键词

  • 「start」「默认值」。元素顶部和滚动容器顶部对齐

  • 「center」:元素和滚动容器居中对齐

  • 「end」:元素底部和滚动容器底部对齐

  • 「nearest」:如果已经在视野范围内,就不滚动,否则就滚动到顶部或者底部(哪个更靠近就滚到哪里)

其中,「start」 和 「end」分别是顶部对齐和底部对齐,效果等同于

// 以下写法
dom.scrollIntoView({
  block: 'start'
})
dom.scrollIntoView({
  block: 'end'
})
// 等同于
dom.scrollIntoView(true)
dom.scrollIntoView(false)

前面已经演示过了,这里就不重复了

「center」是一个比较实用的属性,可以让元素一直处于中间位置,非常适合上一个、下一个切换的场景

dom.scrollIntoView({
  block: 'center'
})

这里简单实现了一个上下切换的效果

94ac7beb656946b9846c8b68763443b1.gif

「nearest」表示邻近的,可能稍微复杂一点,会根据元素是否已经在可视范围内做判断,避免频繁滚动,下面是一个原理演示

1f2b22decfb0943c0c86a7b2a37edae7.png
dom.scrollIntoView({
  block: 'nearest'
})

实际效果如下

3f056f7508a5c550ddc4b8cc6e3899b6.gif

如果用这个来实现上一个、下一个的功能,体验可能会更好

61b9db3919052de21264ccbc6624943f.gif

非常适合下拉列表中的上下键盘操作(截图为 Ant Design Select 组件)

https://ant.design/components/select-cn

e1ac6e1e367cc436ab7d95b29bfb4fed.png

还有个「inline」属性,和「block」是一致的,只是用来定义「水平方向」上的对齐方式,也有 4 个关键词

  • 「start」:元素左侧和滚动容器左侧对齐

  • 「center」:元素和滚动容器居中对齐

  • 「end」:元素右侧和滚动容器右侧对齐

  • 「nearest」「默认值」。如果已经在视野范围内,就不滚动,否则就滚动到左边或者右边(哪个更靠近就滚到哪里)

作用效果和前面完全是一致的,这里就不一一演示了。

有没有发现?「block」 和 「inline」 的默认值是不一样的,这也是为什么在水平滚动下,scrollIntoView(true)scrollIntoView(false)效果完全一致了,就是因为水平方向上已经在可视区了,所以不会有任何滚动。

三、水平和垂直同时存在的情况

很多时候,页面可能会存在水平和垂直都有滚动的情况,例如下面这种

d094820f172cc6d6ab5f54dc4ffb0cf0.png

整个页面是可以上下滚动的,然后页面中包含一个可以横向滚动的区域。

如果这时想要将红色部分滚动到可视区中间,应该怎么做?

按照前面的参数,可能会想到这样

dom.scrollIntoView({
  inline: 'center'
})

效果是这样的...

453379a6e3657710aa942f2e5085ef2f.gif

可以看到,水平方向确实滚动到了中间,但是垂直方向上滚动到了最顶部。

这是因为垂直方向的默认值为start,所以为了产生避免这样的影响,可以这样

dom.scrollIntoView({
  block: 'neareast',
  inline: 'center',
})

这样就不会上下跳动了

4f5af09b1778651c3d5290e1dd4602d5.gif

四、scrollIntoView 的边距

大家发现没,在使用startend这些属性值的时候,元素滚动到视野范围之内都是紧靠边缘的

6675cc2235bd4b2517ced297dbbd9e0a.png

视觉上有点不好看,有没有办法预留一点空间呢?

当然有了,不过不是 「scrollIntoView」本身,而是需要借助 CSS 中的 「scroll-margin」属性。关于这个特性,之前在这篇文章有有详细介绍,有兴趣的可以回顾一下

提升滚动体验!CSS 如何设置自动滚动定位的“安全”间距?(跳转公众号)

因此在这里,要留点间距也很简单,只需要

.item{
  scroll-margin: 10px;
}

再看看前面的上一个、下一个效果

6e354a4eddff39698888459849b85339.gif

是不是舒服了很多?

五、scrollIntoViewIfNeeded

除了 「scrollIntoView」,还有个非标准的 「scrollIntoViewIfNeeded」,-webkit-支持

https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollIntoViewIfNeeded

顾名思义,就是只有需要滚动定位的时候才会滚动,和neareast功能有点像

这个方法可以传递一个布尔值

element.scrollIntoViewIfNeeded(); // 等同于 element.scrollIntoViewIfNeeded(true)
element.scrollIntoViewIfNeeded(true);
element.scrollIntoViewIfNeeded(false);

表示在滚动到视线范围之内时是否「居中对齐」

这是和scrollIntoView不太一样的地方,相当于同时满足了neareastcenter的功能,非常适合用在需要初始化滚动的场景,比如这种滚动定位的任务进度条

85b6ad78ff2987acdfc81ae5f4403ba7.png

下面用一个简单案例模拟一下

3a2bd3ce67ae40845c36f0d50048f2ab.gif

完整 demo 可以参考以下链接

  • scrollIntoViewIfNeed (juejin.cn)[2]

  • scrollIntoViewIfNeeded (codepen.io)[3]

六、兼容性和总结

「Scrollintoview」是个兼容性非常好的属性,有多好呢?连 IE6 都支持

19658abdbd6b188dd35b058fed7636e4.png

当然这只是基础功能,也就是滚动到视区范围,像 options 参数都是后来才出现的,兼容性稍微差一点

5b898fb5bf6329dd0aec84453f8f4973.png

另外,你可能发现,在 safari 上平滑滚动不支持,因此推荐用 CSS scroll-behavior的方式,兼容性稍微好一点(15.4+)

23136e4d585518c8f8e88e04e64dc441.png

下面再来回顾一下这几个关键词

  • 「start」:元素顶部和滚动容器顶部(左侧)对齐

  • 「center」:元素和滚动容器居中对齐

  • 「end」:元素底部和滚动容器底部(右侧)对齐

  • 「nearest」:如果已经在视野范围内,就不滚动,否则就滚动到顶部(左侧)或者底部(右侧)(哪个更靠近就滚到哪里)

相比手动改变 「scrollTop」最大的好处就是无需精确计算每个元素的位置,也无需关注滚动容器是哪个,是不是非常省心呢?

[1]scroll-behavior: https://developer.mozilla.org/zh-CN/docs/Web/CSS/scroll-behavior

[2]scrollIntoViewIfNeed (juejin.cn): https://code.juejin.cn/pen/7279261289191309375

[3]scrollIntoViewIfNeeded (codepen.io): https://codepen.io/xboxyan/pen/dywzrPB

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

e3191453d7cf53fde5b8b2bcb46fb53e.png

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐