preface

I’m currently packaging a custom scroll bar container to replace the usual div tag, because browsers on Windows are really ugly, so I’m going to make a custom scroll bar container myself to keep it as consistent as possible with the MAC scroll bar.

There are two reasons for writing this article

  1. It is convenient to do similar work again in the future to review, to avoid frequent access to all kinds of information
  2. Some deficiencies of the existing network resources are found in the beginning, and are supplemented and described here. I hope that later people can see this article when they look up information to meet the needs.

Briefly say the place that some network data need to strengthen at present, I will make up for these problems here

  • Only container scrolling (such as mouse wheel scrolling), custom scroll bar proportionally moving scheme
  • Only click on the custom scroll bar drag container content to scroll implementation
    • The above two programs are not combined to provide a complete program
  • A variety of computing standards, dazzling, preferably to provide a better understanding of some of the standard calculations
  • Some solutions are scenariospecific and not comprehensive, such as vertical scrollbars only for BOD pages. Here we will consider making all scrollbars custom
  • There are no changes to the content and then to the scrollbar height
  • I didn’t do any compatibility

component

Before I begin, I’d like to introduce a custom container component that I wrapped with VUE. Some people may just want to find such an off-the-shelf solution component without looking at the ins and outs.

Please stamp the NPM address

Moreover, this component will have more optimization work than the liberation scheme explained below. After all, in order to facilitate everyone’s understanding, I will not introduce too much expansion optimization in this article.

The characteristics of

  • For the scrollbar area does not take up the space of the content itself, affecting the size of the browser scrollbar, using the native scrollbar, the component will eventually render only onedivThe label.
    • Such asmacFor most browsers on the system (I haven’t seen it yet), the native scroll bar interaction itself is pretty good and doesn’t require a custom scroll bar
    • In addition to the aboveMACIn addition, due to the implementation problem of the scheme, the scroll bar of this kind of browser is not customized, such as the browser on the Window system, this situation is relatively rare (not encountered for the moment). So you don’t have to deal with this small number of cases, adding complexity to the solution.
    • Custom scrollbars render several nested structures, adding DOM, so don’t use them if you can
  • For browsers that are not in the preceding two situations, generallywindowSystem browser, if yeswebkitThe kernel of the browser, the component will be utilized-webkit-scrollbarAnd so on CSS custom native scrollbar style, finally render into onedivThe label. This option is optional, so you don’t have to use this effect.
  • In addition to the above cases, the custom scrollbar will be used, so that different situations to render different results, can maximize the use of the simplest way to meet the beautiful scrollbar style.
  • The component is composed of horizontal and vertical scroll bars

In short, the component takes the “optimal” solution, the one with the simplest rendering structure and the best component performance, while satisfying the scrollbar style.

The following article only covers the part of the custom scroll bar, not the compatibility judgment.

core idea

First, let’s be clear about what we’re trying to achieve: we’re abandoning the default scrollbar provided by the browser and using DOM elements to simulate scrollbar behavior ourselves

We model it from a normal browser scrollbar phenomenon. Let’s take the vertical scroll bar as an example

This is a container with a scroll bar

Graph one:

The actual height of the content is shown in blue

Figure 2:

Let’s abstract the above phenomenon into the following figure

Figure 3:

  • Actual content area: the blue part of the phenomenon diagram
  • The visual area of the content: the gray box area in Figure 1
  • The scroll bar can swim area: that is, the scroll bar container area, not just the buoy height, is actually equal to the content of the visible area
  • The height of the scroll bar buoy: the height of the dark gray part of the scroll bar container where you drag the scroll bar up and down

All right. Now that we understand the relevant “areas”, let’s move on to the literal scrollbar interaction.

Scroll bar interaction behavior

The following description is not the real nature of the action, but it can be understood phenomenally in terms of the following effects.

When the container rolls, the actual content area moves up/down; The scroll bar buoy also moves down/up.

Here is a question: what is the relationship between the distance of content scrolling and the distance of scroll bar float moving? How much does the content roll, how much should the scroll bar float move?

Why is there this problem? Because when the content is rolled to the bottom, the buoy also has to reach the bottom, that is, the distance that the content can roll, and the distance that the buoy can move, is coordinated in a certain proportion.

