πŸ’Έ κ³ μ†Œλ“μžμ˜ μžλΆ€μ‹¬ 증λͺ…μ„œ λ§Œλ“€κΈ°

Jul 19, 2021Over 2 years ago

πŸ“° λ‰΄μŠ€λ₯Ό 보던 λ‚˜

λ‰΄μŠ€

μ–Όλ§ˆ 전에 이런 λ‰΄μŠ€λ₯Ό 보고, μ–Έμ  κ°€ μžλΆ€μ‹¬(?)을 κ°€μ§ˆ μžκ²©μ„ 증λͺ…ν•  수 μžˆλŠ” 무언가가 μƒκΈ°λŠ” 게 μ•„λ‹κΉŒ ν•˜λŠ” 상상을 ν•΄λ΄€μ–΄μš”.

마침 지인이 백신을 맞고 λ‚˜μ„œ μ•±μœΌλ‘œ μ˜ˆλ°©μ ‘μ’… 증λͺ…μ„œλ₯Ό λ°›μ•˜λ‹€κ³  ν•œ 게 λ– μ˜¬λΌμ„œ, 이것도 증λͺ…μ„œ μ•±/μ›Ήμ˜ ν˜•νƒœλ‘œ νŒ¨λŸ¬λ””ν•΄μ„œ λ§Œλ“€λ©΄ μž¬λ―Έμžˆμ„ 것 κ°™μ•˜μ£ !

πŸ”¨ 증λͺ…μ„œ λ§Œλ“€κΈ°

✍️ μŠ₯μ‚­μŠ₯μ‚­ λ””μžμΈ

ν”Όκ·Έλ§ˆλ‘œ λ””μžμΈν•œ μžλΆ€μ‹¬ 증λͺ…μ„œ

λ‹€ λ§Œλ“€κ³  λ‚˜μ„œ λ³΄λ‹ˆ 생각보닀 λ§Žμ€ μ‹œκ°„μ„ 보낸 것 κ°™μ§€λ§Œ, μ²˜μŒμ—λŠ” μ΅œλŒ€ν•œ λΉ λ₯΄κ²Œ λ§Œλ“€κ³  μ‹Άμ—ˆκΈ° λ•Œλ¬Έμ— 증λͺ…μ„œ? κ·Έλƒ₯ μ΄λ―Έμ§€λ‘œ λŒ€μΆ© κ·Έλ €μ„œ λ„£κ³  νŽ˜μ΄μ§€ 쀑앙 μ •λ ¬λ§Œ μ‹œμΌœμ„œ λ³΄μ—¬μ£Όμž! ν–ˆμŠ΅λ‹ˆλ‹€.

μ €λŠ” 백신을 λͺ» 맞고 있기 λ•Œλ¬Έμ—...! μΈν„°λ„·μ—μ„œ κ΅΄λŸ¬λ‹€λ‹ˆλŠ” μ˜ˆλ°©μ ‘μ’… 증λͺ…μ„œμ˜ μŠ€ν¬λ¦°μƒ·(μœ„μ— μ²¨λΆ€ν•œ 이미지와 같이 ν…ŒμŠ€νŠΈμš© μ•±μ˜ μŠ€ν¬λ¦°μƒ·μ„ μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€)을 ꡬ해 μ˜†μ— 두고 λΉ„μŠ·ν•œ λŠλ‚ŒμœΌλ‘œ ν•˜λ‚˜ κ·Έλ €λ΄€μŠ΅λ‹ˆλ‹€... 😚

⭐️ 이미지 ν‘œμ‹œν•˜κΈ°

이미지λ₯Ό ν‘œμ‹œν•˜κ³ , ν™”λ©΄ 쀑앙에 μ •λ ¬μ‹œμΌœ λ΄€μ–΄μš”. ν…… λΉ„μ–΄ μžˆμ–΄μ„œ 많이 μ™Έλ‘œμ›Œ λ³΄μ΄λ„€μš”.

width: 300px;
border-radius: 8px;
box-shadow: 0px 16px 36px rgba(0, 0, 0, 0.05);
@media (max-width: 400px) {
width: 285px;
}
css
  • λͺ¨μ„œλ¦¬λ₯Ό λ‘₯κΈ€κ²Œ ν•©λ‹ˆλ‹€.
  • μž…μ²΄κ°μ„ μœ„ν•΄ μ•½κ°„μ˜ 그림자λ₯Ό λ„£μ–΄ λ΄€μ–΄μš”.
  • 넓이λ₯Ό μ„€μ •ν•΄ μ€¬μ–΄μš”. 쑰그만 λͺ¨λ°”일 ν™”λ©΄μ—μ„œλ„ μžμ—°μŠ€λŸ½κ²Œ 보일 수 μžˆλ„λ‘ λ―Έλ””μ–΄ 쿼리둜 μ²˜λ¦¬λ„ ν•΄μ€λ‹ˆλ‹€.

