Because I was writing the UI library of the project recently, I was stuck for a long time when I encountered the threshold of custom scrollbar, mainly stuck in how to automatically monitor content changes and update the scrollbar height. Basically all the scrollbar plug-ins on the market do not achieve this point, the most back raking element’s source code to finally solve. This is what this article is about.

First of all, we need to implement the functions of the first to determine.

  • Click the left mouse button to drag
  • Mouse wheel rolling
  • Content changes, automatically update the length of the scroll bar
  • Provides developers with an interface to scroll back

The first two rely on the original scrollbar is actually relatively easy, but in the third point is really stuck for a long time, I thought for a long time did not think out. Finally read the element source code to achieve success.

Next, I’ll use the vertical scroll bar as an example (the horizontal scroll bar is basically the same) to implement a custom scroll bar. I’m going to try to be very clear about how this works.

1. Build a basic style framework

Let’s start by getting the HTML and styling done

<div class="scrollbar">
	<div class="scrollbar-content">
		<ul class="box">
			<li>11111</li><li>11111</li><li>11111</li><li>11111</li>
			<li>11111</li><li>11111</li><li>11111</li><li>11111</li>
			<li>11111</li><li>11111</li><li>11111</li><li>11111</li>
			<li>11111</li><li>11111</li><li>11111</li><li>11111</li>
		</ul>
	</div>
	<div class='scrollbar-bar'>
		<div ref="thumb" class="scrollbar-thumb"></div>
	</div>
</div>

Copy the code

Step 1 HTML and CSS

The frame of the scroll bar is shown above, and I will abbreviate it by wrap, bar,thumb in the following afternoon

  • wrap: Content area package box
  • bar: Scroll box for customizing scroll bars in the parcel area
  • thumb: Custom scroll bar

One thing to keep in mind before we start is that we’re not using the native scrollbar, in fact everything we do depends on the native scrollbar. It’s just hidden in the dark, while custom scrollbars that make the UI look better are in the light.

1.1 Calculate the width of the scroll bar.

The first step is to hide the original scroll bar. But here comes the first problem, which is that the width of the scroll bar varies from browser to browser. We need to know exactly how wide the scroll bar is if the wrap generates it.

Write a callback getScrollWidth that gets the width of the scrollWidth in the region. After getting the height of the scrollWidth,

function getScrollWidth(){
    const outer = document.createElement("div");
    outer.className = "el-scrollbar__wrap";
    outer.style.width = '100px';
    outer.style.visibility = "hidden";
    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;
}
Copy the code

Once you’ve got the scrollBarWidth of the scrollbar, move the scrollbar out of sight by setting the WRAP’s CSS style using marginRight

wrap.style.overflow = scroll;
wrap.style.marginRight = -scrollWidth + "px";
Copy the code
1.2 Calculate the height of the scroll bar.

Step 2 we need to figure out the height of the scroll bar. The calculation method is also very simple, element height scrollHieght/ content height clientHeight, get the percentage of the scrollbar.

Since the content height changes frequently, we can write a callback function updateThumb that updates the scrollbar height, so that S can call it at any time later.

function updateThumb(){
    let heightPercentage = (wrap.clientHeight * 100 / wrap.scrollHeight);
    thumb.style.height = heightPercentage + "%";   
}

Copy the code

At this point, you’ve basically got a basic scroll bar style. Next we want to realize its use function.

View the results of step 1

2, add scroll bar sliding function

Here we have the UI of the formed scroll bar, but the scrolling and dragging functions are still missing. The key is how to listen for scrollbar changes.

2.1 Roller sliding

Remember from the beginning of this article that all of our implementations rely on hidden native scrollbars. If you understand what I said above, then the problem is simple. When we start to slide the wheel, the hidden scroll bar will also scroll, triggering the Scroll event of the original scroll bar.

We can elaborate a little bit more here. The Scroll event must be triggered whenever the element’s scrollTop changes. So what we’re doing with the scroll wheel is essentially changing the scrollTop of the element.

So all we need to do is write a corresponding callback, handleScroll, and change the style of our custom scrollbar in real time every time the callback is triggered.

function handleScroll(){ this.moveY = (wrap.scrollTop *100 / wrap.clientHeight); Transform = "translateY" + moveY; }, wrap.addEventListener('scroll',handleScroll);Copy the code

Check the scroll wheel slide effect

2.2 Click the scroll box to move the scroll bar and content to the corresponding position

Next we implement the second function. When we click on a position in the scroll box, the scroll bar jumps to that position and the content position changes.

The first step is to obtain the y coordinate of the click, and then calculate the distance from the top of the scroll bar, and then calculate the percentage of the scroll box, which is the height of the scroll bar

