@@ -1,12 +1,26 @@ -import { MutableRefObject, useEffect, useRef, useState } from 'react' +import { MutableRefObject } from 'react' export function useGenerallScroll(containerRef: MutableRefObject) { - function scrollToBottom() { + function scrollToBottomSmooth() { if (!containerRef.current) return - containerRef.current.style.transform = `translateY(0px)` - } + const el = containerRef.current + const start = el.scrollTop + const end = el.scrollHeight - el.clientHeight + const duration = 1800 + let startTime: number | null = null - return { - scrollToBottom, + function animateScroll(timestamp: number) { + if (!startTime) startTime = timestamp + const elapsed = timestamp - startTime + const progress = Math.min(elapsed / duration, 1) + el.scrollTop = start + (end - start) * progress + if (progress < 1) { + requestAnimationFrame(animateScroll) + } else { + el.scrollTop = end + } + } + requestAnimationFrame(animateScroll) } + return { scrollToBottomSmooth } } @@ -27,6 +27,9 @@ export function useListenersWheel( const startDragYRef = useRef(0) const startTranslateYRef = useRef(0) + const targetTranslateYRef = useRef(0) + const animationFrameScrollRef = useRef(null) + function defineDraggbleHeight() { const container = containerRef.current const { current } = scrollRef @@ -93,22 +96,43 @@ export function useListenersWheel( step() }, [applyTransform, clampTranslateY]) + function animateToTarget() { + if (!containerRef.current || !scrollRef.current) return + clampTranslateY() + const diff = targetTranslateYRef.current - translateYRef.current + if (Math.abs(diff) < 1) { + translateYRef.current = targetTranslateYRef.current + applyTransform() + animationFrameScrollRef.current = null + return + } + translateYRef.current += diff * 0.015 + clampTranslateY() + applyTransform() + animationFrameScrollRef.current = requestAnimationFrame(animateToTarget) + } + const onWheelCallback = useCallback( async (e) => { if (preventScroll.current) return + const scrollSpeed = 0.5 + const deltaY = e.deltaY * scrollSpeed - e.preventDefault() + translateYRef.current -= deltaY + clampTranslateY() + applyTransform() - const scrollSpeed = 1.5 - const deltaY = e.deltaY * scrollSpeed + if (animationFrameScrollRef.current) { + cancelAnimationFrame(animationFrameScrollRef.current) + animationFrameScrollRef.current = null + } + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current) + animationFrameRef.current = null + } - requestAnimationFrame(() => { - translateYRef.current -= deltaY - onScrolling && onScrolling(translateYRef.current) - clampTranslateY() - applyTransform() - defineDraggbleHeight() - }) + defineDraggbleHeight() + targetTranslateYRef.current = translateYRef.current }, [clampTranslateY, applyTransform] ) @@ -199,6 +223,11 @@ export function useListenersWheel( startDragYRef.current = e.clientY startTranslateYRef.current = translateYRef.current + if (animationFrameScrollRef.current) { + cancelAnimationFrame(animationFrameScrollRef.current) + animationFrameScrollRef.current = null + } + const handleMouseMove = (moveEvent: MouseEvent) => { if (!isDraggingRef.current) return @@ -211,6 +240,7 @@ export function useListenersWheel( translateYRef.current = startTranslateYRef.current - deltaY / proportion clampTranslateY() + targetTranslateYRef.current = translateYRef.current applyTransform() onScrolling && onScrolling(translateYRef.current) } @@ -227,6 +257,20 @@ export function useListenersWheel( [clampTranslateY, applyTransform] ) + function scrollToBottom() { + const container = containerRef.current + const scroll = scrollRef.current + if (!container || !scroll) return + + const maxScroll = container.clientHeight - scroll.clientHeight + targetTranslateYRef.current = -maxScroll + if (targetTranslateYRef.current > 0) targetTranslateYRef.current = 0 + clampTranslateY() + if (!animationFrameScrollRef.current) { + animationFrameScrollRef.current = requestAnimationFrame(animateToTarget) + } + } + return { onWheelCallback, preventScroll, @@ -238,5 +282,6 @@ export function useListenersWheel( handleThumbnailClick, handleThumbMouseDown, isDraggingRef, + scrollToBottom, } } @@ -1,4 +1,4 @@ -import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react' +import React, { forwardRef, useImperativeHandle, useMemo, useRef, useEffect } from 'react' import { useHovered, useListenersThumbnail, useListenersWheel } from '../model' import { useGenerallScroll } from '../model/use-generall-scroll' @@ -19,11 +19,8 @@ export interface CommonScrollbarRef extends HTMLDivElement { export const CommonScrollbar = forwardRef( ({ children, className, blockedScrollTop = false, onScrolling, ...props }, ref) => { const containerRef = useRef(null) - const localRef = useRef(null) - const { scrollToBottom } = useGenerallScroll(containerRef) - const { draggbleRef } = useListenersThumbnail() const { onMounseEnter, onMouseLeave, hovered } = useHovered() @@ -36,6 +33,7 @@ export const CommonScrollbar = forwardRef translateYRef.current, [translateYRef]) @@ -45,6 +43,21 @@ export const CommonScrollbar = forwardRef { + const node = localRef.current + if (!node) return + + function wheelHandler(e: WheelEvent) { + e.preventDefault() + onWheelCallback(e as any) + } + node.addEventListener('wheel', wheelHandler, { passive: false }) + + return () => { + node.removeEventListener('wheel', wheelHandler) + } + }, [onWheelCallback]) + return (
+