๐Ÿฌ ๊ธดํ˜ธํก/๊ณ ์Šค๋ฝ ํ‹ฐ์ผ“

[ํ‹ฐ์ผ“ ์˜ˆ๋งค ํ”„๋กœ์ ํŠธ] 4. ๊ณ ๋ฏผํ•œ ๊ฒƒ๋“ค

ํ•œ๊ทœ์ง„ 2022. 2. 15. 11:06

์ง€๋‚œ ๋ช‡์ฃผ๊ฐ„ ํ”„๋ก ํŠธ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ ํ–ˆ๋˜ ์—ฌ๋Ÿฌ ๊ณ ๋ฏผ๋“ค๊ณผ ํ•ด๊ฒฐ๋“ค, ๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ์ €๊ฒƒ๋“ค์„ ๊ธฐ๋กํ•ด๋ณธ๋‹ค. ์ œ๋ชฉ์„ ๋ญ๋ผ ์ง€์–ด์•ผํ• ์ง€ ํ•œ์ฐธ ๋™์•ˆ ๊ณ ๋ฏผํ–ˆ๋‹ค. ์ ์ ˆํ•œ ์ œ๋ชฉ ์ถ”์ฒœ ๋ฐ›์Šต๋‹ˆ๋‹ค.

 

1. ๋ ˆ์ด์•„์›ƒ

์Šคํ† ๋ฆฌ๋ถ์„ ๋„์ž…ํ•˜๋ฉด์„œ ์ด๊ฑธ ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์ž˜ ์“ธ ์ˆ˜ ์žˆ์„๊นŒ ์ƒ๊ฐํ•˜๋‹ค๊ฐ€ ์ „์ฒด ํŽ˜์ด์ง€์—์„œ ์“ฐ๋Š” ์ปจํ…Œ์ด๋„ˆ์™€ ๋ ˆ์ด์•„์›ƒ์„ ๋ชจ๋“ˆํ™”์‹œ์ผœ๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค. ๋””์ž์ธํ•œ ํŽ˜์ด์ง€๋“ค์˜ ๋ ˆ์ด์•„์›ƒ์„ ๋ณด๋ฉด ์„ธ๊ฐ€์ง€๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค.

 

๊ฐ๊ฐ 1:3:3:1 / 1:3:4 / 1:7

[์ œ๋ชฉ๊ณผ ์„ค๋ช…/๋‚ด์šฉ(์ฃผ๋กœ ์ธํ’‹)/๋‹ค์Œ์œผ๋กœ ๋ฒ„ํŠผ]์œผ๋กœ ์ด๋ฃจ์–ด์ง„ ํŽ˜์ด์ง€, [์ œ๋ชฉ๊ณผ ์„ค๋ช…/๋‚ด์šฉ(์˜ˆ๋งค ๊ด€๋ จ ์ •๋ณด)]๋กœ ์ด๋ฃจ์–ด์ง„ ํŽ˜์ด์ง€, ๊ทธ๋ฆฌ๊ณ  [ํ‹ฐ์ผ“ ์ฝ˜ํ…์ธ ] ํ•˜๋‚˜๋กœ๋งŒ ์ด๋ฃจ์–ด์ง„ ํŽ˜์ด์ง€. ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ํŽ˜์ด์ง€๋“ค์—๋Š” ์ƒํƒœ์— ๋”ฐ๋ผ ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์ด ์žˆ์„ ์ˆ˜ ์žˆ์ง€๋งŒ ๊ทธ์™€ ์ƒ๊ด€ ์—†์ด ํ•ญ์ƒ ๊ทธ๋งŒํผ์˜ ์—ฌ๋ฐฑ์„ ๋‘์—ˆ๋‹ค.

 

.InfoLayout {
  height: 100%;
  display: grid;
  grid-template-rows: 3fr 4fr;
}

.ProgressLayout {
  height: 100%;
  display: grid;
  grid-template-rows: 3fr 3fr 1fr;
}

