使用展示
import React, { useState, useEffect } from 'react';
const Panel = () => {
const [scrollList, setScrollList] = useState<Array<string | number>>([]);
// 滚动条加载-滚动条回弹完成持续加载可在checkScroll中完成,暂时懒得加
function scrollFn(top, bottom) {
if (bottom) setScrollList([...scrollList, '得到']);
if (top) setScrollList(['失去', ...scrollList]);
}
return (
<ScrollFn scrollFn={scrollFn.bind(this)} maxHeight={200}>
{scrollList.map((item, index) => {
return <p key={index}>{item}</p>;
})}
</ScrollFn>)
}
组件代码 index.tsx
import React, { useState, useEffect, useRef } from 'react';
import './index.less';
interface Iprops {
children: React.ReactNode;
height?: number;
maxHeight?: number;
scrollFn?: (top: boolean, bottom: boolean) => void;
}
let thumbDarg = false; //是否可以拖拽
let thumbTopStatic = 0; //当前滚动高度
let scrollHtStatic = 0; //滚动条区域高度
let maxScrollHtStatic = 0; //最大可滚动值
let ratio = 0; //模拟滚动与实际滚动占比
const minThumbHt = 16.5; //滚动条thumb最小值
let bgScrollHt = 0; //最大滚动高度
const ScrollFn = (props: Iprops) => {
const { children, height, maxHeight, scrollFn } = props;
const contentRef = useRef<HTMLDivElement | null>(null);
const bgRef = useRef<HTMLDivElement | null>(null);
const [thumbHeight, setThumbHeight] = useState<number>(0); //滑块高度
const [thumbTop, setThumbTop] = useState<number>(0); //滑块高度
const [scrollVisible, setScrollVisible] = useState(false); //是否有滚动条
// 滚动监听
function scrollChange() {
const dom = bgRef.current;
if (!dom) return;
const newheight = dom.scrollHeight - dom.offsetHeight;
const result = (dom.scrollTop / newheight) * (scrollHtStatic - thumbHeight);
if (result !== thumbTopStatic) setThumbTop(result);
// 是否滚动至底部/顶部
const bottomNum = dom.scrollHeight - dom.offsetHeight - dom.scrollTop;
if (scrollFn) scrollFn(result === 0, bottomNum < 3);
}
// 拖拽滚动
function dragThumbFn(e) {
let top = thumbTopStatic + e.movementY;
top = top < 0 ? 0 : top;
top = top > maxScrollHtStatic ? maxScrollHtStatic : top;
if (bgRef.current && bgRef.current.scrollTop) bgRef.current.scrollTop = top * ratio;
setThumbTop(top);
}
// fn 拖拽
function dargFn(e) {
if (thumbDarg) dragThumbFn(e);
}
// fn 取消拖拽
function cancelDargFn() {
thumbDarg = false;
}
// fn 滚动点击定位
function inderThumbFn(e) {
thumbDarg = true;
if (e.target.className.includes('scroll-thumb')) return;
setThumbTop(e.nativeEvent.layerY);
if (bgRef.current && bgRef.current.scrollTop) bgRef.current.scrollTop = e.nativeEvent.layerY * ratio;
}
// 获取滚动条高度
function checkScroll() {
bgScrollHt = bgRef.current?.scrollHeight || 0;
scrollHtStatic = bgRef.current?.clientHeight || 0;
setScrollVisible(bgScrollHt > 0);
if (bgScrollHt === 0 || bgScrollHt === scrollHtStatic) return;
let result = (scrollHtStatic / bgScrollHt) * scrollHtStatic;
result = result < minThumbHt ? minThumbHt : result;
setThumbHeight(result);
maxScrollHtStatic = scrollHtStatic - result;
ratio = (bgScrollHt - scrollHtStatic) / maxScrollHtStatic;
}
useEffect(() => {
thumbTopStatic = thumbTop;
}, [thumbTop]);
// 拖拽事件监听
useEffect(() => {
window.addEventListener('mousemove', dargFn);
window.addEventListener('mouseup', cancelDargFn);
return () => {
window.removeEventListener('mousemove', dargFn);
window.removeEventListener('mouseup', cancelDargFn);
cancelDargFn();
};
}, []);
// 子元素数量变化
useEffect(() => {
checkScroll();
}, [children]);
return (
<div className="scroll" style={{ height: `${maxHeight ? '' : height}px`, maxHeight: `${maxHeight}px` }}>
<div
className="scroll-bg"
style={{ height: `${maxHeight ? '' : height}px`, maxHeight: `${maxHeight}px` }}
ref={bgRef}
onScroll={scrollChange.bind(this)}
>
<div className="scroll-content" ref={contentRef}>
{children}
</div>
</div>
<div style={{ display: `${scrollVisible ? '' : 'none'}` }} className="scroll-bar" onMouseDown={inderThumbFn.bind(this)}>
<span
onMouseDown={inderThumbFn.bind(this)}
className="scroll-thumb"
style={{
height: `${thumbHeight}px`,
transform: `translateY(${thumbTop}px)`
}}
></span>
</div>
</div>
);
};
export default ScrollFn;
index.less
.scroll {
overflow: hidden;
width: 100%;
position: relative;
&-bg {
overflow-y: auto;
width: calc(100% + 8px);
}
&-content {
height: auto;
}
&-bar {
position: absolute;
right: 0;
top: 0;
background: transparent;
height: 100%;
width: 6px;
}
&-thumb {
position: absolute;
right: 0;
top: 0;
background-color: #909399;
opacity: 0.5;
width: 8px;
height: 30px;
border-radius: 4px;
cursor: pointer;
}
}