【Nova UI】十七、打造组件库之滚动条组件(下):拖动滑块与滚动区域交互的实现

245 阅读5分钟

序言

在之前的内容中,我们已经深入探讨了滚动条组件开发的多个关键方面啦,包括前期准备工作的完成、核心功能的实现以及滑块大小和滚动距离的计算呢 🌟。通过这些步骤,我们已经成功搭建了一个具有基本功能的滚动条组件,为用户带来了初步的滚动体验。然而,滚动条组件的开发之旅尚未结束哦,我们将在本篇文章中继续深入完善这个组件,探索更高级的交互功能 🏗️。

拖动滑块与滚动区域

在一个完整的滚动条组件里,除了滑块的计算和基础滚动功能之外,用户期望获得更加流畅和直观的操作体验呢 👍。其中一个重要的部分就是能够通过拖动滑块来实现滚动区域的滚动 🔄。这不仅涉及到更加复杂的交互逻辑,还需要我们对用户的操作行为进行细致的监听和处理,同时还要考虑各种边界情况和性能优化 🔧。在本篇文章中,我们将详细阐述如何实现拖动滑块时与滚动区域的交互,让滚动条组件的功能更加完善,为用户带来更加出色的用户体验 🌐。

点击轨道滚动

当点击轨道时,我们需要计算出滚动区域需要滚动的距离,并设置相应的值。

const onBarMouseDown = (event: MouseEvent) => {
  const wrap = scrollbar?.wrapElement
  if (!wrap) return
  if (props.direction === 'vertical') {
    const scrollTop = (event.offsetY - size.value / 2) / ratio.value
    wrap.scrollTop = scrollTop
  } else if (props.direction === 'horizontal') {
    const scrollLeft = (event.offsetX - size.value / 2) / ratio.value
    wrap.scrollLeft = scrollLeft
  }
}

设置了 scrollTop 之后,会触发 scrollbar 的滚动事件,该事件会调用 bar 组件的 scrollHandler 来计算滚动的距离。

这个函数 onBarMouseDown 主要是处理用户点击滚动条轨道时的操作哦 👆。当用户点击轨道时,我们会根据点击位置和滑块的尺寸大小,计算出滚动区域应该滚动的距离。对于垂直滚动条,会根据 event.offsetY(点击位置的 Y 坐标)和滑块尺寸的一半进行计算;对于水平滚动条,则依据 event.offsetX(点击位置的 X 坐标)和滑块尺寸的一半来计算呢 🔍。计算得到的结果会被设置到 wrap 的 scrollTop 或 scrollLeft 属性上,进而触发滚动,随后触发相关滚动事件,调用 scrollHandler 进行后续滚动距离的计算 🔄。

拖动滑块滚动

接下来处理滑块的拖动操作啦,它和点击轨道滚动有些类似哦,我们要计算拖动的距离,通过比例计算出视图区域滚动的记录并设置上 🔢。

const startMove = (event: MouseEvent) => {
  const wrap = scrollbar?.wrapElement
  if (!wrap) return

  moveClient.value = props.direction === 'vertical' ? event.clientY : event.clientX
  wrapScroll.value = props.direction === 'vertical' ? wrap.scrollTop : wrap.scrollLeft
  event.stopImmediatePropagation()
  document.addEventListener('mousemove', mouseMoveHandler)
  document.addEventListener('mouseup', mouseUpHandler)
  document.onselectstart = () => false
}
const mouseMoveHandler = (event: MouseEvent) => {
  const wrap = scrollbar?.wrapElement
  if (!wrap || !barRef.value || !thumbRef.value) return
  
  if (props.direction === 'vertical') {
    const moveDistance = event.clientY - moveClient.value
    const scrollTop = moveDistance / ratio.value + wrapScroll.value
    wrap.scrollTop = scrollTop
  } else if (props.direction === 'horizontal') {
    const moveDistance = event.clientX - moveClient.value
    const scrollLeft = moveDistance / ratio.value + wrapScroll.value
    wrap.scrollLeft = scrollLeft
  }
} 
const mouseUpHandler = () => {
  document.removeEventListener('mousemove', mouseMoveHandler)
  document.removeEventListener('mouseup', mouseUpHandler)
  document.onselectstart = () => true
} 

const onThumbMouseDown = (event: MouseEvent) => {
  startMove(event)
}

在这部分代码里呢,我们主要关注的是用户拖动滑块的交互逻辑 👐。startMove 函数会在用户按下鼠标时触发,它会记录初始的鼠标位置和当前滚动位置,同时添加鼠标移动和鼠标松开的事件监听 🔔。mouseMoveHandler 函数负责在鼠标移动时更新滚动位置,它会根据鼠标的移动距离、比例和初始滚动位置计算新的滚动位置,然后将其应用到滚动区域 🔄。最后,mouseUpHandler 函数在用户松开鼠标时移除事件监听,恢复默认的文本选择行为 🔚。而 onThumbMouseDown 函数作为滑块按下事件的处理函数,会调用 startMove 开始拖动操作 🔛。

