阅读 element-ui 源代码来分析 Scrollbar(滚动条) 组件的实现

element-ui 组件库中,存在一个 Scrollbar 组件,Scrollbar 组件的作用是隐藏了原先的滚动条,创建了样式更优雅的滚动条。

不过该组件并没有在 element-ui 组件库的文档中写明。此篇将从 Scrollbar 组件的应用和 Scrollbar 组件的原理这两个维度来分析 Scrollbar 组件。

一. Scrollbar 组件的应用

Scrollbar 组件在 element-ui 组件库中,有很多地方都使用到了。例如:Select 组件下拉选项中显示的滚动条应用了 Scrollbar 组件。

1.1 Scrollbar 组件提供的属性

通过阅读 Scrollbar 组件的源代码,发现其提供以下属性:

参数 说明 类型 可选值 默认值
native 是否在鼠标移入时显示滚动条 Boolean true/false --
wrapStyle 元素外包裹样式(元素可视区域样式) Array/String -- --
wrapClass 元素外包裹类名(元素可视区域类名) Array/String -- --
viewStyle 元素内包裹样式(元素滚动区域样式) Array/String -- --
viewClass 元素内包裹类名(元素滚动区域类名) Array/String -- --
noresize 区域的尺寸是否会发生变化 Boolean true/false --
tag 元素内包裹渲染的标签 String section/ul 等标签 --

1.2 Scrollbar 组件提供的方法

方法名 说明 参数
update 更新滚动条(此方法会更新滚动条的长度) --

1.3 Scrollbar 组件的应用案例

1.3.1 引入 Scrollbar 组件

如果需要使用 Scrollbar 组件,则需要先引入该组件,引入 Scrollbar 组件包含两种方式:

  1. 如果整体引入了 element-ui 组件库,则可以直接使用 Scrollbar 组件,因为该组件库中注册了 Scrollbar 组件。如同以下代码整体引入了 element-ui 组件库,则可以直接使用 Scrollbar 组件:
js 复制代码
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);
  1. 单独引入 Scrollbar 组件。
js 复制代码
import { Scrollbar } from 'element-ui';

Vue.use(Scrollbar);

1.3.2 使用 Scrollbar 组件

  1. 一个基本的 Scrollbar 组件,鼠标移入滚动条会显示

Scrollbar 组件的样式设置一个高度,超出高度会滚动,鼠标移入之后会显示滚动条。

html 复制代码
<template>
  <el-scrollbar :wrapStyle="wrapStyle">
    <div v-for="i in 50" :key="i">测试{{ i }}</div>
  </el-scrollbar>
</template>
<script>
export default {
  data() {
    return {
      wrapStyle: "height: 200px;"
    }
  },
}
</script>
  1. 滚动条隐藏的 Scrollbar 组件

设置 native 属性为 true,鼠标移入之后滚动条不显示。

html 复制代码
<template>
  <el-scrollbar :wrapStyle="wrapStyle" :native="true">
    <div v-for="i in 50" :key="i">测试{{ i }}</div>
  </el-scrollbar>
</template>
<script>
export default {
  data() {
    return {
      wrapStyle: "max-height: 200px;"
    }
  },
}
</script>
  1. Scrollbar 组件设置样式 style

分别传入 wrapStyleviewStyle 参数去设置元素的外包裹样式和内包裹样式。传参支持字符串或者数组形式:

(1)传入字符串:传入字符串时需要注意末尾要增加分号,因为源代码中针对传入字符串的处理是采用的字符串拼接的形式,组件内部在传入的样式之后继续去拼接组件内部定义的样式,如果不增加分号会导致拼接后的 style 格式不符,从而失效。

例如:

js 复制代码
export default {
  data() {
    return {
      wrapStyle: "max-height: 200px;" // 传入这种形式的字符串
    }
  },
}

(2)传入数组:在给元素设置多个样式时,以数组的形式传入会更加清晰。

例如:

js 复制代码
export default {
  data() {
    return {
      wrapStyle: [
        { "max-height": "200px"},
        { "background": "#0088cc" }
      ]
    }
  },
}
  1. Scrollbar 组件设置类名 class

分别传入 wrapClassviewClass 可以设置元素的外包裹类名和内包裹类名。传参支持字符串或者数组形式:

(1)传入字符串:如果只设置一个类名可以传入字符串。

例如:

js 复制代码
export default {
  data() {
    return {
      wrapClass: "classnameA"
    }
  },
}