Proportional relationship

Looking at Figure 3, we understand that when the container rolls, the actual content area moves up/down, just as the content visual area moves down/up; The scroll bar buoy also moves down/up.

Did you notice that the movement behavior of the container’s visible area is very similar to that of the scroll bar buoy?

If we look at scrolling behavior in terms of “the actual content area” as “the area where the scroll bar can swim,” and “the visible content area” as “the height of the scroll bar buoy,” the ratio becomes clear.

The visible area of the content/actual area of the content = the height of the scroll bar buoy/the area where the scroll bar can swim

In addition, there are other formulas for proportional relations, but they all follow the same principle, that is, consistent behavior in the content area, and consistent behavior in the scrollbar area constitute a proportional. Such as

Actual content area movement distance/Actual content area = Scroll bar buoy movement distance/scroll bar can swim area

Thanks to the browser’s default scroll bar, its buoy height is already calculated and we don’t care much about it. But now we need to write our custom scroll bar, so to calculate the height of the buoy, we can calculate the height of the buoy based on the first scale formula above.

So let’s take the literal formula, convert it to the code formula,

According to the first proportional relation formula:

clientHeight / scrollHeight = h / clientHeight

“Scrollbar swimming area” is actually equal to “content visible area”, h stands for “scrollbar buoy height”

According to the second proportional relation formula:

scrollTop / scrollHeight = top / clientHeight

Top stands for “the moving distance of the scroll bar buoy”, so the moving distance of the scroll bar buoy can be calculated according to this formula.

summary

The above spend so much text step by step to figure out the proportion relationship, is to let you understand the proportion relationship, so that the subsequent calculation of all kinds, can be handy.

If you don’t want to know the whole story, there are two formulas to remember

The visible area of the content/actual area of the content = the height of the scroll bar buoy/the area where the scroll bar can swim

clientHeight / scrollHeight = h / clientHeight

Actual content area movement distance/Actual content area = Scroll bar buoy movement distance/scroll bar can swim area

scrollTop / scrollHeight = top / clientHeight

Plan detailed speak

First of all, let’s define the big goal. The solution here will represent a “scroll container” with a combination of HTML elements (scroll bars are not native, but custom). For example, if you want to create a custom scroll bar, replace the div with an HTML combination of this scenario.

Yes, there is no doubt that this style optimization will come at a cost to the DOM (in fact, it is not only this cost). There are of course pure CSS ways to modify the native scrollbar style, but there are compatibility issues, obviously, not the focus of this article, but, billed as a “comprehensive” solution, I have to consider using CSS as well as possible (more on that later), but here I will focus on using JS.

Instead of using this as an example, you can use this HTML code in my scheme to learn how to implement a custom scrollbar.

html & CSS

<! --html-->
<div class="scroll-div">
    <div class="scroll-div-view"></div>
    <div class="scroll-div-y">
        <div class="scroll-div-y-bar"></div>
    </div>
</div>
Copy the code

.scroll-div-view is the container that provides the scroll bar, that is, your content area; .scroll-div-y is the area where the scroll bar is located, and.scroll-div-y-bar is the scroll bar buoy.

Let’s take a look at CSS

