用react实现了navieUI的滚动条组件

145 阅读2分钟

`

"use client"

/*
* 自定义一个滚动区域
* */
import {useEffect, useRef, useState} from "react";
import _ from "lodash"
import {useMutationObserver, useSize} from "ahooks";

export default function Page(props: any) {
    // 最外层容器元素
    const wrapperRef = useRef<any>(null)
    // 滚动外层元素
    const containerRef = useRef<any>(null)
    const rongqiRef = useRef<any>(null)
    // 滚动条垂直

    // 滚动条初始位置
    const [position, setPosition] = useState({
        x: 0,
        y: 0
    })
    // 判断内容是否超出容器,进而显示滚动条
    const [showVertical, setShowVertical] = useState(false)
    const [showHorizontal, setShowHorizontal] = useState(false)
    // 垂直滑块的高度和水平滑块的宽度
    const [verticalHeight, setVerticalHeight] = useState(0)
    const [horizontalWidth, setHorizontalWidth] = useState(0)
    // 垂直滚动百分比
    const [verticalPercent, setVerticalPercent] = useState(0)
    const [horizontalPercent, setHorizontalPercent] = useState(0)
    // 滚轮事件
    const gundongFunc = (e: any) => {
        if (!containerRef.current) {
            return
        }
        containerRef.current.scrollTop = containerRef.current.scrollTop + e.deltaY / 2
    }
    // 垂直滑块拖动事件
    const tuodongFunc = (e: any) => {
        const chushiY = e.clientY
        const chushitop = containerRef.current.scrollTop
        document.onmousemove = (e2) => {
            const chushiY2 = e2.clientY
            const chazhi = chushiY2 - chushiY
            const heightDif = sizeWaibu?.height - verticalHeight
            const bili = chazhi / heightDif
            const height = sizeNeibu?.height - sizeWaibu?.height
            containerRef.current.scrollTop = chushitop + bili * height
        }
        document.onmouseup = () => {
            document.onmousemove = null
            document.onmouseup = null
        }
    }
    // 水平滑块拖动事件
    const tuodongFunc2 = (e: any) => {
        console.log("水平滚动条的拖动事件触发")
        const chushiX = e.clientX
        const chushiLeft = containerRef.current.scrollLeft
        document.onmousemove = (e2) => {
            const chushiX2 = e2.clientX
            const chazhi = chushiX2 - chushiX
            const widthDif = sizeWaibu?.width - horizontalWidth
            const bili = chazhi / widthDif
            const width = sizeNeibu?.width - sizeWaibu?.width
            containerRef.current.scrollLeft = chushiLeft + bili * width
        }
        document.onmouseup = () => {
            document.onmousemove = null
            document.onmouseup = null
        }
    }
    // 获取当前最新的位置
    const getPosition = (e: any) => {
        const data = {
            x: containerRef.current.scrollLeft,
            y: containerRef.current.scrollTop
        }
        console.log("当前的定位:", data)
        setPosition(data)
        updateScroll(data)
    }
    // 更新滑块的位置
    const updateScroll = (data) => {
        // 如果显示滚动条,更新滑块的位置
        if (showVertical && props.verticalScroll) {
            // 首先获取内容高度和视口高度的差
            const height = sizeNeibu?.height - sizeWaibu?.height
            // 获取轨道和滑块的高度差
            const heightDif = sizeWaibu?.height - verticalHeight
            setVerticalPercent(data.y / height * heightDif)
        }
        if (showHorizontal && props.horizontalScroll) {
            // 首先获取内容高度和视口高度的差
            const width = sizeNeibu?.width - sizeWaibu?.width
            // 获取轨道和滑块的高度差
            const widthDif = sizeWaibu?.width - horizontalWidth
            setHorizontalPercent(data.x / width * widthDif)
        }
    }
    // 监听内容的宽高
    const sizeNeibu = useSize(rongqiRef);
    const sizeWaibu = useSize(containerRef);
    useEffect(() => {
        console.log("触发大小改变:", sizeNeibu?.width, sizeWaibu?.width,)
        if (sizeNeibu?.width > sizeWaibu?.width) {
            setShowHorizontal(sizeNeibu?.width > sizeWaibu?.width)
            setHorizontalWidth(sizeWaibu?.width * sizeWaibu?.width / sizeNeibu?.width)
        } else {
            setShowHorizontal(sizeNeibu?.width > sizeWaibu?.width)
            setHorizontalWidth(0)
        }
        if (sizeNeibu?.height > sizeWaibu?.height) {
            setShowVertical(sizeNeibu?.height > sizeWaibu?.height)
            setVerticalHeight(sizeWaibu?.height * sizeWaibu?.height / sizeNeibu?.height)
        } else {
            setShowVertical(sizeNeibu?.height > sizeWaibu?.height)
            setVerticalHeight(0)
        }

    }, [sizeNeibu, sizeWaibu])

    // 垂直滚动条渲染
    const verticalScrollRender = () => <div
        className={"absolute top-0 right-0 bottom-0 w-8px bg-transparent overflow-hidden select-none flex justify-center"}>
        <div onMouseDown={tuodongFunc} className={"absolute right-0 w-8px bg-gray-400 rounded-full"}
             style={{height: `${verticalHeight}px`, top: `${verticalPercent}px`}}/>
    </div>
    // 水平滚动条渲染
    const horizontalScrollRender = () => <div
        className={"absolute left-0 right-0 bottom-0 h-10px bg-transparent overflow-hidden flex items-center"}>
        <div onMouseDown={tuodongFunc2} className={"absolute bottom-0 h-8px bg-blue-400 rounded-full"}
             style={{width: `${horizontalWidth}px`, left: `${horizontalPercent}px`}}/>
    </div>

    return (
        <div ref={wrapperRef} className={["relative", props.class || ""].join(" ")} style={{...(props.style || {})}}>
            <div onWheel={gundongFunc}
                 onScroll={getPosition}
                 ref={containerRef}
                 className={["w-full h-full overflow-scroll", props.contentClass || ""].join(" ")}
                 style={{scrollbarWidth: "none", ...(props.contentStyle || {})}}>
                <div ref={rongqiRef} className={"min-w-full w-fit min-h-full"}>
                    {props.children}
                </div>
            </div>
            {props.verticalScroll && verticalHeight && verticalScrollRender()}
            {props.horizontalScroll && horizontalWidth && horizontalScrollRender()}
        </div>
    )
}