likes
comments
collection
share

【紧贴业务,拿来即用】自定义滚动条组件复杂嘛,一起来看看

作者站长头像
站长
· 阅读数 17

起因

  • 正在沉浸摸鱼快乐的我,突然感受到背后有人;我不动声色地瞟了一眼正对我的反光挡板,发现原来是UI大大

  • 她说:老板要统一不同浏览器下的滚动条样式

  • 我说:卧...........(又是老板?)

    【紧贴业务,拿来即用】自定义滚动条组件复杂嘛,一起来看看

原因

  1. css样式美化原生滚动条,不同浏览器解析渲染后的效果有区别

  2. 原生滚动条对内容空间有侵入,内容展示区域变小

    【紧贴业务,拿来即用】自定义滚动条组件复杂嘛,一起来看看

需求

  1. 支持鼠标滑轮滚动
  2. 滚动条不额外占空间,默认不展示,移入后展示
  3. 支持鼠标左键拖动滑动
  4. 支持鼠标左键点击轨道快速定位

解决思路(垂直滚动条为例)

  1. 本质是借助原生滚动条的能力(scroll 事件提供的事件对象信息),通过 css 处理达到视觉上不可见,再通过自定义 dom 美化来代替滚动条展示
  2. 先确定一下 dom 结构(顺便同步一下每个 dom 在此篇文章的命名),如下图
【紧贴业务,拿来即用】自定义滚动条组件复杂嘛,一起来看看
  1. 确定手柄高度

    • 页面渲染后,需要根据 可视区域高度 / 实际内容区域高度 = 手柄高度 / 轨道高度(等同于可视区域高度)
    • 高度的获取直接使用 Element.clientHeight
    • 实际内容区域高度两种方式都可获取 content.clientHeight、wrap.scrollHeight
  2. 确定手柄偏移高度(handleMove)

    • 手柄顶端到轨道顶端(handleMove) / 轨道高度 = 可视区域移动距离(scollTop)/ 实际内容区域高度
    • scollTop 借助原生滚动条的存在,监听 wrap 的 scroll 事件获取,根据上面公式获取 handleMove
  3. 点击轨道、拖动手柄如何达到可视区域内容滚动

    • 手柄顶端到轨道顶端(handleMove) / 轨道高度 = 可视区域移动距离(scollTop)/ 实际内容区域高度
    • 根据鼠标触发事件计算 handleMove,再根据上面公式获取 scollTop
    • 借助原生滚动条能力,给 wrap 区域设置 scrollTop,达到触发 wrap 的 scroll 事件,进而通过步骤 4 确定手柄偏移高度

具体实现

滚动条组件隐藏原生滚动条

  • 默认 wrap 区域是否存在原生滚动条,取决于 content 与 wrap 的高度,就会出现存在或者不存在情况,直接给 wrap 设置 overflow 为 scroll,让其一定展示原生滚动条;在 wrap 加父 dom,设置 overflow 为 hidden,wrap 设置负右边距,达到隐藏效果
  • 获取负右边距:动态生成父子 dom,父 dom 设置 overflow 为 scroll,子 dom 宽度 100%,通过父子宽度相减得到当前浏览器样式下的滚动条宽
【紧贴业务,拿来即用】自定义滚动条组件复杂嘛,一起来看看

轨道组件

  • 模板结构为父子 dom,分别是轨道、手柄两个 dom
  • 入参
    • isVertical:是否为垂直
    • thumbLen:手柄长度
    • moveRatio:手柄移动
    • wrapRefKey:wrap ref 值,轨道组件计算需要用此 dom 相关值

确定手柄高度

  • 基于上面的公式:可视区域高度 / 实际内容区域高度 = 手柄高度 / 轨道高度
  • 计算出可视区域高度 / 实际内容区域高度结果 heightRatio
  • heightRatio 大于等于1,则赋值手柄长度为0;否则赋值占轨道的百分比
