Design the ultimate solution for Scrollbar

One, foreword

I haven’t updated my article for a long time because I haven’t found good materials. Recently, my work has also been handed over. After summarizing this year’s project, I found such an interesting question and would like to share it with you.

Second, the background

In projects, we often encounter scrollbar scenarios, which generally occur in such scenarios. There is a box outside, and its CSS property overflow is set to Scroll or Auto, but its contents are beyond the bearing range of the box, then horizontal or vertical scrollbars will appear.

The following code looks like this:

<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Scroll to the sample</title>
  <style>
    .container {
      width: 300px;
      height: 500px;
      overflow: auto;
      border: 1px solid #eee;
    }
  </style>
</head>

<body>
  <div class="container">
    <ul class="content"></ul>
  </div>


  <script>
    const content = document.querySelector(".content")
    const fragment = document.createElement("fragment")
    new Array(20).fill(true).forEach((item, i) = > { // The design is out of the carrying range of the outer box
      const li = document.createElement("li")
      li.innerHTML = `<h1>The ${'item' + i}</h1>`
      fragment.appendChild(li)
    })
    content.appendChild(fragment)
  </script>
</body>

</html>

Copy the code

The effect is shown below:

Google Chrome Effects

Edge Browser Effects

Safari Effects

We can see the obvious differences between browsers; Not only that, let’s take Chrome as an example; Sometimes we might think, well, this scrollbar is ugly; Seems to want to modify the style;

Three, how to solve

For Chrome we can do this:

