๐ ๋ค์ด๊ฐ๋ฉฐ
๋์๋ฆฌ์์ React Native๋ก ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์
์ฐ๋์ ์์ ์ ํํ ์ ์๋ ์ปดํฌ๋ํธ๋ฅผ ๊ตฌํํด์ผ ํ๋ ์ํฉ์ด์๋ค.

๋์์ธ ์์์ ์์ ๊ฐ์๋ค.
- ์ฐ๋์ ์ ๋ฆฌ์คํธ๋ ๊ฐ๊ฐ ์ธ๋ก๋ก ์คํฌ๋กค๋๋ค.
- ํ ํ๋ฉด์์๋ ์ฝ 6๊ฐ์ ํญ๋ชฉ์ด ๋ณด์ธ๋ค.
- ๊ฐ์ฅ ์๋ ํญ๋ชฉ์ ์์ํ๊ฒ ํ์ด๋์์ ๋์ด์ผ ํ๋ค.
๊ทธ๋์ ์ฒ์์ ๋ฆฌ์คํธ ์๋์ LinearGradient๋ฅผ ํ๋ ๊น๊ณ ,
๊ทธ๋ผ๋ฐ์ด์ ์ผ๋ก ๊ฐ๋ฆฌ๋ ๋ฐฉ๋ฒ์ ์๊ฐํ์๋ค.

