The ultimate solution for designing a Scrollbar

The ultimate solution for designing a Scrollbar

I. Introduction

I haven't updated the article for a long time, because I haven't found good materials, and the work has been handed over recently. I summarized this year's projects and found such an interesting problem to share with you;

Two, background

In the project, we often encounter the scene of the scroll bar, which generally appears in such a scene. There is a box outside, and its css property overflowis set to scrollor auto, and its content exceeds the carrying range of the box, then the horizontal direction will appear at this time. Or vertical scroll bars;

As shown in the code below:

<!DOCTYPE html>
<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>滚动示例</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) => { // 设计超出外围盒子的承载范围
      const li = document.createElement("li")
      li.innerHTML = `<h1>${'item' + i}</h1>`
      fragment.appendChild(li)
    })
    content.appendChild(fragment)
  </script>
</body>

</html>

复制代码

Results as shown below:

Google Chrome effect

WechatIMG190.png

edge browser effect

WechatIMG191.png

safari browser effect

截屏2022-04-04 下午1.03.11.png

We can clearly feel the differences between different browsers; not only that, we take Google Chrome as an example; sometimes we may think that this scroll bar style is too ugly; it seems that we want to go modify the style;

3. How to solve

For Google Chrome we can do this:

/* 将以下样式写在全局 */
::-webkit-scrollbar-track {  /* 滚动条的滑轨背景颜色 */ 
  background-color: #b46868; 
 }
::-webkit-scrollbar-thumb { /* 滑块颜色 */ 
  background-color: rgba(0, 0, 0, 0.2); 
 } 
::-webkit-scrollbar-button { /* 滑轨两头的监听按钮颜色 */
  background-color: #7c2929; 
} 
::-webkit-scrollbar-corner {  /* 横向滚动条和纵向滚动条相交处尖角的颜色 */
  background-color: black; 
}
复制代码

You can see the following effects:

截屏2022-04-04 下午1.41.41.png

It can be seen that we have successfully changed the style of the browser scroll bar, and since it is written globally, the style of all scroll bars in the project will be changed;

In fact, I often use this way to customize scroll bar styles in actual projects; but is the problem really solved? Actually not?

Because this style only -webkit-supports browsers with kernels, run the same project on Firefox or edge browsers, how about the effect?

WechatIMG191.png

It can be seen that the scroll bar has changed to its original appearance. If it is a project in a large screen scene, the aesthetics of the large screen will be destroyed directly, so we need to find a way to solve this problem!

Five, meditation

If it is an experienced developer, it is not difficult for us to think that we can write it for each browser css解决方案;

Here I will sort out the CSS properties exposed by different browsers for scroll bars;

ie browser

/* IE 浏览器 */
.scrollbar{
    /*三角箭头的颜色*/
    scrollbar-arrow-color: #fff;
    /*滚动条滑块按钮的颜色*/
    scrollbar-face-color: #0099dd;
    /*滚动条整体颜色*/
    scrollbar-highlight-color: #0099dd;
    /*滚动条阴影*/
    scrollbar-shadow-color: #0099dd;
    /*滚动条轨道颜色*/
    scrollbar-track-color: #0066ff;
    /*滚动条3d亮色阴影边框的外观颜色——左边和上边的阴影色*/
    scrollbar-3dlight-color:#0099dd;
    /*滚动条3d暗色阴影边框的外观颜色——右边和下边的阴影色*/
    scrollbar-darkshadow-color: #0099dd;
    /*滚动条基准颜色*/
    scrollbar-base-color: #0099dd;
}

复制代码

Both Google Chrome and Safari are -webkit-内核the same

/* 谷歌浏览器和safari浏览器 */
::-webkit-scrollbar-track {  /* 滚动条的滑轨背景颜色 */ 
  background-color: #b46868; 
 }
::-webkit-scrollbar-thumb { /* 滑块颜色 */ 
  background-color: rgba(0, 0, 0, 0.2); 
 } 
::-webkit-scrollbar-button { /* 滑轨两头的监听按钮颜色 */
  background-color: #7c2929; 
} 
::-webkit-scrollbar-corner {  /* 横向滚动条和纵向滚动条相交处尖角的颜色 */
  background-color: black; 
}
复制代码

The author has not found a solution for the Firefox browser for the time being; it is said that the current Firefox browser does not provide such css properties for developers to modify;

对于大部分应用似乎可以通过这种全量css的方法去解决;但是为了用户体验,我们还是希望能够有更好的解决方案;

我并不推荐以上做法的原因是因为滚动条样式在某些浏览器上用户是可以自定义修改的;此外css的方式还是不确定性因素太多,为了保证滚动条在多类浏览器下的统一性,我们还是需要一种稳定、统一、易于维护的方式;