.TicketLayout {
  height: 100%;
  display: grid;
  grid-template-rows: 7fr;
}

๊ฐ ๋ ˆ์ด์•„์›ƒ์— ๋งž๊ฒŒ๋” ๋‚ด๋ถ€ ์š”์†Œ๋“ค์„ ๊ทธ๋ฆฌ๋“œ๋ฅผ ์ด์šฉํ•ด ๋น„์œจ์„ ์ •ํ•ด์ฃผ์—ˆ๋‹ค. ์–ด๋–ค ๋ ˆ์ด์•„์›ƒ์ด๋“ ์ง€ ์ „์ฒด ๊ทธ๋ฆฌ๋“œ fr๋“ค์˜ ํ•ฉ์ด 7์ด ๋˜๋„๋ก ํ–ˆ๋‹ค.

 

//Container.jsx

/**
 * ํ‹ฐ์ผ“ ๋ฐœ๊ธ‰,์ธ์ฆ ๊ณผ์ •์—์„œ TicketWarpContainer์˜ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋กœ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค
 * ์ƒ๋‹จ ๋’ค๋กœ๊ฐ€๊ธฐ๋ฒ„ํŠผ ์˜์—ญ๊ณผ , ๊ทธ ์˜์—ญ์„ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ์˜์—ญ์œผ๋กœ ๋‚˜๋‰˜์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค
 * grid : 1fr 7fr ์ž…๋‹ˆ๋‹ค.
 * TopElemnt prop์œผ๋กœ TicketTop ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ›์œผ๋ฉฐ
 * children์œผ๋กœ TicketLayout, ProgressLayout, InfoLayout ์ค‘ ํ•˜๋‚˜์˜ ๋ ˆ์ด์•„์›ƒ์„ ์ž์‹์œผ๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค.
 */
export const TicketContainer = ({ TopElement, children, ...props }) => {
  return (
    <div className="Ticket-Container" {...props}>
      <div className="Ticket-Inner-Container">
        <div className="Ticket-Inner-Top">
          {TopElement ? TopElement : <TopElement />}
        </div>
        <div className="Ticket-Inner-Content">{children}</div>
      </div>
    </div>
  );
};


//Container.css

.Ticket-Inner-Container {
  box-sizing: border-box;
  height: 100%;
  padding: 20px 6%;
  display: grid;
  grid-template-rows: 1fr 7fr;
  max-height: 620px;
  min-width: 300px;
}

๊ทธ ๋‹ค์Œ ๋ ˆ์ด์•„์›ƒ์„ ์ž์‹์œผ๋กœ ๋ฐ›์•„ ์ปจํ…Œ์ด๋„ˆ๋กœ ๊ฐ์‹ผ๋‹ค. ๊ทธ ์•ˆ์—์„œ <div className="Ticket-Inner-Top"></div> ์ด๋ผ๋Š” ๋…€์„์ด ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์ด ์žˆ์–ด์•ผ ํ•  ์˜์—ญ์„ ๋งก์•„์ค€๋‹ค. ์œ„์—์„œ ์ „์ฒด ๊ทธ๋ฆฌ๋“œ fr๋“ค์˜ ํ•ฉ์ด 7์ด ๋˜๋„๋ก ํ•œ ์ด์œ ๋Š” ์—ฌ๊ธฐ์— ์žˆ๋‹ค. ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์˜ ์˜์—ญ๊ณผ ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์˜ ๋น„์œจ์ด 1:7์ด ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—. ์ด์ œ ์ € ์ปจํ…Œ์ด๋„ˆ์™€ ๋ ˆ์ด์•„์›ƒ๋“ค์„ ๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ ๋ถˆ๋Ÿฌ์™€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

2. ์ปจํ…Œ์ด๋„ˆ

๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋ฐ”์ผ ํ™”๋ฉด์— ๋งž์ถฐ์„œ ๊ธฐํšํ–ˆ์ง€๋งŒ ํฐ ํ™”๋ฉด์—์„œ๋„ ๋ถˆํŽธํ•˜์ง€ ์•Š๊ณ ๋„ ์ด์˜๊ฒŒ ๋ณด์˜€์œผ๋ฉด ํ–ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ  ๋ชจ๋“  ํŽ˜์ด์ง€๋ฅผ ์™„์ „ํžˆ ๋ฐ˜์‘ํ˜•์œผ๋กœ ๋งŒ๋“ค๊ธฐ์—๋Š” ๊ตณ์ด ์‹ถ์€ ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

๊ฝค ์ด์˜๋‹ค. ๋‚˜๋ฆ„ ๊ฐ€์„ฑ๋น„ ์žˆ๋Š” ๋””์ž์ธ์ด๋ผ๊ณ  ์ƒ๊ฐ์ด ๋“ ๋‹ค. ๋„“์€ํ™”๋ฉด์ผ๋•Œ์˜ ๋’ท ๋ฐฐ๊ฒฝ๋„ ๋‹คํฌ ๊ณ„์—ด๋กœ ๋ฐ”๊ฟ”์•ผ์ง€ ์ƒ๊ฐ๋งŒ ํ•˜๋‹ค๊ฐ€ ์•„์ง๋„ ์•ˆํ–ˆ๋„ค. 

 

์Šคํ† ๋ฆฌ๋ถ์—์„œ ์ง์ ‘ ํ™•์ธํ•ด ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๋ชจ๋ฐ”์ผ์—์„œ 100vh ์Šคํฌ๋กค์ด ์ƒ๊ธฐ๋Š” ๋ฌธ์ œ.

์›น์ด์ง€๋งŒ ์•ฑ์ฒ˜๋Ÿผ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ํ•œ ๋””์ž์ธ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ๊ฒŒ ์Šคํฌ๋กค์ด ์ƒ๊ธฐ์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๊ฑฐ์˜€๋‹ค. ๊ทธ๋ž˜์„œ ์ปจํ…Œ์ด๋„ˆ์˜ height๋ฅผ 100vh๋กœ ์ฃผ์—ˆ๋Š”๋ฐ ์ด ๋ถ€๋ถ„์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค. ์ผ๋ฐ˜์ ์ธ ๋ฐ์Šคํฌํ†ฑ ๋ธŒ๋ผ์šฐ์ €์—์„  ์ •์ƒ์ ์œผ๋กœ ๋ณด์˜€์ง€๋งŒ, ์•„์ดํฐ ์‚ฌํŒŒ๋ฆฌ ๊ฐ™์€ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์Šคํฌ๋กค์ด ์ƒ๊ฒผ๋‹ค. 100vh์ผ๋•Œ ์ฃผ์†Œ์ฐฝ๊ณผ ํ•˜๋‹จ๋ฐ”๊ฐ€ ๋ทฐํฌํŠธ์— ํฌํ•จ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋ผ๊ณ  ํ•œ๋‹ค.

 

//index.js
const setScreenSize = () => {
  let vh = window.innerHeight * 0.01;

  document.documentElement.style.setProperty('--vh', `${vh}px`);
};
setScreenSize();

index.js์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ๋„ฃ๋Š”๋‹ค. window.innerHeight๋กœ ํ˜„์žฌ ๋ทฐํฌํŠธ์˜ ๋†’์ด๋ฅผ ๊ฐ€์ ธ์™€ ์ƒˆ๋กœ์šด vh์˜ ๋‹จ์œ„๋กœ ์‚ผ๋Š”๋‹ค. setProperty ๋กœ '--vh' ๋ผ๋Š” css ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

.Ticket-Container-Wrap {
  width: 100vw;
  height: calc(var(--vh, 1vh) * 100);
  display: flex;
  justify-content: center;
  align-items: center;
}

