自定义滚动条+无限加载

在这里插入图片描述

使用展示

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;
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值