Skip to content

Commit 19479f5

Browse files
committed
feat(desktop): integrate useSmoothScroll hook for enhanced scrolling
- Introduced a new `useSmoothScroll` hook to encapsulate smooth scrolling logic, improving code reusability. - Updated `EntryScrollArea` to utilize the new hook, replacing the previous inline smooth scroll implementation. - Exported the `useSmoothScroll` hook from the hooks index for broader accessibility. Signed-off-by: Innei <[email protected]>
1 parent 6c73ae5 commit 19479f5

File tree

3 files changed

+71
-62
lines changed

3 files changed

+71
-62
lines changed

apps/desktop/layer/renderer/src/modules/entry-content/index.electron.tsx

Lines changed: 4 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { MotionButtonBase } from "@follow/components/ui/button/index.js"
88
import { RootPortal } from "@follow/components/ui/portal/index.js"
99
import { ScrollArea } from "@follow/components/ui/scroll-area/index.js"
1010
import type { FeedViewType } from "@follow/constants"
11-
import { useTitle } from "@follow/hooks"
11+
import { useSmoothScroll, useTitle } from "@follow/hooks"
1212
import type { FeedModel, InboxModel } from "@follow/models/types"
1313
import { nextFrame, stopPropagation } from "@follow/utils/dom"
1414
import { EventBus } from "@follow/utils/event-bus"
@@ -17,7 +17,7 @@ import { ErrorBoundary } from "@sentry/react"
1717
import type { JSAnimation, Variants } from "motion/react"
1818
import { AnimatePresence, m, useAnimationControls } from "motion/react"
1919
import * as React from "react"
20-
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
20+
import { useEffect, useMemo, useRef, useState } from "react"
2121

2222
import { useEntryIsInReadability } from "~/atoms/readability"
2323
import { useIsZenMode, useUISettingKey } from "~/atoms/settings/ui"
@@ -247,7 +247,7 @@ const EntryScrollArea: Component<{
247247
}
248248
return (
249249
<ScrollArea.ScrollArea
250-
focusable={false}
250+
focusable
251251
mask={false}
252252
rootClassName={cn(
253253
"h-0 min-w-0 grow overflow-y-auto print:h-auto print:overflow-visible",
@@ -346,65 +346,7 @@ const RegisterCommands = ({
346346
})
347347

348348
const { highlightBoundary } = useFocusActions()
349-
350-
// Smooth scroll implementation similar to Vimium
351-
const smoothScrollTo = useCallback(
352-
(targetScrollTop: number, element: HTMLDivElement) => {
353-
// Stop any existing animation
354-
if (scrollAnimationRef.current) {
355-
scrollAnimationRef.current.stop()
356-
}
357-
358-
const startScrollTop = element.scrollTop
359-
const distance = targetScrollTop - startScrollTop
360-
361-
// If distance is very small, just set it directly
362-
if (Math.abs(distance) < 1) {
363-
element.scrollTop = targetScrollTop
364-
scrollAnimationRef.current = null
365-
return
366-
}
367-
368-
// Adaptive duration based on distance - shorter for small distances, longer for large ones
369-
const baseDuration = 150
370-
const maxDuration = 300
371-
const duration = Math.min(maxDuration, baseDuration + Math.abs(distance) * 0.5)
372-
const startTime = performance.now()
373-
374-
// Easing function similar to Vimium's smooth scrolling - ease out cubic for natural feel
375-
const easeOutCubic = (t: number): number => {
376-
return 1 - Math.pow(1 - t, 3)
377-
}
378-
379-
let animationId: number
380-
381-
const animateScroll = (currentTime: number) => {
382-
const elapsed = currentTime - startTime
383-
const progress = Math.min(elapsed / duration, 1)
384-
385-
const easedProgress = easeOutCubic(progress)
386-
const currentScrollTop = startScrollTop + distance * easedProgress
387-
388-
element.scrollTop = currentScrollTop
389-
390-
if (progress < 1) {
391-
animationId = requestAnimationFrame(animateScroll)
392-
scrollAnimationRef.current = {
393-
stop: () => {
394-
cancelAnimationFrame(animationId)
395-
scrollAnimationRef.current = null
396-
},
397-
} as any
398-
} else {
399-
scrollAnimationRef.current = null
400-
}
401-
}
402-
403-
animationId = requestAnimationFrame(animateScroll)
404-
},
405-
[scrollAnimationRef],
406-
)
407-
349+
const smoothScrollTo = useSmoothScroll()
408350
useEffect(() => {
409351
const checkScrollBottom = ($scroller: HTMLDivElement) => {
410352
const currentScroll = $scroller.scrollTop

packages/internal/hooks/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export * from "./usePageVisibility"
1313
export * from "./usePrevious"
1414
export * from "./useRefValue"
1515
export * from "./useSetState"
16+
export * from "./useSmoothScroll"
1617
export * from "./useSyncTheme"
1718
export * from "./useTitle"
1819
export * from "./useTriangleMenu"
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { useCallback, useRef } from "react"
2+
3+
/**
4+
*
5+
* Smooth scroll implementation similar to Vimium
6+
*/
7+
export const useSmoothScroll = () => {
8+
const scrollAnimationRef = useRef<{ stop: () => void } | null>(null)
9+
10+
return useCallback(
11+
(targetScrollTop: number, element: HTMLDivElement) => {
12+
// Stop any existing animation
13+
if (scrollAnimationRef.current) {
14+
scrollAnimationRef.current.stop()
15+
}
16+
17+
const startScrollTop = element.scrollTop
18+
const distance = targetScrollTop - startScrollTop
19+
20+
// If distance is very small, just set it directly
21+
if (Math.abs(distance) < 1) {
22+
element.scrollTop = targetScrollTop
23+
scrollAnimationRef.current = null
24+
return
25+
}
26+
27+
// Adaptive duration based on distance - shorter for small distances, longer for large ones
28+
const baseDuration = 150
29+
const maxDuration = 300
30+
const duration = Math.min(maxDuration, baseDuration + Math.abs(distance) * 0.5)
31+
const startTime = performance.now()
32+
33+
// Easing function similar to Vimium's smooth scrolling - ease out cubic for natural feel
34+
const easeOutCubic = (t: number): number => {
35+
return 1 - Math.pow(1 - t, 3)
36+
}
37+
38+
let animationId: number
39+
40+
const animateScroll = (currentTime: number) => {
41+
const elapsed = currentTime - startTime
42+
const progress = Math.min(elapsed / duration, 1)
43+
44+
const easedProgress = easeOutCubic(progress)
45+
const currentScrollTop = startScrollTop + distance * easedProgress
46+
47+
element.scrollTop = currentScrollTop
48+
49+
if (progress < 1) {
50+
animationId = requestAnimationFrame(animateScroll)
51+
scrollAnimationRef.current = {
52+
stop: () => {
53+
cancelAnimationFrame(animationId)
54+
scrollAnimationRef.current = null
55+
},
56+
} as any
57+
} else {
58+
scrollAnimationRef.current = null
59+
}
60+
}
61+
62+
animationId = requestAnimationFrame(animateScroll)
63+
},
64+
[scrollAnimationRef],
65+
)
66+
}

0 commit comments

Comments
 (0)