⭐ 当按下键盘上下键的时候,如何控制 container 的 scrollTop

428 阅读3分钟

有一天,小袁的老板把他叫过去,跟他说,小袁啊,你来做一个需求,当按上下键的时候,保持所选的选项在下拉框的中心。

小袁想了想,呜呜,好像又要去百度啥叫clientHeight、clientWidth、clientTop、clientLeft、scrollHeight、scrollWidth、scrollTop、scrollLeft、offsetHeight、offsetWidth、offsetTop、offsetLeft

(统一下术语,下拉框称为container, 要居中元素称为item)

1.0 版本——计算需要居中元素上方有多少元素

小袁想了想,这还不简单吗,计算需要居中元素上方有多少元素,然后让container scroll 相应的距离不就行了吗。

const itemHeight = 37;
this.containerRef.current.scrollTop = itemHeight * deepLen
   - (this.containerRef.current.clientHeight + itemHeight) / 2;

(deepLen 表示为元素的深度, itemHeight 为元素的高度, this.ulRef.current.clientHeightcontainer 的高度)

小袁交上了代码,老大 CR 了代码并将其打回。 小袁就问:为啥啊,老大说万一不是所有的元素的高度都为 37 呢。

2.0 版本——公式计算

这个时候,小袁的老大瞟了一眼他的草稿纸,跟他说,哎呀不用这么麻烦,你只要知道一个属性叫getBoundingClientRect,它能够知道元素的大小及其相对于视口的位置。

小袁瞟了一眼caniuse 发现市面上的浏览器基本都支持,于是愉快地决定使用这个属性。

老大跟小袁说,这个应该用公式推导,我们约定变量如下:

  • item: 要被居中的元素
  • container: 容器
  • containerHeight: container 的高度
  • itemHeight: item 的高度
  • containerTop: container 的顶部距离文档顶部的距离
  • itemTop: item 的顶部距离文档顶部的距离
  • itemOffset: item 的顶部距离 container 内第一个元素的头部的距离

假设该元素处于居中状态,有如下两个公式:

  1. cHeight / 2 - item / 2 = itemTop - containerTop

  1. scrollTop = itemOffset - (itemTop - containerTop)

由于 itemOffset 是常量,故并结合上述情况可得公式:


scrollTop = itemOffset - (itemTop - containerTop)
          = scrollTop1 - (itemTop1 - containerTop1) - (itemTop - containerTop)
	  = scrollTop1 - (itemTop1 - containerTop1) - (containerHeight / 2 - item / 2)
// scrollTop1、itemTop1、containerTop1 为当前元素滚动到任意位置取到的对应值

3.0 版本 末端超过移动到本项

小袁吭哧吭哧把代码写好,老板这时候说,哎呀我觉得有点奇怪啊,还是做成按下键的时候超过就显示在最后一项吧

于是小袁回去改代码了。

跟居中的情况不一样的是,按下上键跟按下下键的 containerscrollTop 的值不一样,所以需要分开考虑,并且需要考虑按下上键超过了第一项跟按下下键超过最后一项的情况。

当当前在最后一项时,按下下键,此时的 containerScrollTop 为 0 当当前在第一项时,按下上键,此时的 containerScrollTopitemOffset的值

其他情况时,父级 scrollTop 的值如下:

containerScrollTop = itemOffset + itemHeight - containerHeight

完整代码如下:

moveCursor({
    direction?: string,
    overflow?: boolean
}) {
    const containerRef = this.containerRef.current;
    const {cursorItem} = this.state;
    const {top: containerTop, height: containerHeight} = containerRef.getBoundingClientRect();
    const {top: itemTop, height: itemHeight} = cursorItem.getBoundingClientRect();
    let containerScrollTop = containerRef.scrollTop;
    const itemOffset = itemTop - containerTop + containerScrollTop;
    if (direction === 'down') {
        if (overflow) {
            containerScrollTop = 0;
        }
        else if ((itemTop + itemHeight) > (containerTop + containerHeight)) {
            containerScrollTop = itemOffset + itemHeight - containerHeight;
        }
    }
    else if (direction === 'up') {
        if (overflow) {
            containerScrollTop = itemOffset + itemHeight - containerHeight;
        }
        else if (itemTop < containerTop) {
            containerScrollTop = itemOffset;
        }
    }
    containerRef.scrollTop = containerScrollTop;
}

小袁交付了代码, CR 通过,安心得回家吃饭睡觉打豆豆了。

感谢你的阅读,喜欢可以给一个小赞~