πŸŽ‰ 이λͺ¨μ§€ 폭죽 ν„°λœ¨λ¦¬κΈ°

μ €λŠ” 증λͺ…μ„œ 화면에 λ“€μ–΄κ°€μžλ§ˆμž 이λͺ¨μ§€κ°€ μŸμ•„μ Έ 내리길 μ›ν–ˆμ–΄μš”! μ–΄λ–€ λΌμ΄λΈŒλŸ¬λ¦¬κ°€ μžˆλ‚˜ ν•˜κ³  κΉƒν—ˆλΈŒμ—μ„œ μ—¬λŸ¬ 개의 λͺ¨λ“ˆμ„ 비ꡐ해 λ΄€μ—ˆλŠ”λ°μš”. js-confettiκ°€ κ°€μž₯ κΉ”λ”ν•œ 것 κ°™μ•˜μ–΄μš”. μ²˜μŒμ—λŠ” react-rewardsλ₯Ό μ‚¬μš©ν•˜λ €κ³  ν–ˆμ—ˆλŠ”λ°, 이 λͺ¨λ“ˆμ€ μ˜€ν”„λ‹ 폭죽으둜 μ‚¬μš©ν•˜λŠ” κ²ƒλ³΄λ‹€λŠ” μ‚¬μš©μžμ˜ μΈν„°λ ‰μ…˜μ— λ”°λ₯Έ λ³΄μƒμ΄λ‚˜ μ²˜λ²Œμ„ λ‚΄λ¦¬λŠ” μ—­ν• (μ‹€μ œλ‘œ λ©”μ†Œλ“œ 이름이 rewardMe, punishMeλ„€μš” γ„·γ„·)이 더 μ μ ˆν•΄ λ³΄μ˜€μ£ . ~~사싀 슀크둀 버그가 ν•˜λ‚˜ μžˆμ—ˆλŠ”λ° 고치기 μ–΄λ €μ›Œμ„œ νŒ¨μŠ€ν–ˆμŠ΅λ‹ˆλ‹€.~~

export const Card: React.FC<CardProps> = () => {
useEffect(() => {
const confetti = new JSConfetti();
confetti.addConfetti({
emojis: ['πŸ‡°πŸ‡·', 'πŸ’Έ', 'πŸ’΅', 'πŸ’–'],
emojiSize: 256,
confettiNumber: 30,
confettiRadius: 6,
});
}, []);
return (
<CardContainer>
<CardImage src="/images/card.svg" />
</CardContainer>
);
};
tsx

μ΄λ ‡κ²Œ 처음 ν•œ 번만 JSConfetti 클래슀λ₯Ό λ§Œλ“€λ©΄, Canvas μ—˜λ¦¬λ¨ΌνŠΈλ₯Ό ν˜„μž¬ document에 μΆ”κ°€ν•˜λ©΄μ„œ λͺ¨λ“ˆμ΄ μ΄ˆκΈ°ν™”λ©λ‹ˆλ‹€. κ·Έ λ’€λ‘œ addConfetti λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œλ§ˆλ‹€ 폭죽이 터지죠!

μ•— 그런데 μ €λŠ” Next.jsλ₯Ό μ‚¬μš©ν•˜κ³  μžˆμ—ˆλŠ”λ°μš”. μ €λ ‡κ²Œ μ½”λ“œλ₯Ό μ§œλ‹ˆ μ„œλ²„μ‚¬μ΄λ“œμ—μ„œ λ Œλ”λ§λ  λ•Œ new JSConfetti()κ°€ μ‹€ν–‰λ˜λ©΄μ„œ ReferenceError: document is not defined μ—λŸ¬κ°€ λ°œμƒν•˜λŠ” λ¬Έμ œκ°€ μžˆμ—ˆμ–΄μš”.

useEffect(() => {
if (typeof document === 'undefined') {
return;
}
// client only
const confetti = new JSConfetti();
confetti.addConfetti({
emojis: ['πŸ‡°πŸ‡·', 'πŸ’Έ', 'πŸ’΅', 'πŸ’–'],
emojiSize: 256,
confettiNumber: 30,
confettiRadius: 6,
});
}, []);
tsx