(2)传入数组:如果需要设置多个类名可以传入数组。

例如:

js 复制代码
export default {
  data() {
    return {
      wrapClass: ["classnameA", "classnameB"]
    }
  },
}
  1. Scrollbar 组件的内包裹 div 渲染为 ul

传入 tag 参数,可以自定义组件内包裹的标签,默认为 div,如果传入 ul 则会渲染成 ul 列表。例如以下代码会渲染成为一个 ul 列表。

html 复制代码
<template>
  <el-scrollbar :wrapStyle="wrapStyle" tag="ul">
    <li v-for="i in 50" :key="i">测试{{ i }}</li>
  </el-scrollbar>
</template>
<script>
export default {
  data() {
    return {
      wrapStyle: "max-height: 200px;"
    }
  },
}
</script>

二. Scrollbar 组件的原理

为了了解 Scrollbar 组件的原理和实现方式,阅读了 Scrollbar 组件的源代码,整理了以下几个方面去解析 Scrollbar 组件的实现原理:

  1. 组件的 HTML 结构和 CSS 样式。
  2. 滚动条组件的实现。
  3. 监听滚动内容区域的变化(比如:原先滚动 50 条数据,之后动态变成了 25 条数据)。

2.1 组件的 HTML 结构和 CSS 样式

组件的 HTML 结构是通过 render 函数进行渲染的,在其函数内部根据传入的 native 参数,去判断鼠标移入是否显示滚动条,默认显示。

具体逻辑:

  1. 获取到滚动条的宽度。
  2. 将原生的滚动条隐藏。
  3. 整理传入的元素外包裹样式,将样式融合。
  4. 根据鼠标移入是否显示滚动条去渲染不同的 DOM 结构。

第一步:获取到滚动条的宽度

在源代码的 src/utils/scrollbar-width.js 中,有获取滚动条宽度的方法。

方法说明:

  1. 先创建一个隐藏的 div,使用 offsetWidth 获取没有滚动条的宽度。
  2. 将其 overflow 设置为 scroll,并且在这个隐藏 div 的内部再放入一个 div,设置内部的 div 宽度为 100%,再获取内部 divoffsetWidth
  3. 使用外部 divoffsetWidth 减去内部 divoffsetWidth 就是滚动条的宽度。
js 复制代码
import Vue from 'vue';

let scrollBarWidth;

export default function() {
  if (Vue.prototype.$isServer) return 0;
  if (scrollBarWidth !== undefined) return scrollBarWidth;

  const outer = document.createElement('div');
  outer.className = 'el-scrollbar__wrap';
  outer.style.visibility = 'hidden';
  outer.style.width = '100px';
  outer.style.position = 'absolute';
  outer.style.top = '-9999px';
  document.body.appendChild(outer);

  const widthNoScroll = outer.offsetWidth;
  outer.style.overflow = 'scroll';

  const inner = document.createElement('div');
  inner.style.width = '100%';
  outer.appendChild(inner);

  const widthWithScroll = inner.offsetWidth;
  outer.parentNode.removeChild(outer);
  scrollBarWidth = widthNoScroll - widthWithScroll;

  return scrollBarWidth;
};

第二步:将原生的滚动条隐藏

通过两层 div 的方式将原生的滚动条隐藏,外层的 div 设置 overflow: hidden,内层的 divmargin-rightmargin-bottom 设置为滚动条的负值,这样可以将内层的 div 向右、向下延伸,再与外层的 overflow: hidden 结合,从而遮盖住滚动条。

代码类似这样:

html 复制代码
<template>
  <div class="el-scrollbar">
    <div class="el-scrollbar__wrap"></div>
  </div>
</template>
<style>
.el-scrollbar {
  position: relative;
  overflow: hidden;
}
.el-scrollbar__wrap {
  margin-right: -17px;
  margin-bottom: -17px;
}
</style>

第三步:整理传入的元素外包裹样式,将样式融合

js 复制代码
if (Array.isArray(this.wrapStyle)) {
  // 如果传入的是数组,则整理成 style[key] = value 的形式
  style = toObject(this.wrapStyle);
  style.marginRight = style.marginBottom = gutterWith;
} else if (typeof this.wrapStyle === 'string') {
  // 如果传入的是字符串,则直接连接起来
  style += gutterStyle;
} else {
  // 如果是其他的情况,则直接赋值 margin-right 和 margin-bottom
  style = gutterStyle;
}
js 复制代码
// 将数组形式整理成对象的 key-value 形式
export function toObject(arr) {
  var res = {};
  for (let i = 0; i < arr.length; i++) {
    if (arr[i]) {
      extend(res, arr[i]);
    }
  }
  return res;
};

