1. Simulate the scrollbar component

<template>
    <div class="scroll-box" v-scroll>
        <div class="scroll-box-item">
            <div class="box-content">
                <slot></slot>
            </div>
            <div class="scroll-bar-y">
                <div></div>
            </div>
        </div>
        <div class="scroll-bar-x">
            <div></div>
        </div>
    </div>
</template>

<script>
    let count = 0;
    let boxTop = 0;
    let boxLeft = 0;
    let multiple = 0;
    let doc = document;


    /**
     * 滚动条计算
     * @param    content - 滚动条dom
     * @param scrollBarY - Y轴滚动条
     * @param scrollBarX - X轴滚动条
     * @param    scrollY - Y轴滚动条滑块部分
     * @param    scrollX - X轴滚动条滑块部分
     **/
    function calculation(content, scrollBarY, scrollBarX, scrollY, scrollX) {
        // Y轴判断   scrollHeight小于等于clientHeight不显示滚动条
        if (content.clientHeight >= content.scrollHeight) {
            scrollBarY.style.display = 'none';
        } else if (scrollBarY.style.display === 'none') {
            scrollBarY.style.display = 'block';
        }

        // 设置X轴滚动条的长度
        scrollBarX.style.width = `${content.clientWidth}px`;

        // X轴判断  scrollWidth 小于等于 clientWidth 不显示滚动条
        if (content.clientWidth >= content.scrollWidth) {
            scrollBarX.style.display = 'none';
            let scrollMain = doc.querySelector('.scroll-main');
            if(scrollMain){
                doc.querySelector('.scroll-main').style.paddingBottom = '0';
            }
        } else if (scrollBarX.style.display === 'none') {
            scrollBarX.style.display = 'block';
            // 出现X轴滚动条时加内边距,避免遮挡到最后一个元素的X轴滚动条
            doc.querySelector('.scroll-main').style.paddingBottom = '10px';
        }

        // 计算滚动条内滑块的长度
        let numH = content.clientHeight * (content.clientHeight / content.scrollHeight);
        let numW = content.clientWidth * (content.clientWidth / content.scrollWidth);

        scrollY.style.height = `${numH}px`;
        scrollX.style.width = `${numW}px`;
    }

    /**
     * 滚动条事件
     * @param        box - 外层.box
     * @param    content - 滚动条dom
     * @param scrollBarY - Y轴滚动条
     * @param scrollBarX - X轴滚动条
     * @param    scrollY - Y轴滚动条滑块部分
     * @param    scrollX - X轴滚动条滑块部分
     * @see  calculation - 滚动条计算
     **/
    function scrollbarEvents(box, content, scrollBarY, scrollBarX, scrollY, scrollX) {
        calculation(content, scrollBarY, scrollBarX, scrollY, scrollX);

        /**
         * 滚动条滚动事件
         **/
        content.addEventListener('scroll', function () {
            scrollY.style.transform = `translateY(${Math.round(this.scrollTop / scrollBarY.clientHeight * 10000) / 100.00}%)`;
            scrollX.style.transform = `translateX(${Math.round(this.scrollLeft / scrollBarX.clientWidth * 10000) / 100.00}%)`;
        });

        /**
         * 鼠标Y轴移动事件
         **/
        function mouseEventsY(e) {
            // e.clientY - boxTop 计算鼠标在元素上的位置,用以适应被其他元素包裹
            // (计算鼠标在元素上的位置 * multiple) 滚动条距离顶部偏移量(等于百分比转换前的数值)
            // count - 滚动条距离顶部偏移量 = 滚动条移动时保证鼠标一直在点击的位置
            // 移动距离
            content.scrollTop = ((e.clientY - boxTop) * multiple) - count
        }

        /**
         * 鼠标X轴移动事件
         **/
        function mouseEventsX(e) {
            // e.clientX - boxLeft 计算鼠标在元素上的位置,用以适应被其他元素包裹
            // (计算鼠标在元素上的位置 * multiple) 滚动条距离左侧偏移量(等于百分比转换前的数值)
            // count - 滚动条距离左侧偏移量 = 滚动条移动时保证鼠标一直在点击的位置
            // 移动距离
            content.scrollLeft = ((e.clientX - boxLeft) * multiple) - count;
        }

        /**
         * 鼠标点击事件
         * @see mouseEventsY
         **/
        scrollY.onmousedown = function (e) {
            // 禁止选中文字
            doc.onselectstart = function () {
                return false;
            };

            // .box 距离页面顶部的距离
            boxTop = box.getBoundingClientRect().top;
            // 滚动条移动倍数
            multiple = content.scrollHeight / content.clientHeight;
            // 鼠标点击的位置
            count = e.offsetY * multiple;

            // .box添加鼠标移动事件
            box.addEventListener('mousemove', mouseEventsY);
        };

        /**
         * 鼠标点击事件
         * @see mouseEventsX
         **/
        scrollX.onmousedown = function (e) {
            // 禁止选中文字
            doc.onselectstart = function () {
                return false;
            };

            // .box 距离页面顶部的距离
            boxLeft = box.getBoundingClientRect().left;
            // 滚动条移动倍数
            multiple = content.scrollWidth / content.clientWidth;
            // 鼠标点击的位置
            count = e.offsetX * multiple;

            // .box添加鼠标移动事件
            box.addEventListener('mousemove', mouseEventsX);
        };

        /**
         * 松开鼠标按键
         * @see mouseEventsY
         * @see mouseEventsX
         **/
        box.onmouseup = function () {
            // 移除.box鼠标移动事件
            this.removeEventListener('mousemove', mouseEventsY);
            this.removeEventListener('mousemove', mouseEventsX);
            // 启用选中文字
            doc.onselectstart = function () {
                return true;
            }
        };

        /**
         * 鼠标离开.box
         * @see mouseEventsY
         * @see mouseEventsX
         **/
        box.onmouseleave = function () {
            // 移除.box鼠标移动事件
            this.removeEventListener('mousemove', mouseEventsY);
            this.removeEventListener('mousemove', mouseEventsX);
            // 启用选中文字
            doc.onselectstart = function () {
                return true;
            }
        }
    }

    onload = function () {
        let itemArr = doc.querySelectorAll('.scroll-box-item');
        let boxArr = doc.querySelectorAll('.scroll-box');
        let len = boxArr.length;

        boxArr[0].classList.add('scroll-main');

        calculation(itemArr[0].children[0], itemArr[0].children[1], boxArr[0].children[1], itemArr[0].children[1].children[0], boxArr[0].children[1].children[0]);

        /**
         * 监听窗口大小变化
         **/
        onresize = function () {
            for (let i = 0; i < len; i++) {
                // 重新计算Y轴滚动条位置
                itemArr[i].children[1].querySelector('div').style.transform = `translateY(${Math.round(itemArr[i].children[0].scrollTop / itemArr[i].children[1].clientHeight * 10000) / 100.00}%)`;

                scrollbarEvents(boxArr[i], itemArr[i].children[0], itemArr[i].children[1], boxArr[i].children[1], itemArr[i].children[1].children[0], boxArr[i].children[1].children[0]);
            }
        };
    };

    export default {
        name: "scroll",
        directives: {
            scroll: {
                inserted: function (el) {
                    let box = el;
                    let itemChildren = box.children[0];                  // .scroll-box-item
                    let content = itemChildren.children[0];
                    let scrollBarY = itemChildren.children[1];           // .scroll-bar-y
                    let scrollBarX = box.children[1];                    // .scroll-bar-x
                    let scrollY = itemChildren.children[1].children[0];  // .scroll-bar-y > div
                    let scrollX = box.children[1].children[0];           // .scroll-bar-x > div

                    content.scrollTop = 0;

                    setTimeout(() => {
                        scrollbarEvents(box, content, scrollBarY, scrollBarX, scrollY, scrollX);
                    });
                }
            }
        },
    }