Node ν™˜κ²½μ—μ„œλŠ” document global이 μ„ μ–Έλ˜μ§€ μ•Šμ•˜κΈ° λ•Œλ¬Έμ— μƒκΈ°λŠ” 것이죠! ν΄λΌμ΄μ–ΈνŠΈ μ‚¬μ΄λ“œμ—μ„œλ§Œ μ‹€ν–‰ν•˜κΈ° μœ„ν•΄μ„œλŠ” μœ„ μ½”λ“œμ²˜λŸΌ typeof document둜 λΆ„κΈ°ν•  수 μžˆκ² λ„€μš”.

예쁜 배경도 λ„£μ–΄μ€λ‹ˆλ‹€. 이제 λ“œλ””μ–΄ μ’€ 증λͺ…μ„œ 같은 λŠλ‚Œμ΄ λ‚©λ‹ˆλ‹€!

const Container = styled.div`
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-image: url('/images/gradient.webp');
background-size: cover;
`;
tsx

πŸ–¨ λˆ„λ₯Ό λ•Œλ§ˆλ‹€ 돈 찍기

그런데 아직 λ­”κ°€ μ°λ ν•œ λŠλ‚Œμ΄ λ“€μ–΄μ„œ, 뭘 ν•˜λ©΄ μ’‹μ„κΉŒ μƒκ°ν–ˆμ—ˆλŠ”λ°μš”. μΈμ¦μ„œλ₯Ό 클릭할 λ•Œλ§ˆλ‹€ 25λ§Œμ›(μž¬λ‚œμ§€μ›κΈˆ μ•‘μˆ˜)μ”© μΉ΄μš΄νŠΈκ°€ μ˜¬λΌκ°€λŠ”(돈이 μ°νžˆλŠ”?) 게 보이면 μ–΄λ–¨κΉŒ ν•΄μ„œ κ·Έλƒ₯ κ·Έλ ‡κ²Œ ν•˜κΈ°λ‘œ ν–ˆμŠ΅λ‹ˆλ‹€. 별 뜻이 μžˆλŠ” λ™μž‘μ€ μ•„λ‹ˆμ§€λ§Œ λ³΄μ‹œλŠ” λΆ„λ“€κ»˜μ„œ 처음 3초 λ™μ•ˆμ΄λΌλ„ κ·Έλƒ₯ μž¬λ―Έμžˆμ–΄μ„œ λͺ‡ 번 λˆ„λ₯Ό 것 κ°™μ•˜μ–΄μš”.

βš“οΈ μ»€μŠ€ν…€ ν›… useCount κ΅¬ν˜„

클릭은 λˆ„κ΅°κ°€μ˜ λ…Έλ ₯이죠! μ‚¬μ΄νŠΈλ₯Ό μƒˆλ‘œκ³ μΉ¨ν•˜λŠ” λ°”λžŒμ— μƒνƒœκ°€ λͺ¨λ‘ λ‚ μ•„κ°€λ©΄ λ„ˆλ¬΄ μŠ¬ν”„κΈ° λ•Œλ¬Έμ— μ΄λ²ˆμ—λŠ” κ·Έ 값을 localStorage에 μ €μž₯ν•˜κΈ°λ‘œ ν–ˆμŠ΅λ‹ˆλ‹€.

export const useCount = (): [number, () => void] => {
const [count, setCount] = useState<number>(() => {
if (typeof localStorage === 'undefined') {
return 0;
}
const storedNumber = parseInt(localStorage.getItem('@count'));
return storedNumber || 0;
});
const updateCount = useCallback(() => {
const nextCount = count + 1;
localStorage.setItem('@count', nextCount.toString());
setCount(nextCount);
}, [count]);
return [count, updateCount];
};
ts

localStorageλŠ” λ¬Έμžμ—΄ 킀와 λ¬Έμžμ—΄ 값을 맀핑해 μ£ΌκΈ° λ•Œλ¬Έμ—, ν˜„μž¬ count μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•  λ•Œλ§ˆλ‹€ κ·Έ 값을 String으둜 λ°”κΏ” μ €μž₯ν•˜λ„λ‘ ν–ˆμ–΄μš”. μ²˜μŒμ— μ €μž₯된 μƒνƒœκ°€ μžˆλŠ”μ§€ ν™•μΈν•˜κ³ , κ°€μ Έμ˜¬ λ•ŒλŠ” κ·Έ κ°’μ—μ„œ νŒŒμ‹±ν•˜κ³ μš”!

πŸ“– 클릭 μˆ˜μ— 따라 찍은 돈 ν‘œμ‹œν•˜κΈ°

λ§Œλ“  훅은 μ΄λ ‡κ²Œ μ‚¬μš©ν•  수 μžˆμ–΄μš”!