Function clickTrackHandle (e) {/ / click on the location and the distance between the scroll box at the top of the const offset = math.h abs (e. arget. GetBoundingClientRect (). The top - Const thumbHalf = thumb.offsetheight / 2; const thumbHalf = thumb.offsetheight / 2; Const thumbPositionPercentage = (offset - thumbHalf) * 100 / wrap.offsetheight; // Operate by changing scrollTop. The last step in all action scrollbars is to implement wrap.scrollTop = (thumbPositionPercentage * wrap.scrollheight / 100) via handleScroll; } bar.addEventListener("click",clickTrackHandle);Copy the code

Any change in the value of scrollTop triggers the callback we wrote in the previous step.

See what happens when you click the scroll box

2.3 Drag the scroll bar to move the content

Next we go to the implementation of manual drag scroll bar to achieve moving content, this knowledge is the knowledge of drag, but when looking at the source code found element habit is very good, he is when you click on the scroll bar bind drag, and then unbind when unbind.

function mouseMoveDocumentHandler(){}; / / record real-time scrolling position of drag and drop function. / / when click the scrollbar document addEventListener (" mousedown, "mouseMoveDocumentHandler); document.onselectstart = false; / / stop / / selected when loosen the scroll bar at the same time the document. The removeEventListener (" mousedown, "mouseMoveDocumentHandler); document.onselectstart = null; // Block the selection as wellCopy the code

Because this piece of code is more, it will not be posted in the article, we can directly link to see it. See the effect of dragging the scroll bar

3. Realize real-time update of scroll bar with content

The second chapter is mainly about the implementation of the scroll bar function, this chapter is about the tangled 😖 I have a long time of function.

Because the height of the scrollbar is not something we can determine at first, it needs to be determined after the DOM content has been rendered. And sometimes you need to change the height of the scroll bar in real time as the content changes. Look at the market after the scroll bar, found that basically do not meet this function.

In fact, without this, there is no visual interaction to use. For example, adding an element that originally had a scrollbar caused the scrollbar to be small because the content was reduced, but the custom scrollbar remained because the change was not detected, which would cause confusion for the user.

I don’t want to update the scrollbar every time I update content by adding a callback function. I want it to update itself in real time. After failing to find an answer on the Internet, I finally went to the source code of Element and studied it for a long time. Finally, I found the answer I wanted.

The key point here is what I said earlier — if we change the scrollTop of an element, the Scroll event will be triggered.

Imagine a situation where the scrollbar is always at the bottom, like the one below

So as soon as I change the content a little bit, the scroll bar will inevitably get longer or shorter. So when the length of the scroll bar changes, the scrollTop changes naturally (if the scroll bar disappears, the scrollTop becomes 0), then the scroll callback function will be triggered, so we can automatically detect 😊.

With that in mind, a problem arose. Under normal circumstances, the scroll bar can not appear at the bottom ah, so what to do?

Element chose to build its own scroll bar at the bottom to suit its needs.

I made a demo, and I’ll check it out here

<script>
    const ul = document.getElementById("ul");
    const resizeTrigger = document.createElement("div");
        resizeTrigger.className = "resize-triggers";
        resizeTrigger.innerHTML = '<div class="expand-trigger"><div><div></div></div></div>';
        ul.appendChild(resizeTrigger);
    	
    const resetTrigger = function (element) {
        const trigger = element.__resizeTrigger__;
        const expand = trigger.firstElementChild;
        const expandChild = expand.firstElementChild;
        expandChild.style.height = expand.offsetHeight + 1 + 'px';
        expand.scrollTop = expand.scrollHeight;
    };
    
    ul.addEventListener("scroll",function(){
        resetTrigger(this);
    },true)
</script>
Copy the code

Ul is the DOM element that we wrap our content around.

In conjunction with CSS, we create the resizeTrigger div in the first JS paragraph and set its height to 100%. In this case, if the content changes, resizeTrigger always changes height at the same time as the parent ul element. It is important to set the height to 100% so that you can actively synchronize with content changes.

Notice that resizeTrigger also has a parent-child element expand and expandChild. In the second JS resetTrigger function. Then set expandChild’s height above the parent element expand’s height, causing expand to generate the scroll bar. Then we set the scrollTop of the scroll bar to the maximum, so that the scroll bar will appear at the bottom of the scroll area resizeTrigger.

We now have the scrollbar at the bottom, so whenever the content changes, the scrollTop of the scrollbar must also change.

The last piece of code is just listening for Scroll. When the scrollTop value is monitored to change, the corresponding callback function is triggered.

So the logic at the end of this piece of code actually looks like this. Content change –> UL height change –> resizeTrigger height change –> expand scrollTop changes –> Trigger the scroll callback function, adjust the scroll bar height again in the function to ensure that the scroll bar height is correct.

Through these three pieces of code, we also basically implemented automatic listening for content changes to update the scrollbar.

I drew a simple illustration to help me understand the logic

Two small blue boxes generate scrollbars to help monitor content changes

4, realize componentization, convenient for developers to use

It is basically possible to implement a custom scrollbar after the above three steps. The code above is for native JS. In our project, point 4 was implemented by encapsulating a component as a Scrollbar for use in the project.

This requirement because different frameworks show different ways, so I will not paste the code in detail because my project is using a Vue framework, so it is a Vue component, there is a need to see.

For those of you who have never written about Vue components, take a look at this article and step down

View the ScrollBar component


Good article is over here, in the process of looking at somebody else’s source code also learned a lot. Such as writing components using JSX; The scroll monitor is actually to judge the scrollTop; For example, by making your own scrollbar method to listen to the scrollTop to achieve automatic updates. Finally, through writing articles, I have deepened my understanding of some new knowledge points.