Skip to content

Commit ada670a

Browse files
committed
fix: Lock background scroll when image viewer and setting is open
Preserve scroll position while preventing background scroll on image viewer and setting open
1 parent 039619d commit ada670a

3 files changed

Lines changed: 51 additions & 4 deletions

File tree

src/components/ImageViewer.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {cn, createRawImageUrl, getMcmetaPath} from '@/utils'
1616
import {saveAs} from 'file-saver'
1717
import type {GithubRepo} from '@/utils'
1818
import {useSettingStore} from '@/stores/settingStore'
19+
import {useScrollLock} from '@/hooks/useScrollLock'
1920

2021
// Format file size to human-readable format
2122
function formatFileSize(bytes: number): string {
@@ -83,6 +84,8 @@ export function ImageViewer({
8384
? currentImage.substring(0, currentImage.lastIndexOf('/'))
8485
: null
8586

87+
useScrollLock(open)
88+
8689
const handleDownloadCurrent = useCallback(async () => {
8790
try {
8891
const url = createRawImageUrl(repo, currentImage)
@@ -731,7 +734,7 @@ export function ImageViewer({
731734
<Button
732735
variant="outline"
733736
size="icon"
734-
className="size-14 min-w-14 flex-shrink-0"
737+
className="size-14 min-w-14 shrink-0"
735738
onClick={handlePrevious}
736739
aria-label="Previous image">
737740
<ChevronLeft className="size-10" />
@@ -749,7 +752,7 @@ export function ImageViewer({
749752
<Button
750753
variant="ghost"
751754
size="icon"
752-
className="size-14 min-w-14 overlay-button flex-shrink-0"
755+
className="size-14 min-w-14 overlay-button shrink-0"
753756
onClick={handleNext}
754757
aria-label="Next image">
755758
<ChevronRight className="size-10" />
@@ -797,7 +800,7 @@ export function ImageViewer({
797800
type="button"
798801
size="sm"
799802
variant="outline"
800-
className="h-8 px-3 py-1 text-sm font-semibold flex-shrink-0"
803+
className="h-8 px-3 py-1 text-sm font-semibold shrink-0"
801804
onClick={handleDownloadCurrent}
802805
aria-label="Download current image">
803806
<Download className="size-4" />

src/components/SettingButton.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ import {
2222
} from '@/stores/settingStore'
2323
import {useGithubRateLimitStore} from '@/stores/githubApiStore'
2424
import {useState, useMemo} from 'react'
25+
import {useScrollLock} from '@/hooks/useScrollLock'
2526

2627
export function SettingButton() {
2728
const settings = useSettingStore()
2829
const rateLimit = useGithubRateLimitStore()
30+
const [isOpen, setIsOpen] = useState(false)
2931
const [githubToken, setGithubToken] = useState('')
3032
const [columnCount, setColumnCount] = useState(0)
3133
const [pixelated, setPixelated] = useState(true)
@@ -79,6 +81,7 @@ export function SettingButton() {
7981
}
8082

8183
const handleOpenChange = (open: boolean) => {
84+
setIsOpen(open)
8285
// Reset the values if the dialog is opened
8386
if (open) {
8487
const initial = {
@@ -99,8 +102,11 @@ export function SettingButton() {
99102
}
100103
}
101104

105+
// 배경 스크롤 잠금 (설정 다이얼로그 열릴 때)
106+
useScrollLock(isOpen)
107+
102108
return (
103-
<Dialog onOpenChange={handleOpenChange}>
109+
<Dialog onOpenChange={handleOpenChange} modal={false}>
104110
<DialogTrigger asChild>
105111
<Button
106112
aria-label="Settings"

src/hooks/useScrollLock.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {useEffect, useRef} from 'react'
2+
3+
/**
4+
* Lock background scroll while preserving current scroll position.
5+
*/
6+
export function useScrollLock(active: boolean) {
7+
const scrollYRef = useRef(0)
8+
9+
useEffect(() => {
10+
if (!active) return
11+
12+
const scrollY =
13+
window.scrollY ||
14+
window.pageYOffset ||
15+
document.documentElement.scrollTop ||
16+
document.body.scrollTop ||
17+
0
18+
scrollYRef.current = scrollY
19+
20+
const originalBodyPosition = document.body.style.position
21+
const originalBodyTop = document.body.style.top
22+
const originalBodyWidth = document.body.style.width
23+
const originalBodyOverflowY = document.body.style.overflowY
24+
25+
document.body.style.position = 'fixed'
26+
document.body.style.top = `-${scrollY}px`
27+
document.body.style.width = '100%'
28+
document.body.style.overflowY = 'hidden'
29+
30+
return () => {
31+
document.body.style.position = originalBodyPosition
32+
document.body.style.top = originalBodyTop
33+
document.body.style.width = originalBodyWidth
34+
document.body.style.overflowY = originalBodyOverflowY
35+
window.scrollTo(0, scrollYRef.current)
36+
}
37+
}, [active])
38+
}

0 commit comments

Comments
 (0)