js模拟滚动条

112 阅读2分钟

一、html部分

<div class="container" :style="styleContainer">
        <div class="scroll-wrapper" :style="styleVars">
            滚动内容
        </div>
        <div class="scrollbar-wrap" :style="styleScrollbar">
            <div class="scrollbar" id="scrollbar"></div>
        </div>
  </div>

二、css样式

.container {
    width: var(--wrapperWidth);
    height: var(--wrapperScrollHeight);
    position: relative;
    overflow: hidden;
    user-select: none;

    .scrollbar-wrap {
        position: absolute;
        height: var(--wrapperScrollHeight);
        width: 8px;
        top: 0;
        right: 0;
    }

    .scrollbar {
        position: absolute;
        top: 0;
        right: 0;
        width: 6px;
        background-color: #ccc;
        opacity: 0.6;
        border-radius: 5px;
        transition: opacity 0.3s;
        z-index: 1;
    }

    .scrollbar:hover {
        opacity: 1;
    }

    .scrollbar-thumb {
        width: 100%;
        background-color: #888;
        border-radius: 5px;
    }
}
.scroll-wrapper {
    overflow-y: scroll;

}

三、js部分

    get styleContainer() {
        return {
            '--wrapperWidth': this.wrapperWidth,
            '--wrapperScrollHeight': this.wrapperScrollHeight
        };
    }

    get styleScrollbar() {
        return {
            '--wrapperScrollHeight': this.wrapperScrollHeight
        };
    }
    
     /**
     * content: 滚动内容
     * scrollBar:滚动滑块
     * clientY:表示鼠标指针相对于浏览器视口(屏幕)的垂直位置。它是相对于视口左上角的坐标,以像素为单位。当鼠标在浏览器窗口中垂直移动时,clientY的值会变化。
     * offsetTop:表示当前元素的顶部边缘相对于其最近的已定位的父元素的顶部边缘的距离。(非static)
     * isLimitScroll: 是否允许滚动
     * eventTop:鼠标mousedown时记录滑块的垂直位置
     * scrollTop:鼠标mousedown时记录滑块最近的已定位的父元素的顶部边缘的距离
     * offsetHeight:元素在垂直方向上的布局高度,包括元素的内容高度、内边距和边框高度。它包括了元素的可见高度和不可见的部分(如溢出的内容或滚动条)。
     * scrollHeight:元素内容在垂直方向上的总高度,包括被隐藏的内容的高度。它包括了所有内容的高度,无论是否可见。如果元素的内容没有溢出,scrollHeight将等于offsetHeight。
     * 1.设置滑块高度
     * 2.content监听滚动事件。动态更新滚动滑块的高度
     * 3.点击滑块时记录滑块当前所在位置的垂直位置以及距离其最近的已定位的父元素的顶部边缘的距离(项目中为类名为scrollbar-wrap的元素)
     * 4.滑块进行移动时判断移动距离和移动方向,移动滑块
     */
    handleScroll() {
        const content = document.querySelector('.scroll-wrapper') as HTMLElement;
        const scrollbar = document.querySelector('.scrollbar') as HTMLElement;
        const { offsetHeight, scrollHeight } = content;
        scrollbar.style.height = (offsetHeight / scrollHeight) * 100 + '%';
        content.addEventListener('scroll', () => {
            const { scrollTop } = content;
            // @ts-ignore
            scrollbar.style.top = (scrollTop / scrollHeight) * 100 + '%';
        });
        let isLimitScroll = false;
        let eventTop = 0;
        let scrollTop = 0;
        scrollbar.addEventListener('mousedown', (e) => {
            isLimitScroll = true;
            eventTop = e.clientY;
            scrollTop = scrollbar.offsetTop;
        });
        document.addEventListener('mousemove', (e) => {
            if (!isLimitScroll) return;
            // 滚动方向
            let direction = e.clientY > eventTop ? 'down' : 'up';
            // 移动距离
            const moveLen = Math.abs(e.clientY - eventTop);
            let top = 0;
            if (direction === 'up') {
                if (scrollTop - moveLen < 0) {
                    top = 0;
                } else {
                    top = scrollTop - moveLen;
                }
            } else {
                if (scrollTop + moveLen + scrollbar.offsetHeight > offsetHeight) {
                    top = offsetHeight - scrollbar.offsetHeight;
                } else {
                    top = scrollTop + moveLen;
                }
            }
            scrollbar.style.top = top + 'px';
            const rate = top / offsetHeight;
            content.scrollTop = scrollHeight * rate;
        });
        document.addEventListener('mouseup', () => {
            isLimitScroll = false;
        });
    }