css ์†์„ฑ์€ ์œ„์™€ ๊ฐ™์ด ๊ฐ€์ ธ์™€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜์—์„œ ์ฐธ์กฐํ•˜๋Š” ์‚ฌ์šฉ์ž ์ง€์ • ์†์„ฑ์ด ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ํ•จ์ˆ˜๋Š” ๋‘ ๋ฒˆ์งธ ๊ฐ’์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ฐธ๊ณ . ์ด ๋ถ€๋ถ„์€ ๋ ˆ์˜ค๊ฐ€ ํ•ด๊ฒฐํ•ด์ค€ ๋ถ€๋ถ„์ด๋‹ค.

 

3. ๋ชจ๋‹ฌ

๋ชจ๋‹ฌ์ฐฝ์€ ๊ฐœ์ธ์ ์œผ๋กœ ์ข‹์•„ํ•˜๋Š” ๋””์ž์ธ ์ค‘ ํ•˜๋‚˜์ด๋‹ค. ๋งŽ์€ ์ •๋ณด๋ฅผ ๋„ฃ์–ด์•ผ ํ• ๋•Œ ์š”๊ธดํ•˜๊ฒŒ ์‚ฌ์šฉํ•œ๋‹ค.

import React from 'react';

const ModalComponent = ({ children }, ref) => {
  return (
    <div className="modal hidden" ref={ref}>
      <div
        className="modal-overlay"
        onClick={() => {
          ref.current.classList.add('hidden');
        }}
      ></div>
      <div className="modal-content">{children}</div>
    </div>
  );
};

export default React.forwardRef(ModalComponent);

ModalComponent.js์™€,

.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 100;
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal-overlay {
  background-color: rgba(0, 0, 0, 0.8);
  -webkit-backdrop-filter: blur(15px);
  backdrop-filter: blur(15px);
  position: absolute;

  width: 100%;
  height: 100%;
}

@media screen and (min-width: 600px) {
  .modal-overlay {
    width: calc(var(--containerWidth) - 3px);
    height: calc(var(--containerHeight) - 3px);
    border-radius: 15px;
  }
}

.hidden {
  display: none;
}

css ํŒŒ์ผ์ด๋‹ค.

 

setProperty๋ฅผ ์ด์šฉํ•ด์„œ ๋ชจ๋‹ฌ ์˜ค๋ฒ„๋ ˆ์ด ์„ค์ •ํ•˜๊ธฐ.

์œ„์—์„œ ๋ฐฐ์šด setProperty๋ฅผ ์—ฌ๊ธฐ์„œ ์ž˜ ์จ๋จน์—ˆ๋‹ค. position:absolute;์™€ ํ•จ๊ป˜ width์™€ height๋ฅผ ๋ชจ๋‘ 100%๋กœ ์ฑ„์›Œ ์ „์ฒด ํ™”๋ฉด์„ ์–ด๋‘ก๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋„“์€ ํ™”๋ฉด์—์„  ๋ชจ์„œ๋ฆฌ๊ฐ€ ๋‘ฅ๊ทผ ์‚ฌ๊ฐํ˜• ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—๋งŒ ์ฑ„์›Œ์ ธ์•ผ ํ•œ๋‹ค.

 