ํ์ง๋ง ์ค์ ๋ก ๊ตฌํํด๋ณด๋, ์ฌ๋ฌ ๋ฌธ์ ๋ค์ด ์์๋ค ..๐ญ
- ๊ธ์๋ง ์์ฐ์ค๋ฝ๊ฒ ์ฌ๋ผ์ง๋ ๊ฒ ์๋๋ผ ๋ฐฐ๊ฒฝ๊น์ง ํจ๊ป ํ๋ ค์ง
- ์คํฌ๋กค์ด ๋๋ฌ์ ๋, ๋ ๋ด๋ ค๊ฐ ํญ๋ชฉ์ด ์์ด๋ ๊ทธ๋ผ๋ฐ์ด์
์ด ๊ณ์ ๋จ์์์
ํนํ 1์์ ๊ฒฝ์ฐ 0์ ๊ฐ์ ๊ฑด ์์ผ๋๊น ์ด๊ฒ ๋ง์ง๋ง ์์์ผํ ๋ฐ,
๊ทธ๋ ๋ค๋ณด๋ ๊ณ์ํด์ ๊ทธ๋ผ๋ฐ์ด์ ์ด ๋จ์์๊ฒ ๋ผ์ ๊ธ์จ๊ฐ ์ ์ ๋ณด์๋ค .. ๐ - ์ ํ๋ ํญ๋ชฉ์ ์คํ์ผ์ด ๋ฐ๋์ด๋, ๊ทธ๋ผ๋ฐ์ด์
์ด ๊ณ์ ๊ธ์๋ฅผ ๋ฎ์ฌ๋ฒ๋ ค์ ์ ๋ณด์ด์ง๊ฐ ์์
์๋ ๋ง์ฐฌ๊ฐ์ง๋ก 1์์ด ๊ฐ์ฅ ๋ฌธ์ ์๋๋ฐ ..
ํด๋น ์ฐ๋๋ ์์ด ์ ํ๋๋ฉด ๊ธ์ ์์ด white๋ก ๋ฐ๋๊ณ , bold์ฒด๋ก ๋ณ๊ฒฝ๋๋๋ฐ,
1์์ ๊ณ์ํด์ ๊ทธ๋ผ๋ฐ์ด์ ์ด ๋จ์์๋ค ๋ณด๋๊น ์ ํ์ด ๋๊ฑด์ง ๋ง๊ฑด์ง ์ ๋ณด์ด์ง๊ฐ ์์๋ค ใ ใ กใ
๋ฐ๋ผ์ LinearGradient๋ฅผ ์ด์ฉํ ๋ฐฉ๋ฒ ๋ณด๋ค๋, ๊ธ์ ์์ฒด์ ํ์ด๋๋ฅผ ์ ์ฉํ๋ ๊ฒ ์ข๋ค๊ณ ํ๋จํ๋ค.
๋ํ, ๋ฌด์กฐ๊ฑด ๊ฐ์ฅ ์๋ ์์์ ํ์ด๋๋ฅผ ์ ์ฉํ๋ ๋์ ์
์๋๋ก ๋ ์คํฌ๋กคํ ์ ์๋ค๋ฉด ํ๋จ ํ์ด๋๋ฅผ onํ๊ณ ,
์๋ก ๋ ์คํฌ๋กคํ ์ ์๋ค๋ฉด ์๋จ ํ์ด๋๋ฅผ onํ๋ ๋ฐฉ์์ ์ ์ฉํ๊ธฐ๋ก ํ๋ค.
๐ฅ MaskedView ์ ์ฉํ๊ธฐ
MaskedView๋ iOS์ Android์์ ๋ชจ๋ ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ก,
๋ง๊ทธ๋๋ก ๋ง์คํฌ๋ฅผ ๊ธฐ์ค์ผ๋ก ์์ ์์๊ฐ ๋ณด์ผ์ง ์ฌ๋ผ์ง์ง๋ฅผ ๊ฒฐ์ ํ๋ค.
์ฌ๊ธฐ์ ํต์ฌ ๊ฐ๋ ์ ์๋์ ๊ฐ๋ค.
| ๋ง์คํฌ ์์ | ์ค์ ์ฝํ ์ธ |
| black (๋ถํฌ๋ช ) | 100% ๋ณด์ |
| transparent (ํฌ๋ช ) | 0% ๋ณด์ |
| mid-gray | ์ผ๋ถ๋ง ๋ณด์ (= ํ์ด๋ ํจ๊ณผ) |
์ฆ, ๊ฒ์์์ผ์๋ก ๊ธ์๊ฐ ๋ ์ ๋ช ํ๊ฒ ๋ณด์ด๊ณ , ํฌ๋ช ํ ์๋ก ์ฌ๋ผ์ง๋ ๊ฒ์ด๋ค.
๊ทธ๋ฆฌ๊ณ ์๊ฑธ ์ ์กฐํฉํ๋ฉด, ์คํฌ๋กค๋๋ ํ ์คํธ๊ฐ ์์ฐ์ค๋ฝ๊ฒ ์ฌ๋ผ์ง๋ UI๋ฅผ ๋ง๋ค ์ ์์ ๊ฒ ๊ฐ์๋ค !! ๐คฉ
๐ง Mask ๊ตฌ์กฐ ์ค๊ณ
๋๋ ์๋์ ๊ฐ์ด ๋ง์คํฌ๋ฅผ 3๊ฐ์ ์์ญ์ผ๋ก ๋๋ ์ ๊ตฌ์ฑํ๋ค.
mask (listContainer์ ๋์ผ ๋์ด)
โโโ topMask (์ ํ์ด๋ ๊ตฌ๊ฐ)
โโโ middleMask (์ ์์ ์ผ๋ก ๋ณด์ด๋ ๊ตฌ๊ฐ)
โโโ bottomMask (์๋ ํ์ด๋ ๊ตฌ๊ฐ)
์ด๋ ๊ฒ 3๋จ ๊ตฌ์กฐ๋ก ๋๋ ์ด์ ๋ ๊ฐ๋จํ๋ค.
- topMask
- ๋ ์๋ก ์คํฌ๋กคํ ์์๊ฐ ๋จ์์์ ๋๋ง ํ์ด๋๋ฅผ ์ ์ฉํด์ผ ํ๋ค.
- ์ฆ, ๋ฆฌ์คํธ์ ์ต์๋จ์ ๋ฟ์ผ๋ฉด ํ์ ์์ผ๋ฏ๋ก off ํ๋ค.
- middleMask
- ๊ฐ์ด๋ฐ 4๊ฐ ์์๋ ํญ์ fully visibleํ ์์ญ์ด๋ฏ๋ก ์ ๋ช ํ๊ฒ ๋ณด์ด๋๋ก ํด์ผ ํ๋ค.
- bottomMask
- ์๋๋ก ๋ ์คํฌ๋กคํ ์์๊ฐ ๋จ์์์ ๋๋ง ํ์ด๋๋ฅผ ์ ์ฉํด์ผ ํ๋ค.
- ์ฆ, ๋ฆฌ์คํธ์ ์ตํ๋จ์ ๋ฟ์ผ๋ฉด ํ์ ์์ผ๋ฏ๋ก off ํ๋ค.
์ด์ฒ๋ผ ๋ง์คํฌ ์์ฒด๋ฅผ ๋์ ์ผ๋ก ์กฐ์ ํด์
์ฐ๋์ ์ ๋ฆฌ์คํธ์ ์์๋์ ์์ฐ์ค๋ฝ๊ฒ ์ฌ๋ผ์ง๋ ํจ๊ณผ๊ฐ ์๊ธฐ๋๋ก ๋ง๋ค์๋ค.
๐จ ๊ตฌํ ๊ณผ์
MaskedView ์ค์น
npm install @react-native-masked-view/masked-view
์ํ๊ฐ์ผ๋ก fade ์ฌ๋ถ ์ ์ด
์คํฌ๋กค์ด ์์๋๋ก ๋ ๊ฐ๋ฅํ์ง์ ๋ฐ๋ผ ๊ฐ ๋ฐฉํฅ ํ์ด๋๋ฅผ ์ผ๊ฑฐ๋ ๋๋๋ก ํด์ฃผ์๋ค.
const [showMonthTopFade, setShowMonthTopFade] = useState(false);
const [showMonthBottomFade, setShowMonthBottomFade] = useState(true);
์ฆ, ๋ ์๋ก ์คํฌ๋กค์ด ๊ฐ๋ฅํ๋ฉด topFade = true๊ฐ ๋๋ ๊ฒ์ด๊ณ ,
๋์ด์ ์๋ก ์คํฌ๋กคํ ๊ฒ ์์ผ๋ฉด topFade = false๊ฐ ๋๋ ๊ฒ์ด๋ค.
์คํฌ๋กค ์ด๋ฒคํธ์์ ํ์ด๋ ํ์ ์ฌ๋ถ ํ๋จ
์คํฌ๋กคํ ๋๋ง๋ค ํ์ฌ ์์น๋ฅผ ์ธก์ ํด์ fade๋ฅผ on / off ํด์ฃผ๋๋ก ํ์๋ค.
const handleMonthScroll = (e) => {
const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
const y = contentOffset.y;
const maxScroll = contentSize.height - layoutMeasurement.height;
setShowMonthTopFade(y > 0); // y๊ฐ 0 ๋ณด๋ค ํฌ๋ฉด ์๋ก ์คํฌ๋กคํ ์ฌ์ ๊ฐ ์๋ ๊ฑฐ๋๊น fade on!
setShowMonthBottomFade(y < maxScroll); // y๊ฐ maxScroll ๋ณด๋ค ์์ผ๋ฉด ์์ง ์คํฌ๋กคํ ์ฌ์ ๊ฐ ์๋ ๊ฑฐ๋๊น fade on!
};
์ด ๋ก์ง ๋๋ถ์ ์คํฌ๋กค ๊ฐ๋ฅํ ๋ฐฉํฅ์๋ง fade๊ฐ ์ ์ฉ๋ผ์ UX๊ฐ ๋งค์ฐ ์์ฐ์ค๋ฌ์์ก๋ค ๐๐ป
MaskedView์ ๋ง์คํฌ ์์ ๋ง๋ค๊ธฐ
{/* ์ ์นผ๋ผ */}
<View style={styles.column}>
<MaskedView
style={styles.listContainer}
maskElement={
<View style={styles.mask}>
{/* ์์ชฝ fade */}
{showMonthTopFade && (
<LinearGradient
colors={["rgba(0,0,0,0)", "rgba(0,0,0,1)"]}
style={styles.topMask}
/>
)}
{/* ์คํฌ๋กค ๋ฆฌ์คํธ๊ฐ ๋๋ ทํ๊ฒ ๋ณด์ด๋ ์์ญ */}
<View style={styles.middleMask} />
{/* ์๋์ชฝ fade */}
{showMonthBottomFade && (
<LinearGradient
colors={["rgba(0,0,0,1)", "rgba(0,0,0,0)"]}
style={styles.bottomMask}
/>
)}
</View>
}
>
{/* ์ฌ๊ธฐ ์์ ์ค์ month ๋ฆฌ์คํธ ScrollView */}
</MaskedView>
</View>
mask: {
flex: 1, // ๋ง์คํฌ ์ ์ฒด ๋์ด๋ฅผ ๋ฆฌ์คํธ ์์ญ ์ ์ฒด์ ๋๊ฐ์ด ๋ง์ถฐ์ค
},
topMask: {
height: ITEM_HEIGHT * 1.5, // ์์ชฝ์์ ์์ํ ๋ํ๋๋ fade ๊ตฌ๊ฐ
},
middleMask: {
flex: 1,
backgroundColor: "black", // ๊ฐ์ด๋ฐ ์์ญ์ ํญ์ ์์ ํ ๋ณด์ด๋๋ก ๋ง์คํฌ๋ฅผ black์ผ๋ก!
},
bottomMask: {
height: ITEM_HEIGHT * 1.5, // ์๋๋ก ๊ฐ ์๋ก ์์ํ ์ฌ๋ผ์ง๋ fade
},
๐ฉ ๊ฒฐ๊ณผ