</script>

<style scoped>
    .box-content {
        -ms-overflow-style: none; /* IE10 */
        scrollbar-width: none; /* Firefox */
        overflow: -moz-scrollbars-none; /* 旧版Firefox */
    }

    /* chrome  隐藏滚动条 */
    .box-content::-webkit-scrollbar {
        display: none
    }

    .scroll-box {
        position: relative;
    }

    .scroll-main {
        height: 100%;
    }

    .scroll-box-item {
        display: flex;
        height: 100%;
    }

    .scroll-box-item > .box-content {
        width: 99.5%;
        overflow: auto;
    }

    .scroll-bar-y, .scroll-bar-x {
        border-radius: 5px;
        background: #b1b493;
    }

    .scroll-bar-y {
        flex-shrink: 0;
        width: 8px;
    }

    .scroll-bar-x {
        position: absolute;
        bottom: 0;
        height: 8px;
        display: flex;
    }

    .scroll-bar-x > div {
        height: 100%;
    }

    .scroll-bar-y > div, .scroll-bar-x > div {
        background: #4f8a8b;
        border-radius: 5px;
    }

    .scroll-bar-y > div, .scroll-bar-x > div:active {
        cursor: pointer;
    }
</style>

Copy the code

2. Invoke the scroll bar component

<template>
    <scroll>
        <div class="container">
            <template v-for="(item,index) in arr">
                <scroll :key="index">
                    <div class="item">
                        <p>
                            {{item}}
                            aaaaaaaaaaaaaaaaaaaaaaa
                        </p>
                    </div>
                </scroll>
            </template>
        </div>
    </scroll>
</template>

<script>
    import scroll from './scroll'

    export default {
        name: "test1",
        components: {
            scroll
        },
        data() {
            return {
                arr: [1, 2, 3, 4, 5, 6, 7, 8, 9]
            }
        }
    }
</script>

<style scoped>
    .container {
        width: 800px;
    }

    .item {
        height: 200px;
        border: 1px skyblue solid;
    }

    p {
        height: 300px;
        width: 2000px;
    }
</style>
Copy the code

3. The renderings