Skip to content

fix: (QA/3) dim scroll 문제 해결#368

Merged
heesunee merged 2 commits intodevelopfrom
fix/#367/dim-scroll
Sep 7, 2025
Merged

fix: (QA/3) dim scroll 문제 해결#368
heesunee merged 2 commits intodevelopfrom
fix/#367/dim-scroll

Conversation

@yeeeww
Copy link
Contributor

@yeeeww yeeeww commented Sep 7, 2025

#️⃣ Related Issue

Closes #367

💎 PR Point

  1. 스크롤 방지 유틸리티 함수 구현 (src/shared/utils/scroll-lock.ts)
export const handleScrollLock = (isOpen: boolean) => {
  if (isOpen) {
    document.body.style.overflow = 'hidden';
    document.body.style.touchAction = 'none';
  } else {
    document.body.style.removeProperty('overflow');
    document.body.style.removeProperty('touch-action');
  }
};

overflow: hidden: 스크롤바를 완전히 숨기고 스크롤 방지
touchAction: none: 모바일에서 모든 터치 제스처(스크롤, 줌, 팬 등) 비활성화
removeProperty(): 스타일 속성을 완전히 제거하여 원래 상태로 복원

  1. BottomSheet 컴포넌트에 스크롤 방지 적용
handleScrollLock(isOpen);
  1. 홈 페이지 모달에 스크롤 방지 적용
handleScrollLock(needsMatchingSetup);

📸 Screenshot

개발자 모드에서는 확인이 불가해서 배포하고 폰으로 확인 해봐야 할 것 같아요ㅜㅜ

Summary by CodeRabbit

  • 신기능
    • 바텀시트가 열려 있을 때 페이지 배경 스크롤이 자동으로 잠겨 사용자 인터랙션이 안정화되었습니다.
    • 홈 화면에서도 특정 설정 단계에서 스크롤 잠금을 적용해 의도치 않은 화면 이동을 방지합니다.

@coderabbitai
Copy link

coderabbitai bot commented Sep 7, 2025

Walkthrough

홈과 바텀시트에서 상태에 따라 스크롤을 잠그는 유틸리티(handleScrollLock)를 도입하고, Home과 BottomSheet 컴포넌트에서 호출하도록 연결했습니다. 새 유틸 함수는 body 스타일의 overflow/touch-action을 토글합니다. 공개 API 시그니처 변경은 없습니다.

Changes

