๐จ Tailwind Advanced 2์ฅ: ์คํฌ๋กค ๋ง์คํฐ โ Scroll Snap & ๋ถ๋๋ฌ์ด UX
๐ ๊ฐ์
CSS Scroll Snap, overscroll-behavior, scroll-smooth๋ฅผ ์์ ํ ์ดํดํ๊ณ JavaScript ์์ด ์นด๋ ์บ๋ฌ์ ๊ณผ ์น์ ์ค๋ ์คํฌ๋กค์ ๊ตฌํํฉ๋๋ค.
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐ฏ 1๋จ๊ณ: Scroll Snap ํต์ฌ ๊ตฌ์กฐ โ ๋ถ๋ชจ์ ์์์ ๊ณ์ฝ
- ๐ 2๋จ๊ณ: snap-mandatory vs snap-proximity โ ๊ฐ์ ๋ ์์ ๋
- ๐ 3๋จ๊ณ: snap-start, snap-center, snap-end โ ์ด๋์ ๊ฑธ๋ฆด๊น
- ๐ 4๋จ๊ณ: scroll-smooth โ ๋ถ๋๋ฌ์ด ์ต์ปค ์ด๋
- ๐ 5๋จ๊ณ: overscroll-behavior โ ์คํฌ๋กค ์ ํ ๋ง๊ธฐ
- ๐ 6๋จ๊ณ: scroll-margin & scroll-padding โ sticky ํค๋ ์คํ์ ํด๊ฒฐ
- ๐ ์ค์ : ์์๋ค ์ปค๋ฎค๋ํฐ ์คํฐ๋ ์นด๋ ์บ๋ฌ์ + ์น์ ์ค๋
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 22๋ถ (์ ์ฒด) / ํต์ฌ ํํธ๋ง: 10๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ์์(๋์์ด๋): "์์ฒ ๋, ์คํฐ๋ ์นด๋ ๋ชฉ๋ก์ ์ข์ฐ ์คํฌ๋กค๋ก ๋๊ธฐ๋ ์บ๋ฌ์ ๋ก ๋ง๋ค๊ณ ์ถ์ด์. ๊ทผ๋ฐ ๊ทธ๋ฅ ์คํฌ๋กค์ด ์๋๋ผ, ์นด๋ ๋จ์๋ก ๋ฑ๋ฑ ๋ฉ์ถ๊ฒ ํด์ฃผ์ธ์! ์์ฆ ๋ชจ๋ฐ์ผ ์ฑ ๋ณด๋ฉด ๋ค ๊ทธ๋ ๊ฒ ๋์์์."
- ์์ฒ (์ ์
): "์! ์บ๋ฌ์
์ด์? ์ ๊ฐ
touchstart,touchend์ด๋ฒคํธ ๊ฐ์งํด์ ์ค์์ดํ ๊ฑฐ๋ฆฌ ๊ณ์ฐํ๊ณ , requestAnimationFrame์ผ๋ก ๋ถ๋๋ฝ๊ฒ ์ด๋ํ๋ ์ฝ๋ ์ง๋ฉด ๋๊ฒ ๋๋ฐ์? 200์ค ์ ๋๋ฉด..." - ์ํธ(๋ฆฌ๋): "์์ฒ ๋,
snap-x snap-mandatory๋snap-center๋ ํด๋์ค๋ฉด ๋ฉ๋๋ค. JavaScript ํ ์ค๋ ํ์ ์์ด์."
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
-
snap-x,snap-y,snap-mandatory,snap-proximity์ ์กฐํฉ ์๋ฆฌ๋ฅผ ์ดํดํ๋ค. -
snap-start,snap-center,snap-end๋ฅผ ์ํฉ์ ๋ง๊ฒ ์ ํํ ์ ์๋ค. -
overscroll-contain์ผ๋ก ๋ชจ๋ฌ/๋๋ก์ด ๋ด๋ถ ์คํฌ๋กค ์ ํ๋ฅผ ๋ง์ ์ ์๋ค. -
scroll-mt-*๋ก sticky ํค๋์ ๊ฐ๋ ค์ง๋ ์ต์ปค ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
์์์ด์ ์ค๋
์บ๋ฌ์
์์ฒญ โ Scroll Snap ๋ถ๋ชจ-์์ ๊ตฌ์กฐ โ mandatory vs proximity โ ๋ฐฉํฅ๋ณ ์ ๋ ฌ โ smooth scroll โ overscroll ์ ์ด โ ์ค์ ๊ตฌํ
๐ค ์ ์์์ผ ํ๋๊ฐ
์บ๋ฌ์ ์ฌ๋ผ์ด๋. ์น ๊ฐ๋ฐ์๋ผ๋ฉด ๋๊ตฌ๋ ํ๋ฒ์ฏค ๊ตฌํํด๋ดค๊ฑฐ๋, ๊ตฌํ ์์ฒญ์ ๋ฐ์๋ณธ ๊ฒฝํ์ด ์์ ๊ฑฐ์ผ.
์ ํต์ ์ธ ๋ฐฉ์์ ์ด๋ฌ์ด:
- ์ปจํ ์ด๋ ๋๋น ์ธก์
- ํ์ฌ ์คํฌ๋กค ์์น ์ถ์
- ์ค์์ดํ/ํด๋ฆญ ์ด๋ฒคํธ ๊ฐ์ง
- ๊ฐ์ฅ ๊ฐ๊น์ด ์นด๋ ์์น ๊ณ์ฐ
requestAnimationFrame์ผ๋ก ์ ๋๋ฉ์ด์ ์คํ
์ด๊ฒ boilerplate๋ง 100์ค์ด ๋์ด. ๊ทธ๋ฐ๋ฐ ์๋ง์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ์ด ๋ณต์ก์ฑ์ ์จ๊ธฐ๋ ๋ฐ ์ฑ๊ณตํ๋ฉด์ swiper.js, embla-carousel ๊ฐ์ ๊ฒ๋ค์ด ์๊ฒจ๋ฌ์ง.
๊ทผ๋ฐ ์ ๊น. CSS ํ์ค ์์ํ๊ฐ 2019๋ ์ ์ด ๋ชจ๋ ๊ฑธ CSS๋ก ํด๊ฒฐํ๋ ๋ช ์ธ๋ฅผ ์ ์์ผ๋ก ํ์ ํ์ด. ์ด๋ฆํ์ฌ CSS Scroll Snap. ๊ทธ๋ฆฌ๊ณ ํ์ฌ ๋ชจ๋ ์ฃผ์ ๋ธ๋ผ์ฐ์ ์์ ์๋ฒฝํ ์ง์๋ผ.
๋ชจ๋ฅด๋ฉด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ํํ๊ณ , ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ ๊ฑฑ์ ํ๊ณ , ์ ๋ฐ์ดํธ๋ฅผ ๊ด๋ฆฌํ๊ณ , ๋ฒ๊ทธ๋ฅผ ์ง์ ๊ณ ์ณ์ผ ํด. ์๋ฉด ๊ทธ๋ฅ ํด๋์ค ๋ ๊ฐ๋ฉด ๋์ด์ผ.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ ๊ธฐ์ฐจ ์น๊ฐ์ฅ์ ์์ ์ ์์คํ
CSS Scroll Snap์ ๊ธฐ์ฐจ์ญ์ ์น๊ฐ์ฅ๊ณผ ๋น์ทํด.
- ์คํฌ๋กค ์ปจํ ์ด๋ (๋ถ๋ชจ): ๊ธฐ์ฐจ ์ ๋ก. "์ด ์์์ ๊ธฐ์ฐจ๊ฐ ๋ค๋๋ฉฐ ์ค๋ ๊ท์น์ ์ ํด."
- ์ค๋ ์์ดํ (์์๋ค): ์น๊ฐ์ฅ. "๊ธฐ์ฐจ๊ฐ ์ฌ๊ธฐ์ ์ ์ฐจํด์ผ ํด."
- snap-mandatory: ๊ฐ์ ์ ์ฐจ. "๊ธฐ์ฐจ๋ ๋ฐ๋์ ์น๊ฐ์ฅ์์๋ง ๋ฉ์ถฐ์ผ ํด. ์ค๊ฐ์์ ๋ฉ์ถ๋ ๊ฒ์ ํ์ฉ ์ ํด."
- snap-proximity: ์์ ์ ์ฐจ. "์น๊ฐ์ฅ ๊ฐ๊น์ด ์์ ๋๋ง ์๋์ผ๋ก ๋์ด๋น๊ฒจ. ๋ฉ๋ฆฌ ์์ผ๋ฉด ๊ทธ๋ฅ ๋์ด."
- snap-center: ์น๊ฐ์ฅ ์ค์ ํ์. "๊ธฐ์ฐจ ๋ฌธ์ด ์ด ๋งํฌ์ ๋ฑ ๋ง๊ฒ ์."
๋ถ๋ชจ๋ "์ด๋ ๋ฐฉํฅ์ผ๋ก, ์ผ๋ง๋ ์๊ฒฉํ๊ฒ" ์ค๋ ํ ์ง ์ ํ๊ณ ,
์์์ "๋ด ์ด๋ ์ง์ ์ ๊ฑธ๋ฆด์ง" ์ ํด. ์ด ๊ณ์ฝ์ด Scroll Snap์ด์ผ.
๐ฏ 1๋จ๊ณ: Scroll Snap ํต์ฌ ๊ตฌ์กฐ โ ๋ถ๋ชจ์ ์์์ ๊ณ์ฝ
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- Scroll Snap์์ ๋ถ๋ชจ์ ์์์ด ๊ฐ๊ฐ ๋ฌด์จ ์ญํ ์ ๋ด๋นํ๋์ง ๊ตฌ๋ถํ ์ ์๋ค.
- ์ต์ํ์ ํด๋์ค๋ก ์๋ํ๋ ์ค๋ ์ปจํ ์ด๋๋ฅผ ๋ง๋ค ์ ์๋ค.
Scroll Snap์ ๊ตฌ์กฐ๋ ํญ์ 2๊ฐ์ ๋ ์ด์ด๋ก ๋๋์ด.
<!-- ๐ฃ ์์ฒ : "snap-x๋ง ๋ฃ์๋๋ฐ ์ ์ ๊ฑธ๋ ค์?" -->
<!-- snap-x ํผ์์๋ ์๋ฌด๊ฒ๋ ์ ํด. ์์์ align์ด ์์ด์ผ ํจ! -->
<!-- โ ์์์ snap align์ด ์์ผ๋ฉด ์ค๋
ํฌ์ธํธ๊ฐ ์์ด์ ๋์ ์ ํจ -->
<div class="flex overflow-x-auto snap-x snap-mandatory gap-4">
<div class="w-80 h-48 bg-gray-200 shrink-0">์นด๋ 1</div> <!-- snap ํฌ์ธํธ ์์! -->
<div class="w-80 h-48 bg-gray-200 shrink-0">์นด๋ 2</div>
</div>
<!-- โ
๋ถ๋ชจ: ์ปจํ
์ด๋ ์ค์ / ์์: ์ ๋ ฌ ํฌ์ธํธ ์ค์ -->
<div class="
flex overflow-x-auto /* ์คํฌ๋กค ํ์ฑํ */
snap-x /* X์ถ ๋ฐฉํฅ ์ค๋
*/
snap-mandatory /* ์๊ฒฉํ ์ค๋
*/
gap-4
">
<!-- ๊ฐ ์์์ snap-center ๋๋ snap-start ์ถ๊ฐ -->
<div class="w-80 h-48 bg-gray-200 shrink-0 snap-center">์นด๋ 1</div>
<div class="w-80 h-48 bg-gray-200 shrink-0 snap-center">์นด๋ 2</div>
<div class="w-80 h-48 bg-gray-200 shrink-0 snap-center">์นด๋ 3</div>
</div>๋ถ๋ชจ๊ฐ ๋ด๋นํ๋ ๊ฒ:
<!-- 1. ์ค๋
๋ฐฉํฅ -->
snap-x <!-- ๊ฐ๋ก ์คํฌ๋กค ์ค๋
-->
snap-y <!-- ์ธ๋ก ์คํฌ๋กค ์ค๋
-->
snap-both <!-- ์๋ฐฉํฅ ์ค๋
-->
snap-none <!-- ์ค๋
ํด์ -->
<!-- 2. ์ค๋
์๊ฒฉ๋ -->
snap-mandatory <!-- ํญ์ ์ค๋
ํฌ์ธํธ์์ ๋ฉ์ถค -->
snap-proximity <!-- ๊ฐ๊น์ธ ๋๋ง ์ค๋
ํฌ์ธํธ์ ๋๋ฆผ -->์์์ด ๋ด๋นํ๋ ๊ฒ:
<!-- ์ค๋
์ ๋ ฌ ์์น -->
snap-start <!-- ์์ดํ
์ ์์์ ์ด ์ค๋
ํฌ์ธํธ -->
snap-center <!-- ์์ดํ
์ ์ค์์ด ์ค๋
ํฌ์ธํธ -->
snap-end <!-- ์์ดํ
์ ๋์ ์ด ์ค๋
ํฌ์ธํธ -->
snap-align-none <!-- ์ด ์์ดํ
์ ์ค๋
๋์์์ ์ ์ธ -->๐ 2๋จ๊ณ: snap-mandatory vs snap-proximity โ ๊ฐ์ ๋ ์์ ๋
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
snap-mandatory์snap-proximity์ค ์ด๋ค ์ํฉ์ ์ด๋ ๊ฒ์ ์จ์ผ ํ๋์ง ํ๋จํ ์ ์๋ค.- ๊ฐ๊ฐ์ UX ํน์ฑ๊ณผ ์ฃผ์์ฌํญ์ ์ ์ ์๋ค.
snap-mandatory โ ์ ๋ ๋ณต์ข ํ
์คํฌ๋กค์ด ๋ฉ์ถ๋ฉด ๋ฌด์กฐ๊ฑด ๊ฐ์ฅ ๊ฐ๊น์ด ์ค๋ ํฌ์ธํธ๋ก ์ด๋ํด. ์ค๊ฐ ์ด์ ์ฉกํ ์์น์ ๋ฉ์ถ๋ ๊ฒ์ด ํ์ฉ๋์ง ์์.
<!-- snap-mandatory: ํญ์ ์นด๋ ๋จ์๋ก ๋ฑ๋ฑ ๋ฉ์ถค -->
<div class="flex overflow-x-auto snap-x snap-mandatory gap-6 px-6">
<div class="snap-center shrink-0 w-72">์คํฐ๋ ์นด๋ 1</div>
<div class="snap-center shrink-0 w-72">์คํฐ๋ ์นด๋ 2</div>
<div class="snap-center shrink-0 w-72">์คํฐ๋ ์นด๋ 3</div>
</div>snap-mandatory ์ฃผ์์ฌํญ: ์์ดํ ํฌ๊ธฐ๊ฐ ์ปจํ ์ด๋๋ณด๋ค ํฌ๋ฉด ์คํฌ๋กค์ด ๋ถ๊ฐ๋ฅํด์ง ์ ์์ด.
<!-- ๐จ ์ํํ ํจํด: ์์ดํ
์ด ์ปจํ
์ด๋๋ณด๋ค ํฌ๋ฉด ๊ฑด๋๋ ํ์ ๋ฐ์ -->
<!-- ์ปจํ
์ด๋: 400px, ์์ดํ
: 600px -->
<div class="w-[400px] overflow-x-auto snap-x snap-mandatory">
<div class="w-[600px] snap-start">์ด ์์ดํ
์ 100% ๋ณผ ์ ์์ด!</div>
<!-- mandatory๋ฉด ํญ์ ๋ค์ ์ค๋
ํฌ์ธํธ๋ก ์ด๋ โ ๊ธด ์ฝํ
์ธ ์ฝ๊ธฐ ๋ถ๊ฐ -->
</div>
<!-- โ
์ด๋ฐ ๊ฒฝ์ฐ์ snap-proximity๊ฐ ๋ ์ ํฉ -->
<div class="w-[400px] overflow-x-auto snap-x snap-proximity">
<div class="w-[600px] snap-start">์ด ์์ดํ
์ ์ฒ์ฒํ ์ฝ์ ์ ์์ด</div>
</div>snap-proximity โ ์ ๋นํ ํ์กฐํ
์ค๋ ํฌ์ธํธ์ ๊ฐ๊น์ด ์์ ๋๋ง ๋์ด๋น๊ฒจ. ๋ฉ๋ฆฌ ์์ผ๋ฉด ๊ทธ๋ฅ ์คํฌ๋กค ๊ด์ฑ๋๋ก ๋ฉ์ถฐ.
<!-- snap-proximity: ๊ฐ๊น์ธ ๋๋ง ์ค๋
, ๊ฐ์ ์ด๋ ์์ -->
<div class="flex overflow-x-auto snap-x snap-proximity gap-6">
<div class="snap-center shrink-0 w-72">์คํฐ๋ ์นด๋ 1</div>
<div class="snap-center shrink-0 w-72">์คํฐ๋ ์นด๋ 2</div>
<div class="snap-center shrink-0 w-72">์คํฐ๋ ์นด๋ 3</div>
</div>์ธ์ ๋ฌด์์ ์ธ๊น:
| ์ํฉ | ์ถ์ฒ |
|---|---|
| ์นด๋ ์บ๋ฌ์ (์นด๋๊ฐ ํ๋ฉด๋ณด๋ค ์์) | snap-mandatory |
| ํํ์ด์ง ์น์ ์คํฌ๋กค (์น์ ์ด ์ ํํ ํ๋ฉด ํฌ๊ธฐ) | snap-mandatory |
| ๊ธด ๊ธฐ์ฌ/๋ธ๋ก๊ทธ ํฌ์คํธ์ ์น์ ์ด๋ | snap-proximity |
| ์์ดํ ํฌ๊ธฐ๊ฐ ์ปจํ ์ด๋๋ณด๋ค ํฐ ๊ฒฝ์ฐ | snap-proximity |
๐ 3๋จ๊ณ: snap-start, snap-center, snap-end โ ์ด๋์ ๊ฑธ๋ฆด๊น
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
snap-start,snap-center,snap-end์ ์๊ฐ์ ์ฐจ์ด๋ฅผ ์ดํดํ๋ค.- ์นด๋ ์บ๋ฌ์ vs ํํ์ด์ง ์น์ ๋ฑ ์ฉ๋์ ๋ง๋ ์ ๋ ฌ์ ์ ํํ ์ ์๋ค.
snap-start โ ์์ดํ ์ ์ผ์ชฝ(์)์ด ๊ธฐ์ค์
<!-- snap-start: ์์ดํ
์ ์์(left)์ด ์ปจํ
์ด๋์ ์์(left)์ ๋ง์ถฐ์ง -->
<!--
[์นด๋1][์นด๋2][์นด๋3]
^
์ปจํ
์ด๋ ์ผ์ชฝ๊ณผ ์นด๋ ์ผ์ชฝ์ด ๋ง์ถฐ์ง
-->
<div class="flex overflow-x-auto snap-x snap-mandatory">
<div class="snap-start shrink-0 w-72 mr-4">์นด๋ 1</div>
<div class="snap-start shrink-0 w-72 mr-4">์นด๋ 2</div>
<div class="snap-start shrink-0 w-72 mr-4">์นด๋ 3</div>
</div>snap-center โ ์์ดํ ์ ์ค์์ด ๊ธฐ์ค์
<!-- snap-center: ์์ดํ
์ ์ค์์ด ์ปจํ
์ด๋์ ์ค์์ ๋ง์ถฐ์ง -->
<!--
[ ์นด๋1 ][ ์นด๋2 ][ ์นด๋3 ]
^
์ปจํ
์ด๋ ์ค์๊ณผ ์นด๋ ์ค์์ด ๋ง์ถฐ์ง
-->
<div class="flex overflow-x-auto snap-x snap-mandatory">
<!-- ๐ ์ ๋ ์ฌ๋ฐฑ ์์ดํ
: ์ค์ ์ ๋ ฌ ์ ์ฒ์/๋ง์ง๋ง ์นด๋๊ฐ ์ค์์ ์ค๋๋ก -->
<div class="shrink-0 w-[calc(50%-9rem)]"></div> {/* ์นด๋๊ฐ 144px(w-72/2)์ผ ๋ ์ฌ๋ฐฑ */}
<div class="snap-center shrink-0 w-72 mr-4">์นด๋ 1</div>
<div class="snap-center shrink-0 w-72 mr-4">์นด๋ 2</div>
<div class="snap-center shrink-0 w-72 mr-4">์นด๋ 3</div>
<div class="shrink-0 w-[calc(50%-9rem)]"></div>
</div>snap-end โ ์์ดํ ์ ์ค๋ฅธ์ชฝ(์๋)์ด ๊ธฐ์ค์
<!-- snap-end: ์์ดํ
์ ๋(right)์ด ์ปจํ
์ด๋์ ๋(right)์ ๋ง์ถฐ์ง -->
<div class="flex overflow-x-auto snap-x snap-mandatory">
<div class="snap-end shrink-0 w-72 mr-4">์นด๋ 1</div>
<div class="snap-end shrink-0 w-72 mr-4">์นด๋ 2</div>
<div class="snap-end shrink-0 w-72">์นด๋ 3</div> {/* ๋ง์ง๋ง์ mr ์์ */}
</div>์ค๋ฌด ํ โ ์ด๋ align์ ์ธ๊น:
์นด๋ ์บ๋ฌ์
(์ผ๋ฐ) โ snap-start (์์ฐ์ค๋ฌ์ด ์ผ์ชฝ ์ ๋ ฌ)
์นด๋ ์บ๋ฌ์
(์ค์ ๊ฐ์กฐ) โ snap-center (์๋ ์ฌ๋ฐฑ ์์ดํ
์ถ๊ฐ ํ์)
ํํ์ด์ง ์น์
์ค๋
โ snap-start (๊ฐ ์น์
์ ์์์ด ํ๋ฉด ์๋จ์ ๋ง์ถฐ์ง)
์ธ๋ก ์ฌ๋ผ์ด๋์ผ โ snap-center (์ฝํ
์ธ ๊ฐ ์ค์์ ์ค๊ฒ)
๐ 4๋จ๊ณ: scroll-smooth โ ๋ถ๋๋ฌ์ด ์ต์ปค ์ด๋
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
scroll-smooth๋ก ์ต์ปค ๋งํฌ ํด๋ฆญ ์ ๋ถ๋๋ฌ์ด ์คํฌ๋กค ์ด๋์ ๊ตฌํํ ์ ์๋ค.prefers-reduced-motion์ ๊ทผ์ฑ ๊ณ ๋ ค์ ์ค์์ฑ์ ์ดํดํ๋ค.
scroll-behavior: smooth ๋ ๊ฐ์ฅ ๋จ์ํ์ง๋ง UX๋ฅผ ๊ทน์ ์ผ๋ก ๊ฐ์ ํ๋ ์์ฑ์ด์ผ. ์ต์ปค ๋งํฌ(#section)๋ฅผ ํด๋ฆญํ ๋ ์๊ฐ ์ด๋์ด ์๋๋ผ ๋ถ๋๋ฝ๊ฒ ์คํฌ๋กค๋ผ.
<!-- โ ์์ฒ ์ด์ ์ฝ๋: JavaScript๋ก smooth scroll ๊ตฌํ -->
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', (e) => {
e.preventDefault();
const target = document.querySelector(anchor.getAttribute('href'));
target.scrollIntoView({ behavior: 'smooth' });
});
});
<!-- โ
์ํธ์ ๋ฐฉ์: html ํ๊ทธ์ ํด๋์ค ํ๋ -->
<html class="scroll-smooth">
<!-- ์ด์ ๋ชจ๋ ์ต์ปค ๋งํฌ๊ฐ ๋ถ๋๋ฝ๊ฒ ์ด๋ -->
</html><!-- ์ค์ : ๋ค๋น๊ฒ์ด์
๋ฉ๋ด์์ ์น์
์ผ๋ก ์ด๋ -->
<html class="scroll-smooth">
<head>...</head>
<body>
<!-- ๋ค๋น๊ฒ์ด์
-->
<nav class="fixed top-0 ...">
<a href="#studies">์คํฐ๋ ์ฐพ๊ธฐ</a> <!-- ํด๋ฆญ ์ ๋ถ๋๋ฝ๊ฒ ์คํฌ๋กค -->
<a href="#how-it-works">์ด์ฉ ๋ฐฉ๋ฒ</a>
<a href="#testimonials">ํ๊ธฐ</a>
</nav>
<!-- ์น์
๋ค -->
<section id="studies">...</section>
<section id="how-it-works">...</section>
<section id="testimonials">...</section>
</body>
</html>์ ๊ทผ์ฑ ์ฃผ์์ฌํญ:
/* ๋์ ๊ฐ์ ์ค์ ์ ํ ์ฌ์ฉ์์๊ฒ๋ ์ฆ์ ์ด๋์ด ๋์ ์ ์์ด */
/* Tailwind๋ก๋ ์๋์ฒ๋ผ ๋ฐ์ํ ๋ณํ ํ์ฉ */<!-- motion-reduce: ์ ๊ทผ์ฑ ๋ฐฐ๋ ค -->
<html class="scroll-smooth motion-reduce:scroll-auto">
<!-- ์ ๋๋ฉ์ด์
๊ฐ์ ์ ํธ ์ฌ์ฉ์์๊ฒ ์ฆ์ ์ด๋ -->
</html>๐ 5๋จ๊ณ: overscroll-behavior โ ์คํฌ๋กค ์ ํ ๋ง๊ธฐ
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- "์คํฌ๋กค ์ฒด์ธ(scroll chaining)"์ด ๋ฌด์์ธ์ง ์ดํดํ๊ณ , ์ด๋ฅผ ๋ง์ ์ ์๋ค.
- ๋ชจ๋ฌ, ๋๋ก์ด, ์ฌ์ด๋ํจ๋์ ์คํฌ๋กค ๋ ๋ฆฝ์ฑ์ ๊ตฌํํ ์ ์๋ค.
์คํฌ๋กค ์ฒด์ธ์ด๋?
์คํฌ๋กค ๊ฐ๋ฅํ ์์(์: ๋ชจ๋ฌ)๋ฅผ ๋๊น์ง ์คํฌ๋กคํ๋ฉด, ๊ทธ ์คํฌ๋กค ์ด๋ฒคํธ๊ฐ ๋ถ๋ชจ(body)์๊ฒ๋ ์ ํ๋๋ ํ์์ด์ผ.
์ฌ์ฉ์๊ฐ ๋ชจ๋ฌ ๋ด๋ถ๋ฅผ ์คํฌ๋กค โ
๋ชจ๋ฌ ์ฝํ
์ธ ๋ฅผ ๋ค ์ฌ๋ฆผ โ
์คํฌ๋กค์ด ๋จ์ ์์ผ๋ฉด body๊ฐ ์คํฌ๋กค๋จ โ ์ด๊ฒ ์คํฌ๋กค ์ฒด์ธ ๋ฒ๊ทธ!
<!-- โ ์์ฒ ์ด์ JavaScript ํด๊ฒฐ์ฑ
: ์ด๋ฒคํธ ๋ง๊ธฐ -->
modal.addEventListener('wheel', (e) => {
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
if (
(e.deltaY < 0 && scrollTop === 0) ||
(e.deltaY > 0 && scrollTop + clientHeight === scrollHeight)
) {
e.preventDefault();
}
}, { passive: false });
// ๐ฃ passive event ๋ฌธ์ , iOS Safari ๋ฒ๊ทธ, ์ฑ๋ฅ ์ด์... ๊ณจ์น ์ํ
<!-- โ
์ํธ์ CSS ํ ๋ฐฉ ํด๊ฒฐ -->
<div class="overflow-y-auto overscroll-contain max-h-screen">
<!-- ๋ชจ๋ฌ ๋ด๋ถ ์ฝํ
์ธ -->
<!-- overscroll-contain: ์ด ์์์ ์คํฌ๋กค์ด ๋ถ๋ชจ๋ก ์ ํ๋์ง ์์ -->
</div>overscroll 3๊ฐ์ง ๋ชจ๋
<!-- overscroll-auto (๊ธฐ๋ณธ๊ฐ): ์คํฌ๋กค ์ฒด์ธ ํ์ฉ -->
<div class="overflow-y-auto overscroll-auto">
<!-- ๋์์ ์คํฌ๋กคํ๋ฉด ๋ถ๋ชจ ์คํฌ๋กค ๋ฐ์ (๊ธฐ๋ณธ ๋ธ๋ผ์ฐ์ ๋์) -->
</div>
<!-- overscroll-contain: ์คํฌ๋กค ์ฒด์ธ ์ฐจ๋จ + ๋ฐ์ด์ค ํ์ฉ -->
<div class="overflow-y-auto overscroll-contain">
<!-- ๋์์ ์คํฌ๋กค์ด ๋ถ๋ชจ๋ก ์ ํ ์ ๋จ -->
<!-- iOS Safari์ ๊ณ ๋ฌด์ค ๋ฐ์ด์ค ํจ๊ณผ๋ ์ ์ง๋จ -->
</div>
<!-- overscroll-none: ์คํฌ๋กค ์ฒด์ธ + ๋ฐ์ด์ค ๋ชจ๋ ์ฐจ๋จ -->
<div class="overflow-y-auto overscroll-none">
<!-- ๋์์ ์คํฌ๋กค์ด ๋ถ๋ชจ๋ก๋ ์ ํ ์ ๋๊ณ ๋ฐ์ด์ค๋ ์์ -->
<!-- ์ฑ์ฒ๋ผ ๋ฑ๋ฑํ ๋๋ -->
</div>์ธ์ ๋ฌด์์ ์ธ๊น:
| ์ํฉ | ์ถ์ฒ |
|---|---|
| ๋ชจ๋ฌ/๋๋ก์ด ๋ด๋ถ ์คํฌ๋กค | overscroll-contain |
| ์ฑํ ์ฐฝ ๋ฉ์์ง ์์ญ | overscroll-contain |
| ์๋ฒ ๋๋ ์คํฌ๋กค ์ปจํ ์ด๋ | overscroll-contain |
| ๋ชจ๋ฐ์ผ ์ฑ์ฒ๋ผ ๋ฑ๋ฑํ UX | overscroll-none |
| ํํ์ด์ง ์คํฌ๋กค ์น์ | overscroll-none |
์ค์ : ๋ชจ๋ฌ ์คํฌ๋กค ๋ ๋ฆฝ์ฑ
// components/StudyDetailModal.tsx
// ๐ฆ ์ํธ: "๋ชจ๋ฌ ์์์ ์คํฌ๋กคํ ๋ ๋ฐฐ๊ฒฝ์ด ๊ฐ์ด ์์ง์ด๋ ๋ฒ๊ทธ ์์ง? overscroll-contain ํ๋๋ฉด ๋์ด์ผ"
export default function StudyDetailModal({ isOpen, onClose, study }) {
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* ๋ฐฐ๊ฒฝ ์ค๋ฒ๋ ์ด */}
<div className="absolute inset-0 bg-black/50" onClick={onClose} />
{/* ๋ชจ๋ฌ ํจ๋ */}
<div className="
relative z-10
bg-white rounded-2xl
w-full max-w-lg
max-h-[85vh] /* ์ต๋ ๋์ด ์ ํ */
overflow-y-auto /* ๋ด๋ถ ์คํฌ๋กค ํ์ฑํ */
overscroll-contain /* ์คํฌ๋กค ์ ํ ์ฐจ๋จ */
shadow-2xl
">
{/* ๋ชจ๋ฌ ํค๋ (sticky) */}
<div className="sticky top-0 bg-white border-b px-6 py-4 flex justify-between items-center">
<h2 className="text-lg font-semibold">{study.title}</h2>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">โ</button>
</div>
{/* ๋ชจ๋ฌ ์ฝํ
์ธ - ์๋ฌด๋ฆฌ ์คํฌ๋กคํด๋ ๋ฐฐ๊ฒฝ body๋ ์ ์์ง์ */}
<div className="px-6 py-4 space-y-4">
<p className="text-gray-700 leading-relaxed">{study.description}</p>
{/* ... ๋ ๋ง์ ์ฝํ
์ธ ... */}
</div>
</div>
</div>
);
}๐ 6๋จ๊ณ: scroll-margin & scroll-padding โ sticky ํค๋ ์คํ์ ํด๊ฒฐ
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- sticky ํค๋๊ฐ ์์ ๋ ์ต์ปค ์ด๋ ํ ์ฝํ ์ธ ๊ฐ ํค๋์ ๊ฐ๋ ค์ง๋ ๋ฒ๊ทธ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
scroll-mt-*์scroll-pt-*์ ์ฐจ์ด๋ฅผ ์ดํดํ๋ค.
๋ฌธ์ ์ํฉ: ํค๋ ์๋๋ก ์จ๋ ์ฝํ ์ธ
<!-- ๐ฃ ์์ฒ : "nav์ fixed ์ฃผ๊ณ ์ต์ปค ์ด๋ํ๋ฉด ์น์
์ ๋ชฉ์ด nav ๋ค์ ์จ์ด์!" -->
<!--
ํ๋ฉด:
โโโโโโโโโโโโโโโโโโโ
โ Sticky Nav โ โ height: 64px (h-16)
โโโโโโโโโโโโโโโโโโโค
โ (์ด ๋ถ๋ถ์ด ๊ฐ๋ ค์ง) โ ์น์
์ ๋ชฉ์ด ์ฌ๊ธฐ ์์ด์ ์ ๋ณด์!
โ โ
โ ์น์
๋ด์ฉ ... โ
โโโโโโโโโโโโโโโโโโโ
-->ํด๊ฒฐ์ฑ : scroll-margin-top
<!-- โ ์์ฒ ์ด์ ๋ฐฉ๋ฒ: ์น์
์ padding-top ์ฃผ๊ธฐ -->
<section id="studies" class="pt-16"> <!-- ํ์ง๋ง ์น์
๋ด๋ถ ๋ ์ด์์์ด ๋ง๊ฐ์ง -->
<!-- โ
์ํธ์ ๋ฐฉ๋ฒ: scroll-mt-16 -->
<section id="studies" class="scroll-mt-16">
<!-- scroll-mt-16 = scroll-margin-top: 4rem -->
<!-- ์ต์ปค๋ก ์ด๋ ์ 64px ์๋์์ ๋ฉ์ถค โ ํค๋ ๋ฐ์ ์ ๊ฐ๋ ค์ง -->
<h2>์คํฐ๋ ๋ชฉ๋ก</h2>
</section>scroll-padding vs scroll-margin
<!-- scroll-padding: ์ปจํ
์ด๋(๋ถ๋ชจ)์ ์ ์ฉ -->
<!-- ์ปจํ
์ด๋ ์์ ์ค๋
/์ต์ปค ์ด๋ ์ ์์ ์คํ์
-->
<div class="snap-y snap-mandatory scroll-pt-16 overflow-y-auto h-screen">
<!-- ๋ชจ๋ ์์์ ์ค๋
ํฌ์ธํธ์ 16(4rem) ์คํ์
์ ์ฉ -->
<section class="snap-start h-screen">์น์
1</section>
<section class="snap-start h-screen">์น์
2</section>
</div>
<!-- scroll-margin: ์์ดํ
(์์)์ ์ ์ฉ -->
<!-- ์ด ์์ดํ
์ด ์ค๋
/์ต์ปค ์ด๋ ๋์์ด ๋ ๋ ์ฌ๋ฐฑ -->
<section id="studies" class="snap-start scroll-mt-16">
<!-- ์ด ์น์
์ผ๋ก ์ด๋ ์ 16(4rem) ์์์ ๋ฉ์ถค -->
</section>์ค์ ์ ๋ต - sticky ํค๋ ์คํ์ :
// tailwind.config.ts - ํค๋ ๋์ด๋ฅผ CSS ๋ณ์๋ก ๊ด๋ฆฌ
// ๋ชจ๋ ์น์
์ ๋์ผํ scroll-mt ์ ์ฉ์ ์ํด
// JSX์์
const HEADER_HEIGHT = "h-16"; // 64px
const SCROLL_OFFSET = "scroll-mt-16"; // ๋์ผํ๊ฒ ๋ง์ถค
// layout.tsx
<html className="scroll-smooth">
<body>
<nav className={`sticky top-0 z-50 ${HEADER_HEIGHT}`}>
...๋ค๋น
</nav>
<main>
<section id="hero">...</section>
<section id="studies" className={SCROLL_OFFSET}>...</section>
<section id="how-it-works" className={SCROLL_OFFSET}>...</section>
</main>
</body>
</html>๐ ์ค์ : ์์๋ค ์ปค๋ฎค๋ํฐ ์คํฐ๋ ์นด๋ ์บ๋ฌ์ + ์น์ ์ค๋
์ํ ์นด๋ ์บ๋ฌ์
// components/StudyCarousel.tsx
// ๐ฆ ์ํธ: "JavaScript ํ ์ค ์์ด ์ค์์ดํ ์บ๋ฌ์
์์ฑ์ด์์"
interface Study {
id: string;
title: string;
topic: string;
memberCount: number;
thumbnail: string;
}
export default function StudyCarousel({ studies }: { studies: Study[] }) {
return (
<div className="relative">
<h2 className="text-2xl font-bold mb-4 px-4">์ถ์ฒ ์คํฐ๋</h2>
{/* ๐ฎ ํต์ฌ: ์คํฌ๋กค ์ปจํ
์ด๋ */}
<div className="
flex /* ๊ฐ๋ก ์ ๋ ฌ */
overflow-x-auto /* ๊ฐ๋ก ์คํฌ๋กค ํ์ฑํ */
snap-x /* X์ถ ์ค๋
*/
snap-mandatory /* ๊ฐ์ ์ค๋
*/
gap-4 /* ์นด๋ ๊ฐ๊ฒฉ */
px-4 /* ์์ชฝ ์ฌ๋ฐฑ */
pb-4 /* ์คํฌ๋กค๋ฐ ๊ณต๊ฐ */
-mx-4 /* ๋ถ๋ชจ padding ์์ */
/* ์คํฌ๋กค๋ฐ ์จ๊ธฐ๊ธฐ (ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ) */
scrollbar-hide /* tailwind-scrollbar-hide ํ๋ฌ๊ทธ์ธ ํ์ */
[&::-webkit-scrollbar]:hidden /* ๋๋ ์ด ๋ฐฉ์ */
">
{/* ์ฒซ ๋ฒ์งธ ์ฌ๋ฐฑ ์์ดํ
- snap-center๋ฅผ ์ํด ํ์ */}
{/* snap-center ์ฐ์ง ์๋๋ค๋ฉด ์ด๊ฑด ์๋ต ๊ฐ๋ฅ */}
{studies.map((study) => (
<div
key={study.id}
className="
snap-start /* ์นด๋ ์ผ์ชฝ์ด ์ค๋
ํฌ์ธํธ */
shrink-0 /* flex ์์ดํ
์ถ์ ๋ฐฉ์ง */
w-[280px] /* ์นด๋ ๋๋น ๊ณ ์ */
rounded-2xl overflow-hidden
bg-white shadow-md
hover:shadow-xl
transition-shadow duration-200
cursor-pointer
"
>
{/* ์ธ๋ค์ผ */}
<div className="h-40 overflow-hidden">
<img
src={study.thumbnail}
alt={study.title}
className="w-full h-full object-cover hover:scale-105 transition-transform duration-300"
/>
</div>
{/* ์นด๋ ๋ด์ฉ */}
<div className="p-4">
<span className="text-xs font-medium text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full">
{study.topic}
</span>
<h3 className="font-semibold text-gray-900 mt-2 line-clamp-2">{study.title}</h3>
<p className="text-sm text-gray-500 mt-1">๋ฉค๋ฒ {study.memberCount}๋ช
</p>
</div>
</div>
))}
</div>
{/* ์ธ๋์ผ์ดํฐ (CSS only - overflow ๋ค์ gradient fade) */}
<div className="absolute top-0 right-0 bottom-4 w-16 bg-linear-to-l from-gray-50 to-transparent pointer-events-none" />
</div>
);
}ํํ์ด์ง ์น์ ์ค๋
// app/landing/page.tsx
// ๐ฆ ์ํธ: "๊ฐ ์น์
์ด ๋ฑ๋ฑ ๋ง๊ฒ ์ค๋
๋๋ ๋๋ฉํ์ด์ง์ผ"
// ๐ฃ ์์ฒ : "์ด๊ฑฐ ๊ตฌํํ๋ ค๊ณ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐพ๊ณ ์์๋๋ฐ..."
export default function LandingPage() {
return (
// ํํ์ด์ง ์ค๋
์ปจํ
์ด๋
<div className="
h-screen /* ๋ทฐํฌํธ ๋์ด๋ก ์ ํ */
overflow-y-auto /* ์ธ๋ก ์คํฌ๋กค */
snap-y /* Y์ถ ์ค๋
*/
snap-mandatory /* ๊ฐ์ ์ค๋
*/
overscroll-none /* ํํ์ด์ง ํน์ฑ์ ๋ฐ์ด์ค ์ ๊ฑฐ */
scroll-smooth /* ๋ถ๋๋ฌ์ด ์ค๋
์ด๋ */
">
{/* ์น์
1: ํ์ด๋ก */}
<section className="
snap-start /* ์น์
์๋จ์ด ๋ทฐํฌํธ ์๋จ์ ๋ง์ถฐ์ง */
h-screen /* ์ ํํ 1 ํ๋ฉด ๋์ด */
flex items-center justify-center
bg-linear-to-br from-violet-900 to-indigo-900
">
<div className="text-center text-white">
<h1 className="text-6xl font-black mb-6
bg-linear-to-r from-violet-300 to-indigo-300
bg-clip-text text-transparent">
์์๋ค ์ปค๋ฎค๋ํฐ
</h1>
<p className="text-xl text-white/70">
๊ฐ๋ฐ์ ์คํฐ๋ ๋งค์นญ ํ๋ซํผ
</p>
</div>
</section>
{/* ์น์
2: ๊ธฐ๋ฅ ์๊ฐ */}
<section className="
snap-start
h-screen
flex items-center justify-center
bg-white
">
<div className="max-w-4xl mx-auto px-4 text-center">
<h2 className="text-4xl font-bold text-gray-900 mb-12">์ด๋ป๊ฒ ์ฌ์ฉํ๋์?</h2>
<div className="grid grid-cols-3 gap-8">
{/* 3๋จ๊ณ ์๊ฐ ์นด๋ */}
<div className="p-6 rounded-2xl bg-indigo-50">
<div className="text-3xl mb-3">๐</div>
<h3 className="font-bold mb-2">์คํฐ๋ ํ์</h3>
<p className="text-gray-600 text-sm">๊ด์ฌ ๊ธฐ์ ์คํ์ผ๋ก ํํฐ๋ง</p>
</div>
<div className="p-6 rounded-2xl bg-purple-50">
<div className="text-3xl mb-3">๐ค</div>
<h3 className="font-bold mb-2">๋งค์นญ ์ ์ฒญ</h3>
<p className="text-gray-600 text-sm">์ํด๋ฆญ์ผ๋ก ์ฐธ์ฌ ์ ์ฒญ</p>
</div>
<div className="p-6 rounded-2xl bg-pink-50">
<div className="text-3xl mb-3">๐</div>
<h3 className="font-bold mb-2">ํจ๊ป ์ฑ์ฅ</h3>
<p className="text-gray-600 text-sm">์ค์๊ฐ ์ฑํ
์ผ๋ก ์ํต</p>
</div>
</div>
</div>
</section>
{/* ์น์
3: ์คํฐ๋ ๋ชฉ๋ก ๋ฏธ๋ฆฌ๋ณด๊ธฐ */}
<section className="
snap-start
h-screen
flex flex-col items-center justify-center
bg-gray-50
px-4
">
<h2 className="text-4xl font-bold text-gray-900 mb-8">์ธ๊ธฐ ์คํฐ๋</h2>
{/* ์น์
๋ด๋ถ์ ๊ฐ๋ก ์บ๋ฌ์
์ฝ์
*/}
{/* overscroll-contain์ผ๋ก ๊ฐ๋ก ์คํฌ๋กค์ด ์ธ๋ก ํํ์ด์ง์ ๊ฐ์ญ ์ ํจ */}
<div className="
w-full max-w-4xl
flex overflow-x-auto
snap-x snap-mandatory
overscroll-x-contain /* ๊ฐ๋ก ์คํฌ๋กค์ด ์ธ๋ก ์ค๋
์ ์ ํ ์ ๋จ */
gap-4 pb-4
">
{[1, 2, 3, 4, 5].map((i) => (
<div key={i} className="snap-center shrink-0 w-64 bg-white rounded-xl shadow p-4">
<div className="h-32 bg-indigo-100 rounded-lg mb-3" />
<h3 className="font-semibold">React ์ฌํ ์คํฐ๋ {i}</h3>
<p className="text-sm text-gray-500">4๋ช
์ฐธ์ฌ ์ค</p>
</div>
))}
</div>
</section>
</div>
);
}๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ์ ํธ๋ฆฌํฐ | ์ญํ | ์ ์ฉ ๋์ |
|---|---|---|
snap-x | ๊ฐ๋ก ์ค๋ ํ์ฑํ | ๋ถ๋ชจ ์ปจํ ์ด๋ |
snap-y | ์ธ๋ก ์ค๋ ํ์ฑํ | ๋ถ๋ชจ ์ปจํ ์ด๋ |
snap-mandatory | ํญ์ ์ค๋ ํฌ์ธํธ์์ ๋ฉ์ถค | ๋ถ๋ชจ ์ปจํ ์ด๋ |
snap-proximity | ๊ฐ๊น์ธ ๋๋ง ์ค๋ | ๋ถ๋ชจ ์ปจํ ์ด๋ |
snap-start | ์์ดํ ์์์ ์ด ์ค๋ ํฌ์ธํธ | ์์ ์์ดํ |
snap-center | ์์ดํ ์ค์์ด ์ค๋ ํฌ์ธํธ | ์์ ์์ดํ |
snap-end | ์์ดํ ๋์ ์ด ์ค๋ ํฌ์ธํธ | ์์ ์์ดํ |
scroll-smooth | ์ต์ปค ์ด๋ ์ ๋ถ๋๋ฌ์ด ์คํฌ๋กค | <html> ๋๋ ์ปจํ
์ด๋ |
overscroll-contain | ์คํฌ๋กค ์ ํ(์ฒด์ธ) ์ฐจ๋จ | ์คํฌ๋กค ๊ฐ๋ฅํ ์์ |
overscroll-none | ์คํฌ๋กค ์ ํ + ๋ฐ์ด์ค ๋ชจ๋ ์ฐจ๋จ | ํํ์ด์ง ์คํฌ๋กค |
scroll-mt-* | ์ต์ปค ์ด๋ ์ ์์ชฝ ์ฌ๋ฐฑ | ์ต์ปค ๋์ ์์ |
scroll-pt-* | ์ค๋ /์ต์ปค ์ด๋ ์ ์ปจํ ์ด๋ ์์ ์คํ์ | ์ค๋ ์ปจํ ์ด๋ |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ์์์ด๊ฐ ์์ฒญํ "์นด๋ ๋จ์๋ก ๋ฑ๋ฑ ๋ฉ์ถ๋ ๊ฐ๋ก ์บ๋ฌ์ "์ ๊ตฌํํ๊ธฐ ์ํด ์ปจํ ์ด๋(๋ถ๋ชจ)์ ํ์ํ ์ต์ ํด๋์ค ์กฐํฉ์?
โ
์ ๋ต: overflow-x-auto snap-x snap-mandatory
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: Scroll Snap์ ํญ์ ๋ถ๋ชจ + ์์ ๋ ๋ ์ด์ด๊ฐ ํ์ํด. ๋ถ๋ชจ์๋ โ
overflow-x-auto(์คํฌ๋กค ํ์ฑํ), โกsnap-x(๊ฐ๋ก ๋ฐฉํฅ ์ค๋ ํ์ฑํ), โขsnap-mandatory(์๊ฒฉํ ์ค๋ ) ์ธ ๊ฐ์ง๊ฐ ํ์ํ๊ณ , ๊ฐ ์์ ์นด๋์๋snap-start๋๋snap-center๊ฐ ์์ด์ผ ์ค์ ๋ก ๋์ํด. - ์ค๋ต ํผ๋๋ฐฑ:
snap-x๋ง ์ค๋ ์ ๋ผ. ์์์snap-center์์ผ๋ฉด ์ค๋ ํฌ์ธํธ๊ฐ ์์ด์ ํจ๊ณผ ์์ด. ๋ฐ๋์ ๋ถ๋ชจ + ์์์ ๋ ๋ ์ด์ด ์กฐํฉ์ด์ผ. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ๋ถ๋ชจ๋ "๋ฐฉํฅ + ์๊ฒฉ๋", ์์์ "์ ๋ ฌ ์์น". ๋ถ๋ชจ๊ฐ "์ด๋์ ์ค๋ ํ ์ง", ์์์ด "๋ด ์ด๋์ ๊ฑธ๋ฆด์ง"๋ฅผ ๊ฐ๊ฐ ๋ด๋นํด.
Q2. ์์๋ค ์ปค๋ฎค๋ํฐ ์ฑ์์ ์ฑํ ์ฐฝ ๋ด๋ถ๋ฅผ ์คํฌ๋กคํ๋ค ๋์ ๋๋ฌํ๋ฉด ๋ฐฐ๊ฒฝ ํ์ด์ง๋ ํจ๊ป ์คํฌ๋กค๋๋ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ๋ค. ์ด๋ค Tailwind ํด๋์ค๋ก ํด๊ฒฐํ ์ ์๋๊ฐ?
โ
์ ๋ต: ์ฑํ
์ปจํ
์ด๋์ overscroll-contain ์ถ๊ฐ
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: ์ด ํ์์ "์คํฌ๋กค ์ฒด์ธ(scroll chaining)" ์ด๋ผ ํด. ๋ด๋ถ ์คํฌ๋กค์ด ๋๋๋ฉด ์ด๋ฒคํธ๊ฐ ๋ถ๋ชจ๋ก ์ ํ๋๋ ๋ธ๋ผ์ฐ์ ๊ธฐ๋ณธ ๋์์ด์ผ.
overscroll-contain์ CSS์overscroll-behavior: contain์ ์ ์ฉํด ์คํฌ๋กค ์ด๋ฒคํธ๊ฐ ์ด ์์๋ฅผ ๋ฒ์ด๋์ง ๋ชปํ๊ฒ ์ฐจ๋จํด. iOS Safari์ ๋ฐ์ด์ค ํจ๊ณผ๋ ์ ์ง๋ผ์ ์์ฐ์ค๋ฌ์ด ๋๋๋ ์ด์์์ด. - ์ค๋ต ํผ๋๋ฐฑ: JavaScript
preventDefault()๋ก ๋ง์ผ๋ฉดpassive event listener๊ฒฝ๊ณ ์ ์ฑ๋ฅ ์ด์๊ฐ ๋ฐ์ํ ์ ์์ด. CSS ํ ์ค์ด ํจ์ฌ ์์ ํ๊ณ ํจ์จ์ ์ด์ผ. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ๋ชจ๋ฌ/์ฑํ
์ฐฝ/๋๋ก์ด์ฒ๋ผ "์คํฌ๋กค ๋
๋ฆฝ ๊ณต๊ฐ"์ด ํ์ํ ๊ณณ์ ํญ์
overscroll-contain์ธํธ.
Q3. ์์๋ค ์ปค๋ฎค๋ํฐ์ sticky ๋ค๋น๊ฒ์ด์
(๋์ด 64px)์ด ์๋ค. ์ต์ปค ๋งํฌ๋ก #studies ์น์
์ผ๋ก ์ด๋ ์ ์น์
์ ๋ชฉ์ด ๋ค๋น ์๋์ ์จ๋ ๋ฒ๊ทธ๋ฅผ CSS๋ง์ผ๋ก ํด๊ฒฐํ๋ ค๋ฉด?
โ
์ ๋ต: #studies ์น์
์ scroll-mt-16 ์ถ๊ฐ (16 = 64px)
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
:
scroll-margin-top์์ฑ์ ์ต์ปค ์ด๋ ์ ํด๋น ์์๊ฐ ๋ทฐํฌํธ ์๋จ์ผ๋ก๋ถํฐ ์ผ๋ง๋ ๋จ์ด์ง ๊ณณ์ ์์นํ ์ง๋ฅผ ์ค์ ํด.scroll-mt-16= 64px์ด๋ฏ๋ก, 64px ๋์ด์ sticky ํค๋ ์๋์์ ๋ฑ ์์๋ผ.padding-top๊ณผ ๋ฌ๋ฆฌ ๋ ์ด์์์ ์ํฅ์ ์ฃผ์ง ์๋๋ค๋ ๊ฒ์ด ํต์ฌ ์ฅ์ ์ด์ผ. - ์ค๋ต ํผ๋๋ฐฑ:
pt-16์ผ๋ก ํด๊ฒฐํ๋ฉด ์น์ ๋ด๋ถ ๊ฐ๊ฒฉ์ด ํ์ด์ ธ์ ๋ ์ด์์์ด ๋ง๊ฐ์ ธ.scroll-mt-*๋ ์คํฌ๋กค ์์น๋ง ์กฐ์ ํ๊ณ ์ค์ ๋ ์ด์์์ ๊ฑด๋๋ฆฌ์ง ์์. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: sticky ํค๋ ๋์ด = scroll-mt ๊ฐ.
h-16nav โscroll-mt-16sections. ํญ์ ์ง์ ๋ง์ถฐ.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ ์ง์ง ์ถฉ๊ฒฉ์ด์๋ค.
์์ ๋๋๊ฐ "์นด๋๋ฅผ ๋ฑ๋ฑ ๋ฉ์ถ๋ ์ค์์ดํ ์บ๋ฌ์ "์ ์์ฒญํ๊ณ , ๋๋ ์ฆ์ ๊ตฌ๊ธ์์
"react swiper carousel" ์ ๊ฒ์ํ๋ค. Swiper.js ๊ณต์ ๋ฌธ์๋ ์ด์๋ค.์ํธ ํ์ด ๋ค์์ ๋ณด๋๋ "์์ฒ ๋, ๊ทธ๊ฑฐ snap-x snap-mandatory snap-center ์ธ ๊ฐ๋ฉด ๋๋๋ฐ์?"๋ผ๊ณ ํ๋ค.
์ง์ง๋ก ์ธ ํด๋์ค์๋ค. NPM ํจํค์ง๋ ์๊ณ , ๋ฒ๋ค ์ฌ์ด์ฆ๋ 0, JavaScript๋ 0.
๊ทธ๋ฌ๋ฉด์ ์ํธ ํ์ด "CSS๋ 90๋ ๋๋ถํฐ ์คํฌ๋กค์ ๋ค๋ฃจ๊ณ ์์๋๋ฐ,
์ฐ๋ฆฌ๊ฐ 10๋ ๋์ JavaScript๋ก ๊ทธ๊ฑธ ์ฌ๋ฐ๋ช ํ๊ณ ์์๋ ๊ฑฐ์์"๋ผ๋ ๋ง์ ํ๋๋ฐ,
๊ทธ ๋ง์ด ๊ณ์ ๋จธ๋ฆฟ์์์ ๋งด๋์๋ค.์ผ๋ง๋ ๋ง์ ๊ฒ๋ค์ด ์ด๋ฏธ CSS์ ์๋๋ฐ ๋ด๊ฐ ๋ชจ๋ฅด๋ ๊ฑธ๊น.