.scroll-div {
    position: relative;
    display: inline-block;
    overflow: hidden;
    user-select: none;
}
.scroll-div-view {
    margin-left: -17px;
    margin-bottom: -17px;
    overflow: scroll;
    /** Width and height Settings are examples for easy understanding, in fact should not be written dead. * * /
    width: 400px; 
    height: 100px;
}
.scroll-div-y {
    position: absolute;
    right: 1px;
    top: 0;
    height: 100%;
    width: 7px;
}
.scroll-y-bar {
    width: 7px;
    border-radius: 7px;
    background-color:rgba(0, 0, 0, 5);cursor: pointer;
    opacity: 0;
    transition: opacity .5s ease 0s;
}
.scroll-y-bar.is-show {
    opacity: 1;
    transition: opacity 0s ease 0s;
}
Copy the code

Briefly describe what the above styles do.

First of all, the parent element.scroll-div sets display:inline-block, which is “wrapped”, and uses the width and height of the content area.scroll-div-view. And.scroll-div-view sets overflow:scroll, so we don’t see the original scroll, So I set margin-left and margin-right to -17px, so that it exceeds the width and height of the parent element. But with the overflow:hidden set by the parent element, the overflow:hidden is hidden from the child element.

Area and the scroll bar. Scroll – div – y is relative to the parent element. Scroll – div do absolute positioning, positioning on the right, the height and the parent element.

After setting opacity, opacity is used to control the hiding and display of scroll bar floats, rather than display or visibility, for the following reasons:

  • I want the scrollbar to disappear in a gradient, that is, animated, withdisplayControl Hide and Disappear cannot apply animation effectstransition
  • withvisibilityWill cause redraw, while usingopacityDo not.

The js script controls the scroll bar

Here is the main implementation:

  • Drag the scroll bar buoy to cause scrolling
  • Carry on the scroll operation (such as the mouse wheel), the scroll bar buoy moves along with

First, I think the interaction effect of the MAC system is quite good. Second, in order to make users feel as unified as possible, that is, on the MAC and Windows system, the interaction effect can be as unified as possible, so that users can get used to it. Therefore, the scripts here, in addition to the two main purposes mentioned above, come with functional scripts that implement these interactions.

Initialization time

During initialization, each HTML element object is obtained, and the height of the scroll bar buoy is dynamically calculated according to the actual width and height of the container. Finally, the scroll monitor is performed for the content container, and the mouse move monitor (i.e., hover effect) is added for the scroll bar area. Each of the binding event functions and some of the variables defined here at the beginning will be explained later.

const scrollTop = 0; // Record the scrollTop of the last scroll to determine the scrolling direction
const timer = null; // The scrollbar disappears timer
const startY = 0; // Records the pageY of the last scroll bar click
const distanceY = 0; // Record the scrollTop of the content container every time the scrollbar buoy is clicked
const scrollContainer = document.querySelector('.scroll-div-view');
const scrollY = document.querySelector('.scroll-div-y');
const scrollYBar = document.querySelector('.scroll-div-y-bar');
calcSize(); // Calculate the height of the scroll bar buoy
scrollContainer.addEventListener('scroll', handleScroll);
scrollY.addEventListener('mouseover', hoverSrollYBar);

/** * Calculates the height of the vertical scroll bar */
function calcSize () {
    const clientAreaValue = scrollContainer.clientHeight;
    // Calculate the height according to formula 1
    scrollYBar.style.height = clientAreaValue * clientAreaValue / scrollContainer.scrollHeight + 'px';
}
Copy the code

Content scrolling

When you scroll, such as the mouse wheel, or scroll up or down on the touchpad, the content container is triggered.

/** * handles content scrolling events */
handleScroll (el) {
    const e = el || event;
    const target = e.target || e.srcElement;
    // If the last scrollTop is different from the last, a vertical roll has occurred
    // The main purpose is to distinguish between vertical scroll and horizontal scroll, because the horizontal scroll bar is not written here, so it is commented, for a reminder
    // if (target.scrollTop ! == scrollTop) {}
    const scrollAreaValue = scrollContainer.scrollHeight;
    const clientAreaValue = scrollContainer.clientHeight;
    const scrollValue = scrollContainer.scrollTop;
    scrollYBar.className += ' is-show'; // Display the scroll bar buoy
    timer && clearTimeout(timer);
    calcSize(); // Recalculate the scrollbar size every time you scroll, in case the contents of the container change, the scrollbar size does not match the container width and height after the change
    const distance = scrollValue * clientAreaValue / scrollAreaValue; // Calculate the distance that the scroll bar buoy should move according to Formula 2
    scrollYBar.style.transform = `translateY(${distance}px)`;
    timer = setTimeout((a)= > {
        scrollYBar.className = scrollYBar.className.replace(' is-show'.' '); // Hide the scroll bar buoy
    }, 800);
    scrollTop = target.scrollTop;
}
Copy the code

To summarize the function above: when the content is scrolling, calculate the distance that the scroll bar buoy should move according to Formula 2, and then apply it to the transform: translateY() style.

Actually, this should be a very simple function, which evaluates according to the formula and assigns the style.

<! -- Key code, only write these two can achieve scrolling content, scroll bar buoy follow the move -->const distance = scrollValue * clientAreaValue / scrollAreaValue; // Calculate the distance that the scroll bar buoy should move according to Formula 2
scrollYBar.style.transform = `translateY(${distance}px)`;
Copy the code

Why does this seem like so much code? As mentioned earlier, to simulate the interaction of the MAC system:

  1. The scroll bar is not displayed by default, and only appears when scrolling
  2. If you do not continue scrolling for a limited time after scrolling, the scroll bar will disappear

So the rest of the code is mainly for these two effects, including a line that calculates the height of the scrollbar buoy. The purpose of this is that when your content changes, such as after requesting some data, the content becomes less variable, the scrollbar height needs to be recalculated, otherwise the formula will not be correct.

I personally find this interaction nice, redisplaying the scroll and calculating the latest height on each scroll. But when you don’t want to use this interaction, you want to keep scrolling when the content size is larger than the viewable area, such as the current Window system scroll bar interaction, in this case, you need to listen for the content, when the change, you need to recalculate the scroll bar buoy height. And that leads to another question, what if you listen to the content? For the time being I had a MutationObserver in mind, but this guy has compatibility issues and is actually fine regardless of IE. Refer to this article, this is a digression, my plan here does not include this.

Drag the scroll bar to scroll

Floating scroll bar

As we saw during initialization, the Mouseover event was bound to scrollY. Now let’s see what this event does.

I added the hover event in the area of the scrollbar, not just the scrollbar buoys, because when you scroll a certain distance, the buoys hide it from you and it’s hard to know where the original move was, so just hover the entire scrollbar area.

When the scroll bar conditions are met, the float height of the scroll bar should also be re-adjusted to ensure that it is proportionate to the content height.

Note that I bind the mouseDown event to the scrollbar buoy and the mouseout event to the scrollbar area only after mouseover is triggered. This ensures that the scrollbar is displayed before listening, reducing frequent triggering of unnecessary events and reducing performance loss.

/** * Mouse over (hover) the scroll bar or scroll bar area */
function hoverScrollBar () {
    const sA = scrollContainer.scrollHeight;
    const cA = scrollContainer.clientHeight;
    // Display the scroll bar condition
    if (sA > cA) {
        scrollYBar.style[style] = cA * cA / sA + 'px'; // Set the scrollbar length
        scrollYBar.className += ' is-show';
        scrollYBar.addEventListener('mousedown', clickStart);
        scrollY.addEventListener('mouseout', hoverOutSroll); }}Copy the code
Hold the scroll bar

Here’s what happens when you click on the scroll bar buoy

/** * click the vertical scroll bar */
function clickStart (el) {
    const e = el || event;
    const target = e.target || e.srcElement;
    startY = e.pageY; // Record the pageY of the click at the moment, which is used to calculate the distance moved after dragging the mouse
    distanceY = scrollContainer.scrollTop; // Record the scrollTop of the content container when clicked at the moment, which is used to calculate the corresponding rolling ratio of the content container according to the distance of dragging the mouse later, and add the operation to get the final scrollTop
    scrollY.removeEventListener('mouseout', hoverOutSroll);
    document.addEventListener('mousemove', moveScrollYBar);
    document.addEventListener('mouseup', clickEnd);
}
Copy the code

The above function records some initial values later used for dragging the mouse, and binds the mouse movement and mouse release events to the page to ensure that the listening is triggered only after the scroll bar is clicked. Otherwise, it is not desirable to listen directly on the document for these two high-frequency events.

Why listen for events on the page document itself and not on the scroll bar itself? Because there is a scene where dragging the scroll bar sometimes leaves the scroll area, it should still show the scroll bar float and drag before releasing the mouse. A diagram illustrates the situation

In this case, the mouse is no longer on the scrollbar, so you listen on the Document and remove the mouseout event that originally listened on the scrollbar area

Move out of the scroll bar area

Let’s look at what mouseout does:

/** * The scrollbar disappears when the mouse moves over the area */
function hoverOutSroll (el) {
    const e = el || event;
    const target = e.target || e.srcElement;
    scrollYBar.className = scrollYBar.className.replace(' is-show'.' '); // Hide the scroll bar buoy
    scrollYBar.removeEventListener('mousedown', clickStart);
    scrollY.removeEventListener('mouseout', hoverOutSroll);
}
Copy the code

In fact, the move out event is very simple to do: hide the scroll bar buoy, and then unbind some of the original, reduce high-frequency listening.

Hold and drag the scroll bar

This is the key events to monitor, the main function is to, according to the distance of the mouse, the scroll bar buoy moving distance, according to the calculated formula of two corresponding to the content of the rolling distance, then add the previously already rolling distance, the final rolling distance, and then continue to trigger the scroll event, natural to calculate the movement of the buoy position and change the scroll bar.

Pay attention to the movement limits of the scroll bar, namely the top and bottom.

/** * hold the scroll bar to move */
function moveScrollBar (el) {
    const e = el || event;
    const delta = e.pageY - startY;
    const scrollAreaValue = scrollContainer.scrollHeight;
    const clientAreaValue = scrollContainer.clientHeight;
    let change = scrollAreaValue * delta / clientAreaValue; // Calculate how far the content should be moved based on the distance moved (scrollTop)
    change += distanceY; // Add the location of the content that was originally moved to get the exact scrollTop
    // If the value is negative, we must be back to where we started scrolling
    if (change < 0) {
        scrollContainer.scrollTop = 0;
        return;
    }
    // If it is greater than or equal to the distance moved, then it reaches the bottom
    if (change + clientAreaValue >= scrollAreaValue) {
        scrollContainer.scrollTop = scrollAreaValue - clientAreaValue;
        return;
    }
    scrollContainer.scrollTop = change; If scrollTop is set, the Scroll event will be triggered
}
Copy the code
Release the mouse

This is the last step, when the mouse is released, mainly untying some of the listeners before. And put the scrollbar out listener back in, after all, it was untied while holding down the scrollbar.

/** * hold down the scroll bar and release the mouse */
function clickEnd () {
    document.removeEventListener('mousemove', moveScrollYBar);
    document.removeEventListener('mouseup', clickEnd);
    scrollY.addEventListener('mouseout', hoverOutSroll);
}
Copy the code

summary

Above is the article introduces how to make a detailed scheme of custom scroll bar, the inside of the script about interaction design, can be adjusted according to your own preferences to change, this is the interpretation of the scheme mentioned incidentally, as long as you grasp the essence of the custom function, can one instance, behind the lines.

There are, of course, compatibility issues to watch out for

Does not support IE9 below, custom scroll bar is a UI beautification work, since are using IE9 below the browser, the pursuit of this aspect, in fact, is not much important. This scheme uses CSS transform attribute to move the scroll bar, which is not supported by IE9. If you want to support it, please replace it with absolute positioning in style and use orientation attributes top and left instead. Use attachEvent to bind events. Integration of this compatibility scheme is not provided here.

contrast

I don’t need to read this section, but I wrote it anyway. Please be aware that I’m not writing this section to show how good my solution is. Just for the convenience of looking up data in the future, and then encounter this kind of data, you can quickly know its advantages and disadvantages, so as to avoid spending more time to re-interpret and analyze.

Some data will use absolute location content container, by controlling the location property value to simulate the scroll bar content to scroll, it is a good way. But unfortunately, if you do not use scroll events like this scheme to handle the scroll bar float following the movement, you have to use mouseWheel or wheel events to handle, which has disadvantages:

  1. Compatibility issues
  2. It’s not good to get a scroll distance, such as when you scroll the mouse, triggeredwheelEvent, but you can’t get what this “roll distance” is.
  3. When you actually finish the above problems, it’s not nearly as easy as my solution here.

There are also some data whose operation metrics are offsetTop or offsetLeft, which can only be used in certain scenarios. The ultimate goal here is to produce a common “element” that can be applied to any page location, such as encapsulating my scheme as a VUE component or Web Component. You can use a custom tag to represent a container that displays custom scrollbars.

Wechat official account

== Please do not reprint == without permission