The
component does not appear in the corresponding documentation, so the use of this component is a matter of trial and error, with some frustrating problems such as:
- Is the scrolling size set on the component or on the parent container or on the container within the component?
- Additional definitions are required for native scrollbars caused by confusion about the width and height of the container
overflow-x: hidden
oroverflow-y: hidden
Force removal. - The width and height of an elastic layout do not automatically match scrolling.
< el-Scrollbar ><div> Dynamically add nodes or dynamically change width and height </div>< el-Scrollbar >
The scrollbar size is not updated.
For the above questions, and so on using sensory not friendly (maybe is the reason why did not appear in the document), but does not represent the scroll component design is not good, on the contrary, personal use quite agree with this node to simulate the method of the scroll bar to flatten out on each browser compatible with the style, so manually to achieve a relatively good, fit the wheels of the scene is perfect.
Code address: vue 3 x | vue 2 x
Preview effect: Custom scrollbar components
layout
Although it was a reference to < el-Scrollbar >, I thought it could be simplified, so I didn’t copy it and implemented it my own way; Take a look at the final box layout:
Briefly speaking about the layout, there are 4 nodes in total: overall box, content wrap box, horizontal scroll button, vertical scroll button;
- The overall box size automatically ADAPTS according to the width and height of the parent container, and then exceeds the hidden (key) : 100% width and height can be;
- The content package box is also 100% wide and high, but add the width of the scroll bar to the right and bottom, and then set
overflow: scroll
, the scrollbar will always appear, and since the parent container is out of hiding, it is not visually visible. - The last two horizontal and vertical scroll buttons can be dynamically set according to the size of the scroll bar.
Basic HTML fragments
<div class="the-scrollbar">
<div class="the-scrollbar-wrap">
<slot></slot>
</div>
<button class="the-scrollbar-thumb" title="Scroll bar -- hold and drag the Y-axis."></button>
<button class="the-scrollbar-thumb" title="Scroll bar -- hold and drag the X-axis."></button>
</div>
Copy the code
.the-scrollbar {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
.the-scrollbar-wrap {
overflow: scroll;
}
.the-scrollbar-thumb {
position: absolute;
z-index: 10;
outline: none;
border: none; }}Copy the code
Scroll bar thickness acquisition
< span style = “box-sizing: border-box; color: RGB (74, 74, 74); line-height: 20px; font-size: 14px! Important; word-break: inherit! Important;”
/** The thickness of the scroll bar */
const scrollbarSize = (function() {
const el = document.createElement("div");
el.style.width = "100px";
el.style.height = "100px";
el.style.overflow = "scroll";
document.body.appendChild(el);
const width = el.offsetWidth - el.clientWidth;
el.remove();
returnwidth; }) ();Copy the code
When to update the style of the virtual scrollbar
Here I choose to check the size of the the-scrollbar-wrap and set the trigger condition of the the-scrollbar-thumb style:
- The mouse moves out and in, updating the style and display as it moves in
the-scrollbar-thumb
, hidden when the mouse moves outthe-scrollbar-thumb
. addEventListener("scroll")
, updates when scrolling.document.addEventListener("mousedown")
,document.addEventListener("mousemove")
,document.addEventListener("mouseup")
; Monitor mouse movement events, drag and dropthe-scrollbar-thumb
Also update the setting style, and also calculate the distance of movement, here the distance of movement has a detail, is to the currentthe-scrollbar-wrap
To calculate the final distance traveled.
That’s basically all there is to deal with, and the rest is a little bit of detail that I won’t go into here.
Final snippet
<template>
<div class="the-scrollbar" ref="el" @mouseenter="onEnter()" @mouseleave="onLeave()">
<div ref="wrap" class="the-scrollbar-wrap" :style="wrapStyle">
<slot></slot>
</div>
<transition name="fade">
<button
class="the-scrollbar-thumb"
ref="thumbY"
title="Scroll bar -- hold and drag the Y-axis."
:style="thumbStyle.y"
v-show="showThumb"
></button>
</transition>
<transition name="fade">
<button
class="the-scrollbar-thumb"
ref="thumbX"
title="Scroll bar -- hold and drag the X-axis."
:style="thumbStyle.x"
v-show="showThumb"
></button>
</transition>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref, reactive, onUnmounted } from "vue";
/** The thickness of the scroll bar */
const scrollbarSize = (function() {
const el = document.createElement("div");
el.style.width = "100px";
el.style.height = "100px";
el.style.overflow = "scroll";
document.body.appendChild(el);
const width = el.offsetWidth - el.clientWidth;
el.remove();
returnwidth; }) ();/** * scrollbar component */
export default defineComponent({
name: "Scrollbar".props: {
/** Scroll bar color */
thumbColor: {
type: String.default: "Rgba (147, 147, 153, 0.45)"
},
/** Scroll bar thickness */
thumbSize: {
type: Number.default: 8
},
/** * When there is an internal click event, the delay for updating the scroll bar is 0, in milliseconds * - Usage scenario: when there is an internal child node size change to expand the scroll size of the wrapper, and there is an animation, then the delay is set to the animation duration */
clickUpdateDelay: {
type: Number.default: 0}},setup(props) {
/** Component whole node */
const el = ref<HTMLElement>();
/** the enclosing node */
const wrap = ref<HTMLElement>();
/** scroll bar node X */
const thumbX = ref<HTMLElement>();
/** scroll bar node Y */
const thumbY = ref<HTMLElement>();
/** Enclosing node style */
const wrapStyle = reactive({
height: "".width: ""
})
/** Scrollbar node style */
const thumbStyle = reactive({
x: {
width: "".height: "".left: "".bottom: "".transform: "".borderRadius: "".backgroundColor: props.thumbColor
},
y: {
width: "".height: "".top: "".right: "".transform: "".borderRadius: "".backgroundColor: props.thumbColor
}
})
const showThumb = ref(false);
/** * Update the package container style * -!! * 'box-sizing: border-box' (CSS); box-sizing: border-box (CSS); The principle is the same */
function updateWrapStyle() {
constparent = el.value! .parentNodeas HTMLElement;
parent.style.overflow = "hidden"; // The parent element must be set beyond hiding, otherwise the elastic box layout will spread out
const css = getComputedStyle(parent);
// console.log(" Parent border size >>", CSS.borderLeftwidth, CSs.borderRightwidth, CSs.borderTopWidth, CSs.borderBottomWidth);
wrapStyle.width = `calc(100% + ${scrollbarSize}px + ${css.borderLeftWidth} + ${css.borderRightWidth}) `;
wrapStyle.height = `calc(100% + ${scrollbarSize}px + ${css.borderTopWidth} + ${css.borderBottomWidth}) `;
}
/** Initializes the scroll indicator style */
function initThumbStyle() {
thumbStyle.y.right = thumbStyle.y.top = "0px";
thumbStyle.y.width = props.thumbSize + "px";
thumbStyle.x.bottom = thumbStyle.x.left = "0px";
thumbStyle.x.height = props.thumbSize + "px";
thumbStyle.x.borderRadius = thumbStyle.y.borderRadius = `${props.thumbSize / 2}px`;
}
/** * Updates the scroll indicator style * - can be called externally actively */
function updateThumbStyle() {
const wrapEl = wrap.value;
if (wrapEl) {
let height = wrapEl.clientHeight / wrapEl.scrollHeight * 100;
if (height >= 100) {
height = 0;
}
thumbStyle.y.height = height + "%";
thumbStyle.y.transform = `translate3d(0, ${wrapEl.scrollTop / wrapEl.scrollHeight * wrapEl.clientHeight}px, 0)`;
// console.log("scrollWidth >>", wrapEl.scrollWidth);
// console.log("scrollLeft >>", wrapEl.scrollLeft);
// console.log("clientWidth >>", wrapEl.clientWidth);
// console.log("offsetWidth >>", wrapEl.offsetWidth);
let width = (wrapEl.clientWidth / wrapEl.scrollWidth) * 100;
if (width >= 100) {
width = 0;
}
thumbStyle.x.width = width + "%";
thumbStyle.x.transform = `translate3d(${wrapEl.scrollLeft / wrapEl.scrollWidth * wrapEl.clientWidth}px, 0, 0)`;
// console.log("------------------------------------");}}/** Whether to press start drag */
let isDrag = false;
/** Vertical mode */
let vertical = false;
/** The offset when pressing the scroll bar */
let deviation = 0;
/** Update delay */
let timer: NodeJS.Timeout;
function onDragStart(event: MouseEvent) {
// console.log(" press >>", event);
const_thumbX = thumbX.value! ;const_thumbY = thumbY.value! ;const target = event.target as HTMLElement;
if (_thumbX.contains(target)) {
isDrag = true;
vertical = false;
deviation = event.clientX - _thumbX.getBoundingClientRect().left;
}
if (_thumbY.contains(target)) {
isDrag = true;
vertical = true; deviation = event.clientY - _thumbY.getBoundingClientRect().top; }}function onDragMove(event: MouseEvent) {
if(! isDrag)return;
// console.log(" drag move >>", event.offsety, event.clienty, event);
constwrapEl = wrap.value! ;if (vertical) {
const wrapTop = wrapEl.getBoundingClientRect().top;
const wrapHeight = wrapEl.clientHeight;
let value = event.clientY - wrapTop;
wrapEl.scrollTop = (value - deviation) / wrapHeight * wrapEl.scrollHeight;
} else {
const wrapLeft = wrapEl.getBoundingClientRect().left;
const wrapWidth = wrapEl.clientWidth;
letvalue = event.clientX - wrapLeft; wrapEl.scrollLeft = (value - deviation) / wrapWidth * wrapEl.scrollWidth; }}function onDragEnd(event: MouseEvent) {
// console.log(" lift ");
isDrag = false;
if(el.value! .contains(event.targetas HTMLElement)) {
if (props.clickUpdateDelay > 0) {
// console.log(" execute ");
timer && clearTimeout(timer);
timer = setTimeout(updateThumbStyle, props.clickUpdateDelay); }}else {
showThumb.value = false; }}function onEnter() {
showThumb.value = true;
updateThumbStyle();
}
function onLeave() {
if(! isDrag) { showThumb.value =false;
}
}
onMounted(function() {
// console.log("onMounted >>", el.value! .clientHeight);
// console.log("scrollbarSize >>", scrollbarSize);
updateWrapStyle();
initThumbStyle();
wrap.value && wrap.value.addEventListener("scroll", updateThumbStyle);
document.addEventListener("mousedown", onDragStart);
document.addEventListener("mousemove", onDragMove);
document.addEventListener("mouseup", onDragEnd);
});
onUnmounted(function() {
wrap.value && wrap.value.removeEventListener("scroll", updateThumbStyle);
document.removeEventListener("mousedown", onDragStart);
document.removeEventListener("mousemove", onDragMove);
document.removeEventListener("mouseup", onDragEnd);
timer && clearTimeout(timer);
});
return {
el,
wrap,
thumbX,
thumbY,
wrapStyle,
thumbStyle,
showThumb,
updateThumbStyle,
onEnter,
onLeave
}
}
})
</script>
<style lang="scss">
.the-scrollbar {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
.the-scrollbar-wrap {
overflow: scroll;
}
.the-scrollbar-thumb {
position: absolute;
z-index: 10;
outline: none;
border: none; }}</style>
Copy the code
It is also very simple to use, directly define the width of the parent container to do scrolling; Now you can use flex: 1, max-width, max-height for adaptive layout. There is also a detail called props. ClickUpdateDelay that is used to change the scroll size of a scroll component because there is no updateThumbStyle trigger. Current, can be externally in a manual Scrollbar. UpdateThumbStyle () operation to achieve an asynchronous update the scroll bar.