Learn more about el-scrollbar

The original scroll bar is really not very beautiful, and it can barely be accepted in the browser, but the DOM element in the page will also have a great impact on this scroll bar. The element-ui library has a component. Although it is not introduced in the official document, it exists in the source code, in the packages/scrollbar folder of the source code. Been using it before, now take a moment to find out how he does it.

To be clear, if you want to directly modify the style of the browser scrollbar, you generally need a selector with a browser prefix to modify it, for example:::-webkit-scrollbar{}. There will be a lot of compatibility issues, so let's take a look at how el-scrollbar handles compatibility.

Open src/main.js to check it directly. Good guy, the component is defined in the way of rendering function, but the problem is not big, and it is basically understandable.

First look directly at how the render function defines the component

// 获取浏览器滚动条宽度
let gutter = scrollbarWidth();
// 看看有没有wrapStyle绑定的属性过来
let style = this.wrapStyle;
// 有浏览器滚动条宽度
if (gutter) {
    
    
  // 下面将wrapStyle标准化并加入margin属性
  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;
  }
}
// 定义el-scrollbar__view,子组件有个插槽,就是放置el-scrollbar标签下的内容
const view = h(this.tag, {
    
    
  class: ['el-scrollbar__view', this.viewClass],
  style: this.viewStyle,
  ref: 'resize'
}, this.$slots.default);
// 删除掉没啥用的,既然都要用了,还需要原生干嘛
// 定义el-scrollbar__wrap,就是el-scrollbar__view的父级元素
// 并将上述标准化的wrapStyle属性绑定下来
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 = ([
  wrap,
  <Bar
    move={
    
     this.moveX }
    size={
    
     this.sizeWidth }></Bar>,
  <Bar
    vertical
    move={
    
     this.moveY }
    size={
    
     this.sizeHeight }></Bar>
]);
// 最终这里形成的模板就是
// <div class='el-scrollbar'>
//  <div class= 'el-scrollbar__wrap'>
//    <div class= 'el-scrollbar__view'>
//      <slot></slot>
//    </div>
//  </div>
//  <div class='el-scrollbar__bar'></div>
//  <div class='el-scrollbar__bar'></div>
// </div>
return h('div', {
    
     class: 'el-scrollbar' }, nodes);

insert image description here

When I saw this, I woke up. In fact, I used wrap to display the scroll bar directly through overflow:scroll;, and the outer layer of the wrap, that is, el-scrollbar, was hidden through overflow: hidden to make the scroll bar invisible. At the same time, scrolling is monitored here, and the scrolling function simply records a few parameters, which will not be ignored here. The render here is just a brief mention of el-scrollbar__bar, so we need to look at it in detail, in src/bar.js

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>
);

This is relatively simple. To change the style through bar.key, it is actually to display the custom scroll bar or not to display the custom scroll bar. The key is el-scrollbar__thumb. This div is the div that can be dragged, so it is very important. You can see that both of these are bound to a function that monitors mouse presses, which basically handles data changes during dragging.

clickThumbHandler(e) {
    
    
  // prevent click event of right button
  if (e.ctrlKey || e.button === 2) {
    
    
    return;
  }
  this.startDrag(e);
  this[this.bar.axis] = (e.currentTarget[this.bar.offset] - (e[this.bar.client] - e.currentTarget.getBoundingClientRect()[this.bar.direction]));
},

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]);

  this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize] / 100);
},

startDrag(e) {
    
    
  e.stopImmediatePropagation();
  this.cursorDown = true;

  on(document, 'mousemove', this.mouseMoveDocumentHandler);
  on(document, 'mouseup', this.mouseUpDocumentHandler);
  document.onselectstart = () => false;
},

mouseMoveDocumentHandler(e) {
    
    
  if (this.cursorDown === false) return;
  const prevPage = this[this.bar.axis];

  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]);

  this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize] / 100);
},

mouseUpDocumentHandler(e) {
    
    
  this.cursorDown = false;
  this[this.bar.axis] = 0;
  off(document, 'mousemove', this.mouseMoveDocumentHandler);
  document.onselectstart = null;
}

It can be seen here that el-scrollbar__thumb uses html5 drag and drop to calculate the drag distance, and then updates the position of the view. The position of the view is transformed by the translate attribute of css defined in the renderThumbStyle function.

handleScroll() {
  const wrap = this.wrap;

  this.moveY = ((wrap.scrollTop * 100) / wrap.clientHeight);
  this.moveX = ((wrap.scrollLeft * 100) / wrap.clientWidth);
},

From the scrolling function mentioned above, it can be seen that the transformation of the value of warp.scrollTop is changed by the original scrolling. Here, the value of the scrolling is taken out and assigned to this.moveY to make the custom scrollbar move. .

Guess you like

Origin blog.csdn.net/ccy_888/article/details/125223044