样式

@use 'mixins' as *;

$bar: #{$namespace}-bar;
$wrap: #{$namespace}-scrollbar__wrap;

@include b(scrollbar) {
  @include set-variable(('scrollbar-thumb-size'), '6px');
  @include set-variable(('scrollbar-thumb-border-radius'), getVariableValue('border-radius', 'small'));
  @include set-variable(('scrollbar-thumb-background-color'), getVariableValue('border-color', 'light'));
  @include set-variable(('scrollbar-thumb-hover-background-color'), getVariableValue('border-color'));
}

@include b(scrollbar) {
  overflow: hidden;
  position: relative;
  width: 100%;
  height: 100%;
  @include e(wrap) {
    width: 100%;
    height: 100%;
    overflow: auto;
    &::-webkit-scrollbar {
      display: none;
    }
  }

  &:hover {
    & > .#{$bar} {
      opacity: 1;
    }
  }

  @include when(native) {
    & > .#{$wrap} {
      &::-webkit-scrollbar {
        width: getVariableValue('scrollbar-thumb-size');
        height: getVariableValue('scrollbar-thumb-size');
      }
      &::-webkit-scrollbar-track {
        background: transparent;
        cursor: pointer;
        border-radius: getVariableValue('scrollbar-thumb-border-radius');
        opacity: 0;
      }
      &::-webkit-scrollbar-thumb {
        background: getVariableValue('scrollbar-thumb-background-color');
        cursor: pointer;
        border-radius: getVariableValue('scrollbar-thumb-border-radius');
        &:hover {
          background: getVariableValue('scrollbar-thumb-hover-background-color');
        }
      }
      &:hover {
        &::-webkit-scrollbar {
          display: block;
        }
      }
    }

    @include when(always) {
      & > .#{$wrap} {
        &::-webkit-scrollbar {
          display: block;
        }
      }
    }
  }

  @include m(vertical) {
    & > .#{$wrap} {
      overflow-x: hidden;
    }
  }
  @include m(horizontal) {
    & > .#{$wrap} {
      overflow-y: hidden;
    }
  }
}

@include b(bar) {
  position: absolute;
  right: 0;
  bottom: 0;
  border-radius: getVariableValue('scrollbar-thumb-border-radius');
  opacity: 0;
  transition: opacity 0.1s ease-out;
  cursor: pointer;
  &:hover {
    z-index: 2;
    opacity: 1;
  }
  &:has(:active) {
    opacity: 1;
  }

  @include e(thumb) {
    border-radius: inherit;
    background-color: getVariableValue('scrollbar-thumb-background-color');
    &:hover {
      background-color: getVariableValue('scrollbar-thumb-hover-background-color');
    }
  }

  @include m(vertical) {
    top: 0;
    & > div {
      width: getVariableValue('scrollbar-thumb-size');
    }
  }

  @include m(horizontal) {
    left: 0;
    & > div {
      height: getVariableValue('scrollbar-thumb-size');
    }
  }

  @include when(always) {
    & > div {
      opacity: 1;
    }
  }
}

这里提供了完整的样式代码,使用了 SASS 预处理器 🌈。@include b(scrollbar) 部分是用来设置滚动条的基本样式啦,包括滚动条的尺寸、边框半径、背景颜色等,同时处理了不同状态下(比如鼠标悬停)的显示效果和滚动条的显示与隐藏逻辑呢 🔍。对于 @include b(bar) 部分,设置了滚动条的位置、透明度、过渡效果和指针样式,以及滑块的样式,包括不同方向(垂直和水平)下的尺寸设置 🔧。

最终效果

PixPin_2025-01-22_13-12-50.gif 这段代码展示了滚动条组件最终的视觉效果,通过一张 GIF 图片直观地呈现了滚动条的外观和操作时的动态效果,帮助大家更好地理解组件在实际使用中的表现 👀。

🦀🦀感谢看官看到这里,如果觉得文章不错的话🙌,点个关注不迷路⭐。
诚邀您加入我的微信技术交流群🎉,群里都是志同道合的开发者👨‍💻,大家能一起交流分享摸鱼🐟。期待与您在群里相见🚀,咱们携手在开发路上共同进步✨ ! 👉点我

感谢各位大侠一路相伴,实在感激! 不瞒您说,在下还有几个开源项目 📦,它们就像精心培育的幼苗 🌱,急需您的浇灌。要是您瞧着还不错,麻烦动动手指,给它们点亮几颗 Star ⭐,您的支持就是它们成长的最大动力,在此谢过各位大侠啦!