六、绞尽脑汁

在探索的过程中,我在element-ui上找到了思路,我发现element左侧菜单栏的滚动条,无论在什么浏览器下,都能够很好的保持一致性,并且UI样式,交互都很好看;基于此,我点进去看了一下;

截屏2022-04-04 下午2.16.36.png

才发现原来element-ui并非用的浏览器内置的滚动条,而是自己实现了一个滚动条;

确实有一下几点好处:

第一:稳定,无论在任何情况下都可以保持UI的一致性,因为将对内置的滚动条样式的控制,转换为对普通div元素的样式控制,可以保证其更稳定的样式一致性

第二:可控性更强,因为css样式都是自己设计,交互也是自己设计的,无论拓展新的交互都更加容易

第三:更方便维护,对于滚动条,我们很容易想到可以将其封装成一个组件;

当然有优点就有一定的缺点,根据目前我的了解,这种方式依然无法完全依赖于css实现,因此js的消耗代价是其致命弱点

既然element-ui有这样的实现,我想他肯定有这样的组件呀,我只要使用就好了,但是,遗憾的是我并没有在element-ui找到类似的组件实现,因此我想,那就自己实现一个;综合考虑,我选择先封装一个基于vue2的scrollbar的解决方案(因为element-ui就是基于vue2的哈);

七、实现组件

这个组件的目录结构思路如下

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

复制代码

截屏2022-04-04 下午2.51.23.png

其实整个内容还是比较简单的,但是我想分享几个写的过程中,我觉得对大家有帮助的点;

1.如何实现

为了尽可能的利用浏览器滚动的原生特性,我选择将浏览器内置的滚动条,隐藏起来(遮盖住);将我们自己写的滚动条样式暴露在外部;

如下图所示

截屏2022-04-04 下午3.10.50.png

以下将可视区域称为 scrollbox 、实际内容为 contentbox 、滚动条为 scrollbar、

他们应该具备以下关系:

scrollbar的高度 / scrollbox的高度 = scrollbox的高度 / contentbox的高度

scrollbox的scrollTop / contentbox的高度 = scrollbar的top / scrollbox的高度

遵循以上关系,就能完全模拟真实的滚动效果;

2.事件管理

在书写这个组件的时候,我发现我们经常需要书写类似下面的代码


// 在某个时机
element.addEventListener("事件类型" , ()=>{
  // do something ... 
})

// 在某个时机

element.removeEventListener("事件类型" , 原函数引用)

复制代码

这种事件高频的业务场景,尤其是一个事件的监听是在另一个事件触发的回调里这种情况是,管理事件就显得比较消耗心智了,通常情况话,在写代码时总显的力不从心;这个时候,我们其实可以选择写一个保存应用的类,只管注册,而不管取消,最后统一取消,这样的方式会极大的减少我们的心智负担;我们来看一下;

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
  }
}

复制代码

每一个ScrollEvent实例都代表一个DOM元素,所监听的所有事件,管理在自己的events中;

这个时候哪怕是通过

scrollevent.listen("事件类型" , ()=>{
  // to do something..
})
复制代码

这样的函数字面量的方式去进行的注册函数,我们在取消的时候,也只用这样写就好了;

scrollevent.remove("原事件类型")
复制代码

根本就不用去管需是否需要将函数具名的问题;

八、发布组件

通过一顿操作,我将这个组件发布在了npm上,具体如何发布,我推荐看这篇文章

所以使用的时候可以这样使用

npm i story-scrollbar -S
复制代码

然后在全局中注册一下

// 目前还在完善当中,详情请关注npm的readme哈
import SScrollbar from "story-scrollbar/index"
Vue.use(SScrollbar)
复制代码

在组件中可以这样使用

<template>
  <div>
    <h1>原始滚动条</h1>
    <div class="container">
      <ul>
        <li v-for="item in 100" :key="item">item {{item}}</li>
      </ul>
    </div>
    <h1>组件滚动条</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>
复制代码

效果如下

屏幕录制2022-04-04 下午11.47.16.gif

因为我这个是mac系统,所以体现的不是很明显,如果是windows系统,那么就可以明显的看到差异了,并且无论在任何浏览器上面,滚动条的样式都还是表现的比较稳定的,并且提供了定义滚动条样式的style供开发者自定义;

下面贴上git地址:如果觉得不错,还希望给个star哈;多谢啦;也欢迎提交issues;

https://github.com/sonxiaopeng/story-scrollbar.git
复制代码

本文首发时间:2022年04月05日,后续将不断完善和更新;

九、参考资料

火狐浏览器
element-ui
自动生成目录

Guess you like

Origin juejin.im/post/7082786038816440333