๊ฐ์ฅ ์ผ์ชฝ์ ๋์ด์ ์๋ก ์คํฌ๋กคํ ๊ฒ ์๋ ๊ฒฝ์ฐ ํ ์คํธ์ fade๊ฐ ์ ์ฉ๋์ง ์๋ ๋ชจ์ต (12์),
๊ฐ์ด๋ฐ๋ ์์๋ ๋ชจ๋ ์คํฌ๋กคํ ๊ฒ ์๋ ๊ฒฝ์ฐ ๊ฐ์ฅ ์์ ๊ฐ์ฅ ์๋ ํ ์คํธ์ fade๊ฐ ์ ์ฉ๋ ๋ชจ์ต (10์๊ณผ 5์),
๊ฐ์ฅ ์ค๋ฅธ์ชฝ์ ๋์ด์ ์๋๋ก ์คํฌ๋กคํ ๊ฒ ์๋ ๊ฒฝ์ฐ ํ ์คํธ์ fade๊ฐ ์ ์ฉ๋์ง ์๋ ๋ชจ์ต์ด๋ค (1์).
'๐ป๊ณต๋ถ ๊ธฐ๋ก > ๐ Frontend' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [Frontend] React Native + ESLINT v9 Flat Config (0) | 2025.11.17 |
|---|---|
| [Frontend] 3D ๋ก๊ณ ํ์ด๋์ธ ์ต์ ํ (0) | 2025.11.14 |
| [Frontend] ๋ธ๋ ๋์์ ๋ชจ๋ธ ์ ์ํ๊ณ ์น์ผ๋ก ๋์ฐ๊ธฐ (1) | 2025.11.14 |
| [Frontend] ESLint v9๋ก TS+React ๋ฆฐํ ํ๊ฒฝ ๋ง๋ค๊ธฐ (0) | 2025.11.12 |
| [Frontend] ESLint v9 ๊ณต์ ๋ฌธ์ ๋ฏ์ด๋ณด๊ธฐ (0) | 2025.11.12 |