• 基于 element,阅读 ScrollBar 滚动组件源码


    scrollbar

    首先 scrollbar 组件对外暴漏的接口如下:

    	props: {
          // 是否采用原生滚动(即只是隐藏掉了原生滚动条,但并没有使用自定义的滚动条)
          native: {
            type: Boolean,
            // 文件配置信息
            default: scrollbar?.native ?? false,
          },
          // 自定义 wrap 容器的样式
          wrapStyle: {
            type: [String, Array],
            default: '',
          },
          wrapClass: {
            type: [String, Array],
            default: '',
          },
          // 自定义 view 容器的样式
          viewClass: {
            type: [String, Array],
            default: '',
          },
          viewStyle: {
            type: [String, Array],
            default: '',
          },
          // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
          noresize: Boolean, 
          // view 容器用那种标签渲染,默认为div
          tag: {
            type: String,
            default: 'div',
          },
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    最外层的 scrollbar 设置了 overflow:hidden,用来隐藏 wrap 中产生的浏览器原生滚动条。在 scrollbar 组件中的内容都将通过 slot 分发到 view 内部。

    
    <template>
      <div class="scrollbar">
        
        <div
          ref="wrap"
          :class="[wrapClass, 'scrollbar__wrap', native ? '' : 'scrollbar__wrap--hidden-default']"
          :style="style"
          @scroll="handleScroll"
        >
          
          <component :is="tag" ref="resize" :class="['scrollbar__view', viewClass]" :style="viewStyle">
            <slot>slot>
          component>
        div>
        
        <template v-if="!native">
          <bar :move="moveX" :size="sizeWidth" />
          <bar vertical :move="moveY" :size="sizeHeight" />
        template>
      div>
    template>
    <script lang="ts">
      export default defineComponent({
        name: 'Scrollbar',
        setup (props) {
          const sizeWidth = ref('0');
          const sizeHeight = ref('0');
          const moveX = ref(0);
          const moveY = ref(0);
          const wrap = ref();
          const resize = ref();
    
          // wrapStyle 传入样式的数据类型来处理 style
          const style = computed(() => {
            if (Array.isArray(props.wrapStyle)) {
              return toObject(props.wrapStyle);
            }
            return props.wrapStyle;
          });
        },
      })
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    onMounted 钩子中调用 update 方法对 sizeHeight、sizeWidth 进行初始化:

    
    const update = () => {
        if (!unref(wrap)) return;
    
        // 计算 thumb 长度的百分比
        const heightPercentage = (unref(wrap).clientHeight * 100) / unref(wrap).scrollHeight;
        const widthPercentage = (unref(wrap).clientWidth * 100) / unref(wrap).scrollWidth;
    
        // 设置 thumb 的 css 高度的百分比
        // 当这个比值大于等于100,wrap.clientheight(容器高度)大于等于 wrap.scrollheight(滚动高度)时,不需要滚动条了,将 size 置为空字符串。
        sizeHeight.value = heightPercentage < 100 ? heightPercentage + '%' : '';
        sizeWidth.value = widthPercentage < 100 ? widthPercentage + '%' : '';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    update 方法:thumb 在 track 中上下滚动,可滚动区域 view 在可视区域 wrap 中上下滚动,可以将 thumb 和 track 的这种相对关系看作是 wrap 和 view 相对关系的一个 微缩模型 (微缩反应),而滚动条的意义就是用来反映 view 和 wrap 的这种相对运动关系的。即:wrap.clientheight / wrap.scrollheight = thumb.clientheight / track.clientheight。

    // 滚动条滚动位置的更新
    const handleScroll = () => {
        if (!props.native) {
            moveY.value = (unref(wrap).scrollTop * 100) / unref(wrap).clientHeight;
            moveX.value = (unref(wrap).scrollLeft * 100) / unref(wrap).clientWidth;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    handleScroll 函数逻辑所在:wrap.scrolltop = wrap.clientheight 时,thumb 应该向下滚动它自身长度的距离,也就是transform: translateY(100%)。即当 wrap 滚动的时候,thumb 应该向下滚动的距离正好是 transform: translateY(wrap.scrolltop / wrap.clientheight )。

    import { onMounted, onBeforeUnmount } from 'vue';
    onMounted(() => {
        if (props.native) return;
        nextTick(update);
        if (!props.noresize) {
            addResizeListener(unref(resize), update);
            addResizeListener(unref(wrap), update);
            addEventListener('resize', update);
        }
    })
    
    onBeforeUnmount(() => {
        if (props.native) return;
        if (!props.noresize) {
            removeResizeListener(unref(resize), update);
            removeResizeListener(unref(wrap), update);
            removeEventListener('resize', update);
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    对于使用自定义的滚动条,若容器尺寸有变化的,我们分别在 onMounted 和 onBeforeUnmount 生命周期内,添加监听 resize 事件和移出事件等。

    bar

    bar 组件:右侧 track 是滚动条的滚动滑块、thumb 上下滚动的轨迹吧,并分别绑定了onmousedown事件。

    bar 组件对外暴漏的接口如下:

      props: {
        vertical: Boolean, // 当前bar组件是否为垂直滚动条
        size: String, // 百分数,当前bar组件的thumb长度 / track长度的百分比
        move: Number, // 滚动条向下/向右发生transform: translate的值
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5

    自定义滚动条渲染 dom 信息。

    import { defineComponent, h, computed, ref, getCurrentInstance, onUnmounted, inject, Ref } from 'vue'
    import { on, off } from '/@/utils/domUtils'
    
    export default defineComponent({
        name: 'Bar',
        setup(props) {
            const instance = getCurrentInstance();
            const thumb = ref();
            const barStore = ref<Recordable>({});
            const cursorDown = ref();
            const wrap = inject('scroll-bar-wrap', {} as Ref<Nullable<HTMLElement>>) as any;
    
    
            return () =>
                h(
                    'div',
                    {
                        class: ['scrollbar__bar', 'is-' + bar.value.key],
                        onMousedown: clickTrackHandler,
                    },
                    h('div', {
                        ref: thumb,
                        class: 'scrollbar__thumb',
                        onMousedown: clickThumbHandler,
                        style: renderThumbStyle({
                            size: props.size,
                            move: props.move,
                            bar: bar.value,
                        }),
                    }),
                );
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    通过 renderthumbstyle 转化为 trumb 的样式 transform: translatex( m o v e X {moveX}%) / transform: translatey( moveX{moveY}%) ,来生成 thumb,并且给 track 和 thumb 分别绑定了 onmousedown 事件。

    const clickThumbHandler = () => {
        if (e.ctrlKey || e.button === 2) {
            return;
        }
        window.getSelection() ?.removeAllRanges();
        startDrag(e);
        // 记录this.y , this.y = 鼠标按下点到thumb底部的距离
        // 记录this.x , this.x = 鼠标按下点到thumb左侧的距离
        barStore.value[bar.value.axis] =
            e.currentTarget[bar.value.offset] -
            (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]);
    }
    
    // 开始拖拽
    const startDrag = () => {
        e.stopImmediatePropagation();
        // 标识位, 标识当前开始拖拽
        cursorDown.value = true;
        // 绑定 mousemove 和 mouseup 事件
        on(document, 'mousemove', mouseMoveDocumentHandler);
        on(document, 'mouseup', mouseUpDocumentHandler);
        // 解决拖动过程中页面内容选中的bug
        document.onselectstart = () => false;
    }
    
    const mouseMoveDocumentHandler = () => {
        // 判断是否在拖拽过程中
        if (cursorDown.value === false) return;
        // 刚刚记录的this.y(this.x) 的值
        const prevPage = barStore.value[bar.value.axis];
    
        if (!prevPage) return;
    
        // 鼠标按下的位置在 track 中的偏移量,即鼠标按下点到 track 顶部(左侧)的距离
        const offset = (instance ?.vnode.el ?.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) * -1;
        // 鼠标按下点到 thumb 顶部(左侧)的距离
        const thumbClickPosition = thumb.value[bar.value.offset] - prevPage;
        // 当前thumb顶部(左侧)到track顶部(左侧)的距离,即thumb向下(向右)偏移的距离 占track高度(宽度)的百分比
        const thumbPositionPercentage = ((offset - thumbClickPosition) * 100) / instance ?.vnode.el ?.[bar.value.offset];
    
        // wrap.scrollheight / wrap.scrollleft * thumbpositionpercentage 得到 wrap.scrolltop / wrap.scrollleft
    
        // 当 wrap.scrolltop(wrap.scrollleft) 发生变化时,会触发父组件 wrap 上绑定的 onscroll 事件,
    
        // 从而重新计算movex/movey的值,这样 thumb 的滚动位置就会重新渲染
        wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100;
    }
    
    function mouseUpDocumentHandler() {
        // 当拖动结束,将标识位设为false
        cursorDown.value = false;
        // 将上一次拖动记录的this.y(this.x)的值清空
        barStore.value[bar.value.axis] = 0;
        // 取消页面绑定的 mousemove 事件
        off(document, 'mousemove', mouseMoveDocumentHandler);
        // 清空 onselectstart 事件绑定的函数
        document.onselectstart = null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    thumb 滚动条拖拽处理逻辑:在拖拽 thumb 的过程中,动态的计算 thumb 顶部(左侧)到 track 顶部(左侧)的距离占 track 本身高度(宽度)的百分比,然后利用这个百分比动态改变 wrap.scrolltop 的值,从而触发页面滚动以及滚动条位置的重新计算,实现滚动效果。

    const clickTrackHandler = () => {
        const offset = Math.abs(
            e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client],
        );
        const thumbHalf = thumb.value[bar.value.offset] / 2;
        const thumbPositionPercentage =
            ((offset - thumbHalf) * 100) / instance ?.vnode.el ?.[bar.value.offset];
    
        wrap.value[bar.value.scroll] =
            (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    需要注意一下两点:

    • track 的 onmousedown 事件回调中不会给页面绑定 mousemove 和 mouseup 事件,因为 track 相当于 click 事件。
    • 计算 thumb 顶部到 track 顶部的方法是:用鼠标点击点到 track 顶部的距离减去 thumb 的二分之一高度,因为点击 track 之后,thumb 的中点刚好要在鼠标点击点的位置。

    完整代码如下:

    // src/components/Scrollbar/src/bar.ts
    import { defineComponent, h, computed, ref, getCurrentInstance, onUnmounted, inject, Ref } from 'vue'
    import { on, off } from '/@/utils/domUtils'
    
    export default defineComponent({
      name: 'Bar',
    
      setup(props) {
        const instance = getCurrentInstance();
        const thumb = ref();
        const barStore = ref<Recordable>({});
        const cursorDown = ref();
        const wrap = inject('scroll-bar-wrap', {} as Ref<Nullable<HTMLElement>>) as any;
    
    
        // 滚动条信息
        const bar = computed(() => {
          return BAR_MAP[props.vertical ? 'vertical' : 'horizontal'];
        });
    
        const clickThumbHandler = () => {
          if (e.ctrlKey || e.button === 2) {
            return;
          }
          window.getSelection()?.removeAllRanges();
          startDrag(e);
          // 记录this.y , this.y = 鼠标按下点到thumb底部的距离
          // 记录this.x , this.x = 鼠标按下点到thumb左侧的距离
          barStore.value[bar.value.axis] =
            e.currentTarget[bar.value.offset] -
            (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]);
        }
    
        // track 的 onmousedown 事件回调中不会给页面绑定 mousemove 和 mouseup 事件,因为 track 相当于 click 事件。
        const clickTrackHandler = () => {
          const offset = Math.abs(
            e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client],
          );
          const thumbHalf = thumb.value[bar.value.offset] / 2;
          // 用鼠标点击点到 track 顶部的距离减去thumb的二分之一高度,点击 track 之后,thumb 的中点刚好要在鼠标点击点的位置。
          const thumbPositionPercentage =
            ((offset - thumbHalf) * 100) / instance?.vnode.el?.[bar.value.offset];
    
          wrap.value[bar.value.scroll] =
            (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100
        }
    
        // 开始拖拽
        const startDrag = () => {
          e.stopImmediatePropagation();
          // 标识位, 标识当前开始拖拽
          cursorDown.value = true;
          // 绑定 mousemove 和 mouseup 事件
          on(document, 'mousemove', mouseMoveDocumentHandler);
          on(document, 'mouseup', mouseUpDocumentHandler);
          // 解决拖动过程中页面内容选中的bug
          document.onselectstart = () => false;
        }
    
        const mouseMoveDocumentHandler = () => {
          // 判断是否在拖拽过程中
          if (cursorDown.value === false) return;
          // 刚刚记录的this.y(this.x) 的值
          const prevPage = barStore.value[bar.value.axis];
    
          if (!prevPage) return;
    
          // 鼠标按下的位置在 track 中的偏移量,即鼠标按下点到 track 顶部(左侧)的距离
          const offset = (instance?.vnode.el?.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) * -1;
          // 鼠标按下点到 thumb 顶部(左侧)的距离
          const thumbClickPosition = thumb.value[bar.value.offset] - prevPage;
          // 当前thumb顶部(左侧)到track顶部(左侧)的距离,即thumb向下(向右)偏移的距离 占track高度(宽度)的百分比
          const thumbPositionPercentage = ((offset - thumbClickPosition) * 100) / instance?.vnode.el?.[bar.value.offset];
    
          // wrap.scrollheight / wrap.scrollleft * thumbpositionpercentage 得到 wrap.scrolltop / wrap.scrollleft
    
          // 当 wrap.scrolltop(wrap.scrollleft) 发生变化时,会触发父组件 wrap 上绑定的 onscroll 事件,
    
          // 从而重新计算movex/movey的值,这样 thumb 的滚动位置就会重新渲染
          wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100;
        }
    
        function mouseUpDocumentHandler() {
          // 当拖动结束,将标识位设为false
          cursorDown.value = false;
          // 将上一次拖动记录的this.y(this.x)的值清空
          barStore.value[bar.value.axis] = 0;
          // 取消页面绑定的 mousemove 事件
          off(document, 'mousemove', mouseMoveDocumentHandler);
          // 清空 onselectstart 事件绑定的函数
          document.onselectstart = null;
        }
    
        onUnmounted(() => {
          // 取消页面绑定的 mouseup 事件
          off(document, 'mouseup', mouseUpDocumentHandler)
        })
        
        return () =>
          h(
            'div',
            {
              class: ['scrollbar__bar', 'is-' + bar.value.key],
              onMousedown: clickTrackHandler,
            },
            h('div', {
              ref: thumb,
              class: 'scrollbar__thumb',
              onMousedown: clickThumbHandler,
              // 将它转化为trumb 的样式 transform: translatex(${moveX}%) / transform: translatey(${moveY}%) 
              style: renderThumbStyle({
                size: props.size,
                move: props.move,
                bar: bar.value,
              }),
            }),
          );
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119

    滚动容器 ScrollContainer

    ScrollContainer 组件对外提供 scrollTo、scrollBottom 方法,通过该方法可以指定滚动条滚动的位置。

    
    <template>
      <Scrollbar ref="scrollbarRef" class="scroll-container" v-bind="$attrs">
        <slot>slot>
      Scrollbar>
    template>
    
    <script lang="ts">
      import { defineComponent, ref, unref, nextTick } from 'vue'
      import { Scrollbar } from '/@/components/Scrollbar';
      import { useScrollTo } from '/@/hooks/event/useScrollTo';
      
    
      export default defineComponent({
        name: 'ScrollContainer',
        components: { Scrollbar },
    
        setup() {
          const scrollbarRef = ref(null)
    
          function getScrollWrap () {
            const scrollbar = unref(scrollbarRef)
            if (!scrollbar) {
              return null
            }
            return scrollbar.wrap
          }
    
          // 滚动到指定位置
          function scrollTo (to: Number, duration = 500) {
            const scrollbar = unref(scrollbarRef)
            if (!scrollbar) {
              return
            }
            nextTick(() => {
              const wrap = unref(scrollbar.wrap)
              if (!wrap) {
                return
              }
              const { start } = useScrollTo({ el: wrap, to, duration })
              start()
            })
          },
          
          // 滚动到底部
          function scrollBottom () {
            const scrollbar = unref(scrollbarRef)
            if (!scrollbar) {
              return
            }
    
            nextTick(() => {
              const wrap = unref(scrollbar.wrap)
              if (!wrap) {
                return
              }
              const scrollHeight = wrap.scrollHeight
              const { start } = useScrollTo({ el: wrap, to: scrollHeight })
              start()
            })
          }
    
          return {
            scrollbarRef,
            scrollTo,
            scrollBottom,
            getScrollWrap,
          }
        },
      })
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    useScrollTo 实现

    useScrollTo Hook 添加滚动动画效果。

    // src/hooks/event/useScrollTo.ts
    
    // 当前位置
    const position = (el: HTMLElement) => {
      return el.scrollTop
    }
    
    // 滚动至
    const move = (el: HTMLElement, amount: number) => {
      el.scrollTop = amount
    }
    
    // 缓入缓出
    const easeInOutQuad = (t: number, b: number, c: number, d: number) => {
      t /= d / 2;
      if (t < 1) {
        return (c / 2) * t * t + b;
      }
      t--;
      return (-c / 2) * (t * (t - 2) - 1) + b;
    }
    
    export function useScrollTo({ el, to, duration = 500, callback}) {
      // 滚动标识
      const isActiveRef = ref(false)
      const start = position(el)
      const change = to - start
      const increment = 20
      let currentTime = 0
      duration = isUnDef(duration) ? 500 : duration
    
      // 滚动动画
      const animateScroll = function () {
        if (!unref(isActiveRef)) {
          return
        }
        currentTime += increment
        const val = easeInOutQuad(currentTime, start, change, duration)
        move(el, val)
    
        if (currentTime < duration && unref(isActiveRef)) {
          // 节流: 每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。
          requestAnimationFrame(animateScroll)
        } else {
          if (callback && isFunction(callback)) {
            callback({ currentTime, start, change, duration })
          }
        }
      }
    
      const run = () => {
        isActiveRef.value = true
        animateScroll()
      }
    
      const stop = () => {
        isActiveRef.value = false
      }
    
      return { start: run, stop }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    使用案例

    <template>
    	<ScrollContainer class="mt-4" ref="scrollRef">
    		<template v-for="index in 100" :key="index">
    			<p class="p-2" :style="{ border: '1px solid #eee' }"> {{ index }} p>
    		template>
    	ScrollContainer>
    template>
    <script lang="ts">
      import { defineComponent, ref, unref } from 'vue';
    
      export default defineComponent({
        setup () {
          const scrollRef = ref(null)
    
          const getScroll = () => {
            const scroll = unref(scrollRef)
            if (!scroll) {
              throw new Error('scroll is Null')
            }
            return scroll
          }
    
          function scrollTo(top: Number) {
            getScroll().scrollTo(top)
          }
          function scrollBottom () {
            getScroll().scrollBottom()
          }
    
          return { scrollRef, scrollTo, scrollBottom };
        }
      })
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    参考地址:

    1. 深入分析element ScrollBar滚动组件源码
    2. vue 是实现简易滚动效果
  • 相关阅读:
    zookeeper+kafka消息队列群集部署
    RStudion | 基础使用教程(初学者详细) | 基本设置 | 快捷操作 | 脚本运行 | 画图
    通信协议(三)——IIC协议
    基于Java+Spring+Strusts2+Hibernate 社区智慧养老服务平台 系统设计与实现
    在 Python 中,函数是第一类对象
    QT当中的connect+eventFilter函数全解
    ros建图过程中给上位机发布地图信息
    WIN+R 实用大总结
    三层神经网络的输出公式,神经网络层数怎么算
    在 macOS 上管理 Node版本
  • 原文地址:https://blog.csdn.net/qq_36437172/article/details/127537188