/* Write the following styles globally */::-webkit-scrollbar-track {/* background-color:#b46868; } ::-webkit-scrollbar-thumb {/* slider color */ background-color:rgba(0.0.0.0.2); } ::-webkit-scrollbar-button {/* background-color:#7c2929; } :-webkit-scrollbar-corner {/* background-color: black; }Copy the code

You can see the following effect:

You can see that we have actually changed the style of the browser scrollbar, and since it is written globally, all scrollbars in the project will change their style;

In fact, I often do custom scrollbar styles this way in real projects; But has the problem really been solved? Not really?

Since this style only works with -webkit-core browsers, how about running the same project on Firefox or Edge?

As you can see, the scrollbar is back to what it used to be. If you are working on a project with a large screen, the aesthetics of the large screen will be destroyed directly, so we need to fix this problem!

Five, meditate

As an experienced developer, we can easily imagine that we can write CSS solutions for each browser.

Here I’ve reorganized the CSS properties that different browsers expose for scrollbars;

Ie browser

/* Internet Explorer */
.scrollbar{
    /* Triangle arrow color */
    scrollbar-arrow-color: #fff;
    /* The color of the slider button */
    scrollbar-face-color: #0099dd;
    /* Scroll bar overall color */
    scrollbar-highlight-color: #0099dd;
    /* Scroll bar shadow */
    scrollbar-shadow-color: #0099dd;
    /* Scroll bar track color */
    scrollbar-track-color: #0066ff;
    /* Scroll bar 3D light shadow border appearance color - left and top shadow color */
    scrollbar-3dlight-color:#0099dd;
    /* Scroll bar 3D dark shadow border appearance color - right and bottom shadow color */
    scrollbar-darkshadow-color: #0099dd;
    /* Scroll bar base color */
    scrollbar-base-color: #0099dd;
}

Copy the code

Google Chrome and Safari are both webkit-kernel so they’re the same

/* Google Chrome and Safari */::-webkit-scrollbar-track {/* background-color:#b46868; } ::-webkit-scrollbar-thumb {/* slider color */ background-color:rgba(0.0.0.0.2); } ::-webkit-scrollbar-button {/* background-color:#7c2929; } :-webkit-scrollbar-corner {/* background-color: black; }Copy the code

I haven’t found a solution for Firefox yet; It is said that currently Firefox does not provide such CSS properties for developers to modify;

For most applications it seems possible to solve the problem with this full CSS approach; But for the sake of user experience, we still want a better solution;

The reason I don’t recommend this is because scrollbar styles are customizable on some browsers; In addition, CSS is still too much uncertainty, in order to ensure the unity of the scroll bar in multiple browsers, we still need a stable, unified, easy to maintain way;

Six, racking their brains

In the process of exploration, I found some ideas on Element-UI. I found that the scroll bar on the left menu bar of Element could maintain consistency no matter in any browser, and the UI style and interaction were good-looking. Based on that, I clicked on it;

It turns out that element-UI doesn’t use a built-in browser scroll bar, but implements a scroll bar of its own.

There are several benefits:

First: stability. UI consistency can be maintained no matter what, because switching control over built-in scrollbar styles to normal DIV elements ensures more consistent styling

Second, it is more controllable, because CSS styles are designed by themselves and interactions are also designed by themselves, making it easier to expand new interactions

Third, it is easier to maintain. For scrollbars, it is easy to think of packaging them into a component.

Of course, there are advantages have certain disadvantages, according to my current understanding, this way still can not fully rely on CSS implementation, so js consumption cost is its Achilles heel

Since Element-UI has such an implementation, I figured it must have such a component that I could just use it, but UNFORTUNATELY I didn’t find a similar implementation in Element-UI, so I thought, I’ll implement it myself; All things considered, I chose to package a VUe2-based scrollbar solution first (because element-UI is vuE2-based).

Vii. Implementation components

The idea of the directory structure for this component is as follows

|-- scrollbar
|   |-- index.js
|   |-- helper
|   |   |-- calc.js
|   |   |-- event.js
|   |   |-- index.js
|   |-- src
|       |-- index.vue

Copy the code

In fact, the whole content is relatively simple, but I want to share a few points in the writing process that I found helpful to you;

1. How to achieve it

To make the most of the browser’s scrolling nature, I chose to hide the browser’s built-in scroll bar. Expose our own scrollbar styles externally;

As shown in the figure below

The visual area is called scrollBox below, the actual content is contentBox, the scrollbar is Scrollbar,

They should have the following relationships:

Scrollbar height/ScrollBox height = ScrollBox height/ContentBox height

ScrollTop/ContentBox height = scrollbar top/scrollbox height

Following the above relationship, the real scrolling effect can be completely simulated;

Event management

While writing this component, I find that we often need to write code like the following


// At some point
element.addEventListener("Event type" , () = >{
  // do something ... 
})

// At some point

element.removeEventListener("Event type", function reference)Copy the code

In business scenarios with high event frequency, especially when one event is listening to a callback triggered by another event, managing events can be mentally taxing, and in general, writing code can be difficult; At this time, we can choose to write a class to save the application, just register, no matter cancel, finally cancel, this way will greatly reduce our mental burden; Let’s see;

export class ScrollEvent {
  constructor(el) {
    this.el = el;
    this.events = {}
  }

  listen (type, fn) {
    (this.events[type] || (this.events[type] = [])).push(fn);
    this.el.addEventListener(type, fn, { passive: false })
  }

  remove (type) {
    const callbacks = this.events[type]
    callbacks && callbacks.forEach((fn) = > {
      this.el.removeEventListener(type, fn)
    })
  }

  removeAll () {
    Object.keys(this.events).forEach(type= > {
      this.remove(type)
    })
  }

  removeThat (type, fn) {
    const callbacks = this.events[type]
    const index = callbacks.indexOf(fn)
    if (index === -1) {
      return false
    }
    this.el.removeElementListener(type, callbacks[index])
    return true}}Copy the code

Each ScrollEvent instance represents a DOM element, and all events monitored are managed in its own events.

This time even through

scrollevent.listen("Event type" , () = >{
  // to do something..
})
Copy the code

Such a function literal way to register the function, we cancel the time, also just write like this;

scrollevent.remove("Original event type")
Copy the code

Never mind whether you need to name the function;

Publish components

After a quick operation, I published this component on NPM. I recommend reading this article on how to publish this component

So you can use it like this

npm i story-scrollbar -S
Copy the code

Then register globally

// Work in progress, please check NPM's readme for details
import SScrollbar from "story-scrollbar/index"
Vue.use(SScrollbar)
Copy the code

This can be used in components

<template> <div> <h1> <div class="container"> <ul> <li V -for="item in 100" :key="item">item {{item}}</li> < / ul > < / div > < h1 > component scroll bar < / h1 > < SScrollbar class = "scroll" > < ul > < li v - for = "item in 100:" key = "item" > item {{item}} < / li > </ul> </SScrollbar> </div> </template> <script> export default { data () { return {} } } </script> <style scoped> .container { width: 300px; height: 300px; border: 1px solid #333; overflow: auto; } .scroll { width: 300px; height: 300px; border: 1px solid #333; } </style>Copy the code

Results the following

Because this is a MAC system, so it is not very obvious, if it is a Windows system, then you can obviously see the difference, and no matter in any browser, the style of the scroll bar is still relatively stable performance, and provides a style to define the style of the scroll bar for developers to customize;

If you feel good, you also want to give a star. Thank you very much. Issues are also welcome;

https://github.com/sonxiaopeng/story-scrollbar.git
Copy the code

This article was first published on April 5, 2022. It will be improved and updated continuously.

Ix. Reference materials

Firefox’s Element-UI automatically generates directories