export const handleResize = () => {
  const [container] = document.getElementsByClassName('Ticket-Container');
  if (!container) return;
  document.documentElement.style.setProperty(
    '--containerHeight',
    `${container.clientHeight}px`
  );
  document.documentElement.style.setProperty(
    '--containerWidth',
    `${container.clientWidth}px`
  );

์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ฐ€์ ธ์™€ ์—˜๋ฆฌ๋จผํŠธ์˜ ๋†’์ด์™€ ๋„ˆ๋น„๋ฅผ css ๋ณ€์ˆ˜์— ๋„ฃ์–ด๋‘๊ณ ,

 

  useEffect(() => {
    handleResize();
    window.addEventListener('resize', handleResize);
    return () => {
      // cleanup
      window.removeEventListener('resize', handleResize);
    };
  }, []);

์ปดํฌ๋„ŒํŠธ์—์„œ ์ด๋ฒคํŠธ๋ฆฌ์Šค๋„ˆ์— ์ฝœ๋ฐฑ์œผ๋กœ ๋‹ฌ์•„์ฃผ์—ˆ๋‹ค.

์ด์ œ ๋ธŒ๋ผ์šฐ์ € ์‚ฌ์ด์ฆˆ๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ๋งค๋ฒˆ css ๋ณ€์ˆ˜์˜ ๊ฐ’์ด ๋ฐ”๋€Œ๋ฉด์„œ modal-overlay์˜ ๋„ˆ๋น„์™€ ๋†’์ด ๊ฐ’์ด ์ ์ ˆํ•˜๊ฒŒ ์œ ์ง€๋œ๋‹ค.

 

ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ref ์ „๋‹ฌํ•˜๊ธฐ.

์ € ๋ชจ๋‹ฌ ์ฝ”๋“œ๋ฅผ ๋‹ค๋ฅธ ๊ณณ์—์„œ๋„ ์‚ฌ์šฉํ•ด์•ผํ•  ์ผ์ด ์žˆ์–ด์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋”ฐ๋กœ ๋บ๋‹ค. ๊ทธ๋žฌ๋”๋‹ˆ ์ž˜ ๋˜๋˜ ๋ชจ๋‹ฌ์ด ๊ฐ‘์ž๊ธฐ ์•ˆ๋˜๋Š”๊ฒŒ ์•„๋‹Œ๊ฐ€.. className์„ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•ด์„œ ๋‹ฌ์•„๋†จ๋˜ ref๊ฐ€ ์ „๋‹ฌ์ด ์•ˆ๋ผ์„œ ์ƒ๊ธด ๋ฌธ์ œ์˜€๋‹ค.

 

https://ko.reactjs.org/docs/refs-and-the-dom.html

๊ณต์‹๋ฌธ์„œ์—์„œ ๊ทธ๋ ‡๋‹ค๊ณ  ํ•˜๋‹ˆ.. forwardRef๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์•˜๋‹ค. export ํ•˜๊ธฐ ์ „์— ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ๋ฅผ forwardRef๋กœ ๊ฐ์‹ธ์„œ ๋ณด๋‚ด์ฃผ์—ˆ๋‹ค. ์ด์ œ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ์— ref๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 

4. input ์ปดํฌ๋„ŒํŠธ

๋ฐฐ๋ฆฌ์—์ด์…˜์ด ๋งŽ์•„์„œ ๊ทธ๋Ÿฐ์ง€ ์Šคํ† ๋ฆฌ๋ถ์—์„œ input ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด์„œ ์œ ๋‚œํžˆ ์˜ค๋ž˜ ๊ฑธ๋ ธ๋‹ค. ๋•๋ถ„์— ๋ง˜์— ๋“œ๋Š” ๊ฒฐ๊ณผ๋ฌผ์ด ๋‚˜์™”๋‹ค.

 

์ž๋™ focus, ์ž๋™ blur

์ฒ˜์Œ ํ‹ฐ์ผ“ ์˜ˆ๋งค ๊ณผ์ • ๋””์ž์ธ์„ ํ•˜๋ฉด์„œ ๊ฐ€์žฅ ํž˜๋“ค์—ˆ๋˜ ๋ถ€๋ถ„์€ '๋‹ค์Œ์œผ๋กœ' ๋ฒ„ํŠผ์ด์—ˆ๋‹ค. ๋ฒ„ํŠผ์˜ ์œ„์น˜๋„ ์œ„์•„๋ž˜๋กœ ๊ณ„์† ์›€์ง์—ฌ๋ณด๊ณ  ๋ฒ„ํŠผ์˜ ๋””์ž์ธ๋„ ๋ฐ”๊ฟ”๋ณด๊ณ  ํ•˜๋ฉด์„œ ์ผ๋‹จ ์ด๋Ÿฐ ๊ฒฐ๊ณผ๋ฌผ์ด ๋‚˜์™”์ง€๋งŒ.. ์ข€ ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์žˆ์—ˆ์„ํ…๋ฐ.

 

๋‹ค์Œ์œผ๋กœ ๋„˜์–ด๊ฐˆ๋•Œ ํ™”๋ฉด์ด ์˜ค๋ฅธ์ชฝ์—์„œ ์™ผ์ชฝ์œผ๋กœ ์Šฌ๋ผ์ด๋“œํ•˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์žˆ์—ˆ์œผ๋ฉด ์ข‹๊ฒ ๋‹ค๋Š” ์˜๊ฒฌ๊ณผ, ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋ฒ„ํŠผ์ด ์˜ค๋ฅธ์ชฝ ๋ฐ‘์— ์žˆ์—ˆ์œผ๋ฉด ์ข‹๊ฒ ๋‹ค๋Š” ์˜๊ฒฌ์ด ์žˆ์–ด์„œ ๋ฒ„ํŠผ ์œ„์น˜๋ฅผ ์ €๋ ‡๊ฒŒ ๋†“์•˜๋‹ค. ์—„์ง€๊ฐ€ ๋‹ฟ๊ธฐ์—๋„ ํŽธํ•œ ์œ„์น˜์˜€๋‹ค. ๋ณด๊ธฐ์—” ์ด๋ปค์ง€๋งŒ ๋ฌธ์ œ๊ฐ€ ํ•˜๋‚˜ ์žˆ์—ˆ๋‹ค. ๋ชจ๋ฐ”์ผ์—์„œ ์ € ํŽ˜์ด์ง€๋ฅผ ๋ณด๋ฉด ํ‚คํŒจ๋“œ์— ๋‹ค์Œ์œผ๋กœ ๋ฒ„ํŠผ์ด ๊ฐ€๋ ค์ง€๊ฒŒ ๋œ๋‹ค.

 

์ธํ’‹์— ํฌ์ปค์Šค๊ฐ€ ๋˜๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ํ•จ๊ป˜ ๋ณด๋ผ์ƒ‰ ์ค„๋กœ ๋ฐ”๋€๋‹ค. ์ฒ˜์Œ ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง๋ ๋•Œ ์ธํ’‹์— ์ž๋™์œผ๋กœ ํฌ์ปค์Šค๊ฐ€ ๋˜๋„๋ก ํ–ˆ๋‹ค. (๋ชจ๋ฐ”์ผ ํ™”๋ฉด์—์„  ๋ฐ”๋กœ ํ‚ค๋ณด๋“œ๊ฐ€ ์˜ฌ๋ผ์˜จ๋‹ค ๋ผ๋Š” ์ƒ๊ฐ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๋„ฃ์—ˆ์ง€๋งŒ ์‹ค์ œ๋กœ ํ‚ค๋ณด๋“œ๊ฐ€ ์˜ฌ๋ผ์˜ค์ง€๋Š” ์•Š์•˜๋‹ค. ๋ธŒ๋ผ์šฐ์ €๋‹จ์—์„œ ๋ง‰๋Š”๊ฐ€๋ณด๋‹ค.)

์ธํ’‹์— ์˜ˆ๋ฅผ ๋“ค์–ด ์ธ์ฆ๋ฒˆํ˜ธ ๊ธธ์ด์ธ 6๊ธ€์ž๊ฐ€ ์ž…๋ ฅ๋˜๋ฉด ๋‹ค์‹œ ์ž๋™์œผ๋กœ ํฌ์ปค์Šค๊ฐ€ ํ’€๋ฆฌ๋„๋ก ํ–ˆ๋‹ค. focus()์™€ blur() ๋ฉ”์†Œ๋“œ๋กœ ๋™์ž‘ํ•œ๋‹ค. ๋ชจ๋ฐ”์ผ ํ™”๋ฉด์—์„  ์ž๋™์œผ๋กœ ํ‚ค๋ณด๋“œ๊ฐ€ ๋‚ด๋ ค๊ฐ„๋‹ค. ํ‚ค๋ณด๋“œ๋ฅผ ์ง์ ‘ ๋‚ด๋ ค์•ผ ํ•˜๋Š” ๋ถˆํŽธํ•จ ์—†์ด '๋‹ค์Œ์œผ๋กœ' ๋ฒ„ํŠผ์„ ์‰ฝ๊ฒŒ ๋ˆ„๋ฅผ ์ˆ˜ ์žˆ๋‹ค.

 

์ด ์—ญ์‹œ ์Šคํ† ๋ฆฌ๋ถ์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๊ต‰์žฅํžˆ ํŽธํ•˜๋‹ค. ์ •ํ•ด์ค€ length๋งŒํผ ์ž…๋ ฅํ•˜๋ฉด ์•„์˜ˆ ์ž…๋ ฅ์„ ์ œํ•œํ•˜๋ฉด์„œ ํด๋ผ์ด์–ธํŠธ๋‹จ์—์„œ ๋ณด๋‚ด๋Š” ๊ฐ’๋“ค์„ ํ•œ๋ฒˆ ๋” ๊ฒ€์‚ฌํ•ด์ฃผ๋Š” ์—ญํ• ๋„ ํ•ด์ค€๋‹ค. ๊ฝค ์ด์˜๊ธฐ๋„ ํ•˜๋‹ˆ ๋งค์šฐ ๋งŒ์กฑํ•˜๋Š” ๋ถ€๋ถ„์ด์—ˆ๋‹ค.

 

input border ์• ๋‹ˆ๋ฉ”์ด์…˜

์• ๋‹ˆ๋ฉ”์ด์…˜ ์ž์ฒด๋ณด๋‹ค ์ € ๋ณด๋ผ์ƒ‰ ๋ง‰๋Œ€๊ธฐ ์œ„์น˜๋ฅผ ์žก๋Š”๊ฒŒ ์–ด๋ ค์› ๋‹ค. ็”ด ์˜ ํ˜•ํƒœ๋กœ ๊ทธ๋ฆฌ๋“œ๋ฅผ ๋ฐฐ์น˜ํ–ˆ๋‹ค. grid-column: span 3; ์œผ๋กœ ํ•˜๋‚˜์˜ ์š”์†Œ๊ฐ€ ๊ทธ๋ฆฌ๋“œ ์—ฌ๋Ÿฌ ์นธ์„ ์ฐจ์ง€ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์ด์ „๊นŒ์ง„ ๋‹จ์ˆœํžˆ grid-template-์œผ๋กœ ๋น„์œจ๋งŒ ๊ณ ์ •ํ•ด์ฃผ๋Š” ์šฉ๋„๋กœ ๊ทธ๋ฆฌ๋“œ๋ฅผ ์ผ์—ˆ๋Š”๋ฐ, ๊ทธ๋ฆฌ๋“œ๊ฐ€ ์ด๋ ‡๊ฒŒ ํŽธํ•˜๊ณ  ์ข‹์€๊ฑฐ์˜€๋‹ค๋‹ˆ.

 

์• ๋‹ˆ๋ฉ”์ด์…˜ ์ž์ฒด๋Š” ์ด๋ ‡๊ฒŒ ์ค„ ์ˆ˜ ์žˆ๋‹ค.

.input-box ~ .border-animate {
  margin: 0 auto;
  width: 0%;
  height: 2px;
  transition: 0.4s;
}

.input-box:focus ~ .border-animate {
  width: 100%;
  height: 2px;
  background-color: #bf94e4;
}

~(๋ฌผ๊ฒฐ) ์„ ํƒ์ž๋Š” ์ฒ˜์Œ ์•Œ์•˜๋‹ค. A~B ๋กœ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ Aํƒœ๊ทธ์™€ ํ˜•์ œ๊ด€๊ณ„์— ์žˆ๋Š” ๋ชจ๋“  Bํƒœ๊ทธ๊ฐ€ ์„ ํƒ๋œ๋‹ค.

 

์ธ์ฆ๋ฒˆํ˜ธ ์žฌ์ „์†ก 3๋ถ„ ํƒ€์ด๋จธ

์ด๊ฒŒ ์ƒ๊ฐ๋ณด๋‹ค ์€๊ทผ ๊นŒ๋‹ค๋กญ๋”๋ผ.

export const Timer = ({ time, setTime }) => {
  const [isRunning, setIsRunning] = useState(true);
  const savedCallback = useRef();

  const callback = () => {
    setTime(time - 1);
  };
  useEffect(() => {
    savedCallback.current = callback;
  });

  useEffect(() => {
    const tick = () => {
      savedCallback.current();
      console.log('tick');
    };
    let timer;
    if (isRunning) {
      timer = setInterval(tick, 1000);
    }

    return () => clearInterval(timer);
  }, [isRunning]);

  useEffect(() => {
    if (time == 0) {
      setIsRunning(false);
    }
  }, [time]);

  return (
    <div className="indicator">
      {parseInt(time / 60)}:{('00' + parseInt(time % 60)).slice(-2)}
    </div>
  );
};

time ์ƒํƒœ๋ฅผ ๋ฐ›์•„์™€์„œ setInterval()๋กœ 1์ดˆ๋งˆ๋‹ค ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์ฃผ๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ 2:59์—์„œ ๋ฉˆ์ถฐ๋ฒ„๋ฆฌ๋Š” ์ผ์ด ์ƒ๊ฒผ๋‹ค.

 

๋ฌธ์ œ๋Š” useEffect๊ฐ€ count๋ฅผ ์ฒซ ๋ Œ๋”์—์„œ ์žก์•„๋ฒ„๋ฆฌ๋Š” ํ˜„์ƒ ๋•Œ๋ฌธ์— ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ Œ๋”์—์„œ count๋Š” 0์ž…๋‹ˆ๋‹ค. ์ดํŽ™ํŠธ๋ฅผ ์žฌ์ ์šฉํ•˜์ง€ ์•Š์•„์„œ setInterval์— ์žˆ๋Š” ํด๋กœ์ €๊ฐ€ ํ•ญ์ƒ ์ฒซ ๋ Œ๋”์˜ count๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  count + 1์€ ๊ณ„์† 1์ด ๋˜๋Š” ๊ฒƒ์ด์ฃ .

๋ผ๊ณ  ํ•œ๋‹ค. ์ฐธ๊ณ . ๋งค ๋ Œ๋”๋ง๋งˆ๋‹ค ๋ฐ”๋€Œ๋Š” time ์ƒํƒœ๋ฅผ ref์— ๋‹ด์•„๋†“๊ณ  setInterval() ํ•จ์ˆ˜์˜ ์ฝœ๋ฐฑ์— ์‚ฌ์šฉํ•œ๋‹ค. ref๋Š” ์ปดํฌ๋„ŒํŠธ ์‚ฌ์ดํด ์•ˆ์—์„œ ๊ฐ’์ด ์œ ์ง€๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์žฌ๋žœ๋”๋ง๋˜๋Š”๋™์•ˆ ๊ฐ’์ด ๋ณดํ˜ธ๋œ๋‹ค.

 

time์ด 0์ด ๋˜๋ฉด isRunning state๋ฅผ false๋ฅผ ๋ฐ”๊พผ๋‹ค. useEffect ํ›…์—์„œ isRunning ๊ฐ’์ด ๋ฐ”๋€Œ๋Š”๊ฑธ ๋ณด๊ณ  0์ด ๋˜๋ฉด clearInterval()๋ฅผ ์‹คํ–‰ํ•˜๋ฉฐ ํƒ€์ด๋จธ๋ฅผ ์ข…๋ฃŒํ•œ๋‹ค.