Cohort / File(s) Change Summary
Scroll lock 유틸 추가
src/shared/utils/scroll-lock.ts
handleScrollLock(isOpen: boolean) 신규 추가. document.body.style.overflowtouchAction을 토글해 스크롤 잠금/해제.
Home 연동
src/pages/home/home.tsx
handleScrollLock 임포트 및 needsMatchingSetup 값에 기반한 호출 추가.
BottomSheet 연동
src/shared/components/bottom-sheet/bottom-sheet.tsx
handleScrollLock(isOpen) 호출 추가로 열림 상태에 따라 스크롤 잠금 동작 연동.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant Home as Home Component
  participant BS as BottomSheet
  participant SL as handleScrollLock
  participant DOM as document.body

  rect rgba(230, 240, 255, 0.6)
    Note over Home: 홈 최초 진입/상태 변화
    U->>Home: 진입/상태 계산
    Home->>SL: handleScrollLock(needsMatchingSetup)
    SL->>DOM: set/remove overflow,touch-action
  end

  rect rgba(230, 255, 230, 0.6)
    Note over BS: 바텀시트 열림/닫힘
    U->>BS: Open/Close
    BS->>SL: handleScrollLock(isOpen)
    SL->>DOM: set/remove overflow,touch-action
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
홈 최초 진입 시 딤 영역 스크롤 고정 (#367) needsMatchingSetup가 딤 노출 조건과 정확히 매핑되는지 불명확. 렌더 시점 호출의 SSR/초기 마운트 조건도 확인 필요.
경기 바텀시트 열림 시 스크롤 고정 (#367)
캘린더 바텀시트 열림 시 스크롤 고정 (#367)

Assessment against linked issues: Out-of-scope changes

(해당 없음)

Suggested labels

희선

Suggested reviewers

  • Dubabbi
  • bongtta

Poem

새벽 바람, 딤이 내려 살포시,
토끼 발끝, 스크롤을 멈춰 보지. 🐇
홈엔 살짝, 시트엔 또렷이,
overflow 잠그고, 꿈길로 또르르—
밤하늘 별도 touch-action 쉬-익!

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/#367/dim-scroll

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the 예원 label Sep 7, 2025
@yeeeww yeeeww changed the title fix(QA/3): dim scroll 문제 해결 fix: (QA/3) dim scroll 문제 해결 Sep 7, 2025
@github-actions
Copy link

github-actions bot commented Sep 7, 2025

MATEBALL-STORYBOOK
⚾ Storybook 배포가 완료되었습니다!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/shared/utils/scroll-lock.ts (1)

1-9: 접근성 고려: pinch-zoom 차단 최소화 검토

body에 touch-action: none은 핀치 줌까지 막습니다. 필요 최소 범위(예: 오버레이/시트 래퍼)에 한정하거나 manipulation/pan-y 등으로 완화할지 검토해 주세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84c4226 and 345ea69.

📒 Files selected for processing (3)
  • src/pages/home/home.tsx (2 hunks)
  • src/shared/components/bottom-sheet/bottom-sheet.tsx (2 hunks)
  • src/shared/utils/scroll-lock.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-29T18:02:42.616Z
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#7
File: src/shared/routes/Router.tsx:4-4
Timestamp: 2025-06-29T18:02:42.616Z
Learning: The Home component file in the MATEBALL-CLIENT project was renamed from Home.tsx to home.tsx, so the correct import path is 'pages/home/home'.

Applied to files:

  • src/pages/home/home.tsx
🧬 Code graph analysis (2)
src/shared/components/bottom-sheet/bottom-sheet.tsx (1)
src/shared/utils/scroll-lock.ts (1)
  • handleScrollLock (1-9)
src/pages/home/home.tsx (1)
src/shared/utils/scroll-lock.ts (1)
  • handleScrollLock (1-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (1)
src/pages/home/home.tsx (1)

43-44: 렌더 중 DOM 사이드 이펙트 제거: useEffect로 이동 및 클린업 추가 필요
렌더 단계에서 document.body를 조작하면 React StrictMode 하에서 이중 호출 또는 언마운트 누락 시 스크롤 락이 해제되지 않을 수 있습니다. 아래처럼 useEffect로 옮기고 클린업을 등록해주세요.

-  handleScrollLock(needsMatchingSetup);
+  useEffect(() => {
+    if (!needsMatchingSetup) return;
+    handleScrollLock(true);
+    return () => handleScrollLock(false);
+  }, [needsMatchingSetup]);

온보딩 모달과 캘린더/경기 바텀시트가 동시에 열릴 수 있는지 확인하고, 동시 가능 시 BottomSheet 쪽 락 참조 카운트가 의도한 대로 동작하는지 꼭 검증해주세요.

Comment on lines +29 to +30
handleScrollLock(isOpen);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

렌더 중 body 스타일 변경 제거 — 이펙트+클린업으로 전환

handleScrollLock(isOpen)을 렌더에서 호출하면 리렌더마다 DOM을 건드립니다. 이펙트로 감싸고 언마운트 시 원복해 주세요.

-  handleScrollLock(isOpen);
+  useEffect(() => {
+    if (!isOpen) return;
+    handleScrollLock(true);
+    return () => handleScrollLock(false);
+  }, [isOpen]);

추가 수정(파일 상단 import):

// 기존
import { useRef } from 'react';
// 변경
import { useEffect, useRef } from 'react';
🤖 Prompt for AI Agents
In src/shared/components/bottom-sheet/bottom-sheet.tsx around lines 29-30,
remove the direct call to handleScrollLock(isOpen) from the render path and
instead add a useEffect that calls handleScrollLock(isOpen) when isOpen changes
and returns a cleanup that restores the body styles (i.e., calls
handleScrollLock(false) or reverts whatever changes were applied); also update
the imports at the top to include useEffect alongside useRef. Ensure the effect
runs only when isOpen changes and that cleanup runs on unmount to restore scroll
behavior.

Comment on lines +1 to +9
export const handleScrollLock = (isOpen: boolean) => {
if (isOpen) {
document.body.style.overflow = 'hidden';
document.body.style.touchAction = 'none';
} else {
document.body.style.removeProperty('overflow');
document.body.style.removeProperty('touch-action');
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

동시 오버레이에서 잠금이 풀릴 수 있는 경쟁 상태 — ref-count와 복원 로직으로 교체 필요

Home/BottomSheet가 각각 토글하면 한 쪽이 닫힐 때 다른 쪽이 열려 있어도 스크롤 잠금이 해제될 수 있습니다. SSR/초기 렌더에서도 안전 가드가 필요합니다.

아래처럼 참조 카운트, 이전 스타일 복원, 환경 가드를 추가해 주세요.

-export const handleScrollLock = (isOpen: boolean) => {
-  if (isOpen) {
-    document.body.style.overflow = 'hidden';
-    document.body.style.touchAction = 'none';
-  } else {
-    document.body.style.removeProperty('overflow');
-    document.body.style.removeProperty('touch-action');
-  }
-};
+let scrollLockCount = 0;
+let prevOverflow: string | null = null;
+let prevTouchAction: string | null = null;
+
+export const handleScrollLock = (lock: boolean) => {
+  if (typeof document === 'undefined' || !document.body) return;
+  const style = document.body.style;
+
+  if (lock) {
+    if (scrollLockCount === 0) {
+      prevOverflow = style.overflow || '';
+      prevTouchAction = style.touchAction || '';
+      style.overflow = 'hidden';
+      style.touchAction = 'none';
+    }
+    scrollLockCount += 1;
+  } else {
+    scrollLockCount = Math.max(0, scrollLockCount - 1);
+    if (scrollLockCount === 0) {
+      if (prevOverflow) style.overflow = prevOverflow; else style.removeProperty('overflow');
+      if (prevTouchAction) style.touchAction = prevTouchAction; else style.removeProperty('touch-action');
+      prevOverflow = null;
+      prevTouchAction = null;
+    }
+  }
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const handleScrollLock = (isOpen: boolean) => {
if (isOpen) {
document.body.style.overflow = 'hidden';
document.body.style.touchAction = 'none';
} else {
document.body.style.removeProperty('overflow');
document.body.style.removeProperty('touch-action');
}
};
let scrollLockCount = 0;
let prevOverflow: string | null = null;
let prevTouchAction: string | null = null;
export const handleScrollLock = (lock: boolean) => {
if (typeof document === 'undefined' || !document.body) return;
const style = document.body.style;
if (lock) {
if (scrollLockCount === 0) {
prevOverflow = style.overflow || '';
prevTouchAction = style.touchAction || '';
style.overflow = 'hidden';
style.touchAction = 'none';
}
scrollLockCount += 1;
} else {
scrollLockCount = Math.max(0, scrollLockCount - 1);
if (scrollLockCount === 0) {
if (prevOverflow) style.overflow = prevOverflow;
else style.removeProperty('overflow');
if (prevTouchAction) style.touchAction = prevTouchAction;
else style.removeProperty('touch-action');
prevOverflow = null;
prevTouchAction = null;
}
}
};
🤖 Prompt for AI Agents
In src/shared/utils/scroll-lock.ts around lines 1 to 9, the current simple
toggle causes a race where multiple overlays can remove the scroll lock when one
closes; replace it with a reference-counted lock that increments on open and
decrements on close, only restoring styles when the count reaches zero; store
and restore previous body style values (overflow and touch-action) rather than
blindly removing properties; add environment guards so the logic no-ops during
SSR or when document/body is unavailable; ensure the exported API exposes
functions to acquire/release the lock (or keeps the same handleScrollLock
boolean API but internally manages the ref count and restoration).

@MATEBALL MATEBALL deleted a comment from netlify bot Sep 7, 2025
@heesunee heesunee merged commit d59f82c into develop Sep 7, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: 딤 영역 스크롤 고정합니다.

2 participants