initThumbLen() {
  const wrapEl = this.$refs.wrap
  if (!wrapEl) return
  const heightRatio = wrapEl.clientHeight / wrapEl.scrollHeight
  const widthRatio = wrapEl.clientWidth / wrapEl.scrollWidth

  this.thumbHeight = heightRatio >= 1 ? 0 : heightRatio * 100
  this.thumbWidth = widthRatio >= 1 ? 0 : widthRatio * 100
},

确定手柄偏移高度

  • 鼠标滚动触发原生滚动条(隐藏)scroll 事件,自定义滚动条也要同步移动
  • 原生滚动条高度与自定义滚动条高度都是 wrap 区域高度
  • 偏移高度 = scrollTop / wrap区域高度
  • 不需要考虑边界情况,因为原生滚动条已经处理
scrollWrap() {
  const wrapEl = this.$refs.wrap
  this.moveY = (wrapEl.scrollTop / wrapEl.clientHeight) * 100
  this.moveX = (wrapEl.scrollLeft / wrapEl.clientWidth) * 100
},

点击轨道、拖动手柄如何达到可视区域内容滚动

  • 不管是点击轨道,还是拖动手柄,此公式适用:手柄顶端到轨道顶端 / 轨道高度 = 可视区域移动距离(scollTop)/ 实际内容区域高度,公共方法如下
// 入参:鼠标顶端到轨道起点位置的距离 handleMove
getScorllByclickToTrack(handleMove) {
  // 轨道上的比率,移动距离 / 轨道长度
  const moveRatio = handleMove / this.$el[this.barObj.offset]

  // 基于比率,得出实际内容长度移动的距离
  this.$emit('wrapScroll', this.barObj.scroll, this.warpEl[this.barObj.scrollSize] * moveRatio)
},
  • 处理不同情况下获取鼠标点击位置到轨道起点位置的距离
    • 鼠标点击轨道快速定位

      • 求如下图红色线段

      【紧贴业务,拿来即用】自定义滚动条组件复杂嘛,一起来看看

      • 主要利用 Element.getBoundingClientRect MouseEvent.clientY 获取的距离都是相对浏览器可视区域
      clicktrackDown(e) {
        const distance = e[this.barObj.client] - e.target.getBoundingClientRect()[this.barObj.direction]
        const halfThumb = this.$refs.thumb[this.barObj.offset] / 2
      
        this.getScorllByclickToTrack(distance - halfThumb)
      }
      
    • 鼠标左键拖动滑动

      • 求如下图红色线段

      【紧贴业务,拿来即用】自定义滚动条组件复杂嘛,一起来看看

      • 鼠标点击手柄,求鼠标位置到轨道顶端距离
       clickthumbDown(e) {
             // 点击手柄,阻止点击事件到轨道
             e.stopPropagation()
             if (e.ctrlKey || e.button === 2) {
               return
             }
             this.downObj.cursorDown = true
             const clickClient = e[this.barObj.client]
             // 点击位置到轨道起点位置
             const clickToTrack = clickClient - this.$el.getBoundingClientRect()[this.barObj.direction]
             const clickToThumb = clickClient - e.target.getBoundingClientRect()[this.barObj.direction]
             // 记录此时的 handleMove
             this.downObj.handleMove = clickToTrack - clickToThumb
             this.initMoveFuc()
           }
      
      • 鼠标拖动手柄,求移动后鼠标位置到轨道顶端距离
       mouseMoveDoc(e) {
         this.downObj.handleMove = this.downObj.handleMove + e[this.barObj.movement]
         this.getScorllByclickToTrack(this.downObj.handleMove)
       }
      

其他

  • 默认不展示滚动条,移入指定区域展示
  • 绑定移动、抬起事件要在 document,并且移动过程中禁止鼠标选中
  • 点击手柄时,阻止点击事件冒泡到轨道上
  • 通过定义垂直、水平对象来抹平适用差异(BAR_MAP)

体验

最后

如果对你开发某些功能有所帮助,麻烦多点赞评论收藏😊

如果对你实现某类业务有所启发,麻烦多点赞评论收藏😊

如果...,麻烦多点赞评论收藏😊

如果大家有其他的方案,欢迎留言交流哦!

【紧贴业务,拿来即用】自定义滚动条组件复杂嘛,一起来看看