const [clicks, updateClicks] = useCount();
const message = useMemo(() => {
if (!clicks) {
return 'μΈμ¦μ„œλ₯Ό 눌러 λˆμ„ 찍어 λ³΄μ„Έμš”.';
}
return `${convert(',$.3s', STIMULUS * clicks)}을 μ°μ–΄λ‚΄μ…¨λ„€μš”.`;
}, [clicks]);
...
<Card onClick={updateClicks} />
tsx

μ—¬κΈ°μ„œ convert ν•¨μˆ˜λŠ” uckμ΄λΌλŠ” λͺ¨λ“ˆμ˜ κ²ƒμž…λ‹ˆλ‹€. 숫자λ₯Ό μžμ—°μŠ€λŸ½κ²Œ ν•œκΈ€λ‘œ μ½μ–΄μ£ΌλŠ” 역할을 ν•©λ‹ˆλ‹€.

  • ,$.3s λ¬Έμžμ—΄μ€ convert ν•¨μˆ˜μ˜ 쑰건을 λ‚˜νƒ€λƒ…λ‹ˆλ‹€(μœ„ 링크 README μ°Έκ³ ).
    • ,: 천 μžλ¦¬λ§ˆλ‹€ 콀마(,)λ₯Ό ν‘œμ‹œν•  것
    • $: μ™„μ„±λœ ν•œκΈ€ λ¬Έμžμ—΄μ— 원 λ‹¨μœ„λ₯Ό 뢙일 것
    • .3: precision 수 μ„€μ •(μ—¬κΈ°μ„  크게 μ€‘μš”ν•˜μ§€ μ•Šμ•„μ„œ νŒ¨μŠ€ν•˜κ³  예제 κ·ΈλŒ€λ‘œ λΆ™μ˜€λ˜ 것 κ°™μ•„μš”)
    • s: λ‹¨μœ„λ§ˆλ‹€ 곡백 ν•œ μΉΈ μΆ”κ°€(space의 sλ₯Ό μ˜λ„ν•˜μ‹  λ“―)
  • 2λ…„ 정도 μ§€λ‚œ λΌμ΄λΈŒλŸ¬λ¦¬μ΄κΈ°λŠ” ν•˜μ§€λ§Œ, μ‚¬μš©ν•˜λ©΄μ„œ ꡳ이 λ”°λ‘œ λ…μž 포맷을 λ§Œλ“€ ν•„μš”κ°€ μžˆμ—ˆμ„κΉŒ ν•˜λŠ” 아쉬움이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€(이것 λ§κ³ λŠ” κ°€μž₯ κΉ”λ”ν•˜κΈ°λ„ ν–ˆκ³  μ’‹μ•˜μ–΄μš”). λ¬Όλ‘  μ½”λ“œκ°€ 짧아지긴 ν–ˆμ§€λ§Œ uck을 λͺ¨λ₯΄λŠ” μ‚¬λžŒμ΄ ν•œλˆˆμ— μ•ŒκΈ°λŠ” μ’€ 어렡지 μ•Šλ‚˜ μ‹Άλ„€μš”. μ•„λž˜μ²˜λŸΌ κ·Έλƒ₯ μ˜΅μ…˜μ„ λ‹€λ£¨λŠ” Objectλ₯Ό 전달할 수 μžˆμ—ˆλ‹€λ©΄ 훨씬 잘 μ½νžˆκΈ°λ„ ν•˜κ³  TypeScript λͺ¨λ“ˆμ˜ μž₯점을 λ”μš± ν™œμš©ν•  수 μžˆμ—ˆμ„ 것 κ°™μ•„μš”.
convert(value, {
suffix: '원',
precision: 3,
addComma: true,
addSpace: true,
});
ts

βœ… μ™„μ„±

클릭할 λ•Œλ„ 이λͺ¨μ§€ 폭죽을 ν„°λœ¨λ¦¬κ²Œ ν•˜κ³ , μ•½κ°„μ˜ μ• λ‹ˆλ©”μ΄μ…˜κ³Ό μŠ€νƒ€μΌμ„ λ”ν•˜μž μ•„λž˜ 같은 결과물이 λ‚˜μ™”μ–΄μš”!

클릭 μ• λ‹ˆλ©”μ΄μ…˜

μžλΆ€μ‹¬μ€ 슀슀둜 κ°€μ§€λŠ” κ²ƒμž…λ‹ˆλ‹€.

μ½μ–΄μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€. πŸ™‡

μ„œλΉ„μŠ€λŠ” https://pride-stimulus.vercel.appμ—μ„œ, μ½”λ“œλŠ” https://github.com/junhoyeo/pride-stimulusμ—μ„œ ν™•μΈν•˜μ‹€ 수 μžˆμŠ΅λ‹ˆλ‹€!