function extend(to, _from) {
  for (let key in _from) {
    to[key] = _from[key];
  }
  return to;
};

第四步:根据鼠标移入是否显示滚动条去渲染不同的 DOM 结构

  • 鼠标移入不显示滚动条,则不需要额外的逻辑,只需要将原生的滚动条隐藏即可。
  • 鼠标移入显示滚动条,需要增加滚动条组件,处理滚动时的逻辑。
js 复制代码
// 内层滚动区域的 DOM 结构
const view = h(this.tag, {
  class: ['el-scrollbar__view', this.viewClass],
  style: this.viewStyle,
  ref: 'resize'
}, this.$slots.default);
// 包裹滚动区域的外层 wrap 的 DOM 结构
const wrap = (
  <div
    ref="wrap"
    style={ style }
    onScroll={ this.handleScroll }
    class={ [this.wrapClass, 'el-scrollbar__wrap', gutter ? '' : 'el-scrollbar__wrap--hidden-default'] }>
    { [view] }
  </div>
);
let nodes;

if (!this.native) {
  // 如果没有传入 native 参数,则需要显示滚动条,需要增加 Bar 组件。
  nodes = ([
    wrap,
    <Bar
      move={ this.moveX }
      size={ this.sizeWidth }></Bar>,
    <Bar
      vertical
      move={ this.moveY }
      size={ this.sizeHeight }></Bar>
  ]);
} else {
  // 如果传入了 native 参数,则只需要将原生的滚动条隐藏即可
  nodes = ([
    <div
      ref="wrap"
      class={ [this.wrapClass, 'el-scrollbar__wrap'] }
      style={ style }>
      { [view] }
    </div>
  ]);
}
// 最外层增加 class 为 el-scrollbar 的 div 包裹
return h('div', { class: 'el-scrollbar' }, nodes);

render() 函数整体代码:

js 复制代码
// rander 函数渲染 Scrollbar 组件
render(h) {
  // 获取滚动条的宽度
  let gutter = scrollbarWidth();
  // 获取到传入的 wrapStyle
  let style = this.wrapStyle;

  // 如果获取到了滚动条的宽度,则将传入的 wrapStyle 参数与隐藏滚动条的样式进行融合,从而整理出包裹滚动区域的 div 的样式
  if (gutter) {
    const gutterWith = `-${gutter}px`;
    const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`;

    if (Array.isArray(this.wrapStyle)) {
      style = toObject(this.wrapStyle);
      style.marginRight = style.marginBottom = gutterWith;
    } else if (typeof this.wrapStyle === 'string') {
      style += gutterStyle;
    } else {
      style = gutterStyle;
    }
  }
  // 内层滚动区域的 DOM 结构
  const view = h(this.tag, {
    class: ['el-scrollbar__view', this.viewClass],
    style: this.viewStyle,
    ref: 'resize'
  }, this.$slots.default);
  // 包裹滚动区域的外层 wrap 的 DOM 结构
  const wrap = (
    <div
      ref="wrap"
      style={ style }
      onScroll={ this.handleScroll }
      class={ [this.wrapClass, 'el-scrollbar__wrap', gutter ? '' : 'el-scrollbar__wrap--hidden-default'] }>
      { [view] }
    </div>
  );
  let nodes;

  if (!this.native) {
    // 如果没有传入 native 参数,则需要显示滚动条,需要增加 Bar 组件。
    nodes = ([
      wrap,
      <Bar
        move={ this.moveX }
        size={ this.sizeWidth }></Bar>,
      <Bar
        vertical
        move={ this.moveY }
        size={ this.sizeHeight }></Bar>
    ]);
  } else {
    // 如果传入了 native 参数,则只需要将原生的滚动条隐藏即可
    nodes = ([
      <div
        ref="wrap"
        class={ [this.wrapClass, 'el-scrollbar__wrap'] }
        style={ style }>
        { [view] }
      </div>
    ]);
  }
  // 最外层增加 class 为 el-scrollbar 的 div 包裹
  return h('div', { class: 'el-scrollbar' }, nodes);
},

2.2 滚动条组件的实现

通过阅读滚动条组件 bar.js 的源代码,将从如下几个方面去解析滚动条组件:

  1. 滚动条的基本属性。
  2. 滚动组件的基本构成。
  3. 滚动的实现。

2.2.1 滚动条的基本属性

滚动条分为横向滚动条和竖向滚动条。横向滚动条绝对定位到滚动区域的下方,竖向滚动条绝对定位到滚动区域的右方。

在源代码 packages/scrollbar/src/util.js 中定义了滚动条的基本属性:

js 复制代码
export const BAR_MAP = {
  // 竖向滚动条
  vertical: {
    offset: 'offsetHeight', // 返回该元素的像素高度,高度包含内边距(padding)和边框(border),不包含外边距(margin)
    scroll: 'scrollTop', // 滚动条到元素顶部的距离
    scrollSize: 'scrollHeight', // 返回包含滚动部分的内容的实际高度,在加上 padding
    size: 'height', // 元素的高度
    key: 'vertical', // 竖直滚动条
    axis: 'Y', // Y 轴(垂直移动)
    client: 'clientY', // 置或获取鼠标指针位置相对于窗口客户区域的 x 坐标
    direction: 'top' // 获取的 getBoundingClientRect() 的方向
  },
  // 横向滚动条
  horizontal: {
    offset: 'offsetWidth', // 返回该元素的像素宽度,宽度包含内边距(padding)和边框(border),不包含外边距(margin)
    scroll: 'scrollLeft', // 滚动条到元素左边的距离
    scrollSize: 'scrollWidth', // 返回包含滚动部分的内容的实际宽度,在加上 padding
    size: 'width', // 元素的宽度
    key: 'horizontal', // 水平滚动条
    axis: 'X', // X 轴(水平移动)
    client: 'clientX', // 置或获取鼠标指针位置相对于窗口客户区域的 y 坐标
    direction: 'left' // 获取的 getBoundingClientRect() 的方向
  }
};

2.2.2 滚动组件的基本构成

接收的参数

滚动组件 bar.js 接收三个参数:

  1. vertical:是否是竖直滚动条。
  2. size:滚动条的长度(竖直滚动条指的是高度,水平滚动条指的是宽度)。
  3. move:滚动条的位移(竖直滚动条指的是纵向位移,水平滚动条指的是横向位移)。
js 复制代码
props: {
  vertical: Boolean,
  size: String,
  move: Number
},

滚动条的长度计算

计算思路:

滚动条的长度是用百分比去表示的。用外部包裹元素的 clientHeight(clientWidth) 除以 scrollHeight(scrollWidth) 再乘以 100 得到百分比的高度。

  • clientHeight(clientWidth) 指的是元素的 content + padding 的高度。
  • scrollHeight(scrollWidth) 指的是元素的 content + 溢出的不可见的部分 + padding 的高度。

计算方法:

js 复制代码
update() {
  let heightPercentage, widthPercentage;
  const wrap = this.wrap;
  if (!wrap) return;
  // 用元素的可见高度(content + padding)的高度去除以元素的可见高度+不可见高度,得到其所占的百分比
  heightPercentage = (wrap.clientHeight * 100 / wrap.scrollHeight);
  // 用元素的可见宽度(content + padding)的宽度去除以元素的可见宽度+不可见宽度,得到其所占的百分比
  widthPercentage = (wrap.clientWidth * 100 / wrap.scrollWidth);

  // 如果得到小于 100 的数字,则表示有超出的部分,需要显示滚动条,否测不需要显示滚动条
  this.sizeHeight = (heightPercentage < 100) ? (heightPercentage + '%') : '';
  this.sizeWidth = (widthPercentage < 100) ? (widthPercentage + '%') : '';
}

然后再把计算出来的百分比传入给 Bar 组件:

html 复制代码
<Bar
  move={ this.moveX }
  size={ this.sizeWidth }></Bar>,
<Bar
  vertical
  move={ this.moveY }
  size={ this.sizeHeight }></Bar>

滚动条的位移

滚动条的位移初始值是0,随着鼠标滚动和拖拽滚动会更新滚动条的位移,在后面的如何实现滚动条部分会说明如何计算滚动条的位移。

滚动条的渲染

DOM 结构:

滚动条的 DOM 结构分为两层,外层 div 是滚动的轨道,内层 div 是滚动条。

js 复制代码
computed: {
  bar() {
    return BAR_MAP[this.vertical ? 'vertical' : 'horizontal'];
  },
  ...
},
render() {
  const { size, move, bar } = this;

  return (
    <div
      class={ ['el-scrollbar__bar', 'is-' + bar.key] }
      onMousedown={ this.clickTrackHandler } >
      <div
        ref="thumb"
        class="el-scrollbar__thumb"
        onMousedown={ this.clickThumbHandler }
        style={ renderThumbStyle({ size, move, bar }) }>
      </div>
    </div>
  );
},

内层 div 的样式渲染:

js 复制代码
/**
 * 
 * @param {*} move 滚动条的位移
 * @param {*} size 滚动条的长度
 * @param {*} bar 滚动条属性
 * @returns 样式
 */
export function renderThumbStyle({ move, size, bar }) {
  const style = {};
  // 向水平方向(垂直方向)移动对应的百分比的位移
  const translate = `translate${bar.axis}(${ move }%)`;
  // 设置滚动条的宽度(高度)
  style[bar.size] = size;
  style.transform = translate;
  style.msTransform = translate;
  style.webkitTransform = translate;

  return style;
};

2.2.3 滚动的实现

想要实现滚动,需要先梳理触发滚动的情况:

  1. 鼠标的滚轮去触发滚动时,计算滚动条的实时位移。
  2. 拖拽滚动条进行滚动时,滚动条的位置和内容的位置实时改变。
  3. 在滚动的轨道上,按下鼠标,从而直接将滚动条置于某个位置来触发滚动,此时内容的位置也会更新。

鼠标滚轮触发滚动

鼠标的滚轮去触发滚动时,需要计算滚动条的实时位移,计算的位移通过百分比来表示。

需要给外层 wrap 绑定 scroll 事件,onScroll={ this.handleScroll }
handleScroll 的处理如下:

js 复制代码
handleScroll() {
  const wrap = this.wrap;
  // 用滚动条到元素顶部的距离乘以100,再去除以元素的可见高度,从而得到滚动条距离顶部的百分比
  this.moveY = ((wrap.scrollTop * 100) / wrap.clientHeight);
  // 用滚动条到元素左侧的距离乘以100,再去除以元素的可见宽度,从而得到滚动条距离左侧的百分比
  this.moveX = ((wrap.scrollLeft * 100) / wrap.clientWidth);
},

通过 handleScroll 方法的处理,从而实时的去计算 moveYmoveX

拖拽滚动条进行滚动

拖拽滚动条分为三个步骤:鼠标按下 → 拖拽过程 → 鼠标抬起

鼠标按下的处理:

在滚动条上绑定 mousedown 事件,onMousedown={ this.clickThumbHandler }

clickThumbHandler 方法:

js 复制代码
clickThumbHandler(e) {
  // 如果按下了 ctrl 键,或者按下的是鼠标的右键,则直接 return
  if (e.ctrlKey || e.button === 2) {
    return;
  }
  // 处理拖拽
  this.startDrag(e);
  // 更新滚动条的 axis 属性,对应的是 Y 或者 X
  // 这里计算的是点击的位置距离滚动条底部(或者右侧)的距离
  // this[this.bar.axis] = 滚动条的高度(宽度)- (鼠标距离页面可视区域的位置 - 滚动条距离页面顶端或者左侧的位置)
  // 鼠标距离页面可视区域的位置 - 滚动条距离页面顶端或者左侧的位置 = 鼠标距离滚动条顶部(或者左侧的距离)
  this[this.bar.axis] = (e.currentTarget[this.bar.offset] - (e[this.bar.client] - e.currentTarget.getBoundingClientRect()[this.bar.direction]));
},

以竖向滚动条举例,如下图所示:

拖拽过程的处理:

开始拖拽 startDrag 事件:

js 复制代码
startDrag(e) {
  // 阻止事件冒泡并且阻止该元素上同事件类型的监听器被触发
  e.stopImmediatePropagation();
  // 用 cursorDown 变量记录鼠标被按下
  this.cursorDown = true;
  // 监听鼠标移动事件
  on(document, 'mousemove', this.mouseMoveDocumentHandler);
  // 监听鼠标抬起事件
  on(document, 'mouseup', this.mouseUpDocumentHandler);
  // 禁止选中
  document.onselectstart = () => false;
},

鼠标移动实现拖拽的事件:

js 复制代码
// 鼠标移动事件的处理
mouseMoveDocumentHandler(e) {
  // 如果没有监听到鼠标按下,则直接 return
  if (this.cursorDown === false) return;
  // 这里是计算出来的点击的位置距离滚动条底部(或者右侧)的距离
  const prevPage = this[this.bar.axis];
  // 不存在 prevPage 则直接 return
  if (!prevPage) return;
  // 计算出的是鼠标距离滚动轨道最上方(或者最左侧)的距离
  const offset = ((this.$el.getBoundingClientRect()[this.bar.direction] - e[this.bar.client]) * -1);
  // 鼠标距离滚动条最上方的位置
  const thumbClickPosition = (this.$refs.thumb[this.bar.offset] - prevPage);
  // 计算出滚动条距离滚动轨道最上方(或者最左侧)的百分比
  const thumbPositionPercentage = ((offset - thumbClickPosition) * 100 / this.$el[this.bar.offset]);
  // 计算出需要滚动的距离 = 滚动条距离滚动轨道最上方的百分比 * 元素的实际高度(content + padding + 滚动部分) / 100 => 得到实际需要滚动的距离
  // 给 wrap 的 scrollTop 设置成需要滚动的距离,从而触发拖拽滚动的效果
  this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize] / 100);
},

如下几张图是计算过程的拆解:

offset 的计算:

thumbClickPosition 的计算:

thumbPositionPercentage 的计算:

最后需要计算 wrapscrollTop 的值,使用 wrapscrollHeight 和计算出的 thumbPositionPercentage(滚动条距离滚动轨道最上方或者最左侧的百分比)相乘即可。

鼠标抬起的处理:

js 复制代码
// 鼠标抬起事件的处理
mouseUpDocumentHandler() {
  // 将 cursorDown 重置为 false
  this.cursorDown = false;
  // 将 this[this.bar.axis] 重置为 0
  this[this.bar.axis] = 0;
  // 取消监听 mousemove
  off(document, 'mousemove', this.mouseMoveDocumentHandler);
  // 取消禁止选中
  document.onselectstart = null;
}

组件销毁时的处理:

取消监听 mouseup 事件。

js 复制代码
destroyed() {
  off(document, 'mouseup', this.mouseUpDocumentHandler);
}

在滚动的轨道上按下鼠标触发滚动

在外层滚动轨道上面绑定 mousedown 事件:onMousedown={ this.clickTrackHandler }

clickTrackHandler 方法的处理:

以鼠标的点击位置作为滚动条的中心,计算出 wrapscrollTop

js 复制代码
clickTrackHandler(e) {
  // 计算出的是鼠标距离滚动轨道最上方(或者最左侧)的距离
  const offset = Math.abs(e.target.getBoundingClientRect()[this.bar.direction] - e[this.bar.client]);
  // 计算出滚动条的一半的距离
  const thumbHalf = (this.$refs.thumb[this.bar.offset] / 2);
  // 以鼠标的位置作为滚动条的中心,计算出滚动条距离滚动轨道最上方的百分比
  const thumbPositionPercentage = ((offset - thumbHalf) * 100 / this.$el[this.bar.offset]);
  // 计算出需要滚动的距离 = 滚动条距离滚动轨道最上方的百分比 * 元素的实际高度(content + padding + 滚动部分) / 100 => 得到实际需要滚动的距离
  // 给 wrap 的 scrollTop 设置成需要滚动的距离,从而触发拖拽滚动的效果
  this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize] / 100);
},

2.4 监听滚动内容区域的变化

滚动内容的多少发生变化是指原先滚动 50 条数据,动态变化为 25 条数据。

Scrollbar 组件中引入了 resize-observer-polyfill 去监听元素尺寸变化,当元素的尺寸发生变化时调用 update 方法,重新计算滚动条的长度。

当组件被销毁时,取消对元素尺寸变化的监听。

三. 总结

通过对 element-ui 组件库的阅读,了解到了其内部的 Scrollbar 组件,以及该组件的用法。然后通过阅读 Scrollbar 组件的源代码,梳理出来了实现滚动条的要素。

阅读组件库的源代码,是一个很有价值的过程,可以了解到组件结构,滚动机制等。同时,这些知识也可以应用到开发项目中,提高开发效率和代码质量。

相关推荐
J不A秃V头A17 分钟前
Vue3:编写一个插件(进阶)
前端·vue.js
司篂篂41 分钟前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客1 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹1 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码2 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
天下无贼!3 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr3 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林3 小时前
npm发布插件超级简单版
前端·npm·node.js
罔闻_spider3 小时前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔3 小时前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab