Figma 디자인 가지고 반응형 페이지 만들기

오늘의 목적은 Width 크기에 따라서 디자인을 한 Figma 의 디자인 요소들을 가지고,

실게로 React 페이지에 적용을 시키는 것을 하려 합니다.

내 디자인

저는 다음과 같이 디자인을 했습니다.

총 3가지 버전이죠.

L의 경우는 1200px 을 기준으로 하며, 이보다 더 커질 경우는 알아서 가로로 늘어나는 형태로 제작했습니다.

M의 경우는 L이 900px 이하로 줄어들면 나타나게 되며, 이때 버튼이 세로로 배열이 됩니다.

S의 경우는 360px 까지 줄어들면 나타나게 되며, 사진이 잘리는 대신에 배경색이 추가가 됩니다.

그럼 이것들을 효율적으로 React 코드에 나타내기 위해서 어떤 작업을 하면 좋을지 보겠습니다. 

내 디자인에 쓰인 컴포넌트 구성

우선 테스트 L의 페이지는 다음과 같은 요소들로 구성이 되어 있습니다.

여기서 메뉴바를 뜻하는 GNB 를 제외하고는 컴포넌트를 사용하지 않았습니다.

때문에 우선은 이 디자인에서 공통적으로 사용이 되는 디자인을 컴포넌트로 바꿔주는 작업을 해볼까 싶습니다. 

메뉴 부분을 제외하고 보겠습니다.

마지막 페이지를 제외하고, 각각의 페이지는 “사진” “사진 타이틀” “버튼” 으로 구성이 되어 있습니다.

그렇다면 “사진” “사진 타이틀” “버튼” 을 구성하는 특정한 컴포넌트를 제작 가능할 것입니다. 

어떤 사진을 넣는지, 어떤 타이틀 텍스트를 넣는지, 버튼의 텍스트와 경로는 어떻게 지정할 것인지 정도만 입력하면 알아서 구성이 되는 컴포넌트를요.

이를 위해서 우선 가로 1200 세로 720의 사각형을 만들어 줍시다.

왼쪽과 같은 디자인으로 오른쪽과 같이 버튼을 제작할 수 있습니다.

여기 색깔만 다르게 해서 다른 버튼 하나 또 만들 수 있겠죠?

이 버튼을 위에서 생성했던 사각형 내부로 적절한 위치에 넣어줍시다.

Text 를 삽입하고 적절한 위치로 배치를 해주면, 위와 같은 요소를 얻을 수 있습니다.

이제 이걸 컴포넌트화 하기 위해서 위 녀석들을 한 번에 선택하고 마우스 우클릭을 해줍시다.

Add Auto Layout 이라는 것을 선택하여 위 요소들을 하나의 Frame 으로 묶어줍시다.

그러면 위처럼 됩니다.

Frame 으로 변하면서 서식이 틀어져서 그런 건데요.

Frame을 클릭하고 우측 조작판으로 가서 다음과 같이 설정을 변경합시다. 

왼쪽처럼 Auto Layout 을 조절하면, 우측처럼 Titla 과 Button 사이에 알아서 공간이 채워지며 Button 들이 아래로 내려가게 됩니다.

좌우에는 202라는 공통의 패딩을 넣었습니다. 

또한 타이틀이 너무 위에 딱 붙어서 배치되는 것을 막기 위해 위쪽에도 120 패딩을 넣었습니다. 

이건 그냥 알아서 내 눈에 예쁘게 고치면 될 것 같습니다. 

그럼 문제가 이제 타이틀 텍스트를 중앙으로 정렬을 해주겠습니다. 

Frame 내부에 있는 Title을 선택하고, 조작판을 만져보죠. 

왼쪽처럼 텍스트 설정을 해주니까 이제 좀 제가 원하는 도안이 되었네요.

어떤 텍스트를 채워넣고, 어떤 그림을 뒤에 넣느냐에 따라서 하나의 페이지 디자인이 만들어지겠죠?

버튼을 눌렀을 때 어떤 경로로 될 것인지 등은 React 코드 쪽에서 수정을 하도록 하고…

우선은 이 디자인대로 React 컴포넌트를 제작을 해봅시다. 

React 컴포넌트 제작과 디자인

컴포넌트를 제작할 때 왼쪽 처럼 DEV 모드에서 Layout 과 Style 을 체크할 수도 있으나,

Anima 플러그인을 활용하면 아예 style.css 형태로 특정 요소의 스타일을 가져올 수도 있습니다. 

이건 편한 방법을 이용하면 될 것 같습니다. 

페이지에 활용할 버튼을 하나 React로 만들어보겠습니다. 

				
					import React from "react";
import "./style.css";

const ButtonPrimary = () => {
  return (
    <div className="button-primary">
      <div className="label">Label</div>
    </div>
  );
};

export default ButtonPrimary;
				
			
				
					.button-primary {
    align-items: center;
    background-color: #865bff99;
    border-radius: 100px;
    display: inline-flex;
    gap: 10px;
    justify-content: center;
    padding: 10px;
    position: relative;
  }
  
.button-primary .text-wrapper {
color: #ffffff;
font-family: "Inter-ExtraBold", Helvetica;
font-size: 16px;
font-weight: 800;
height: 36px;
letter-spacing: 0;
line-height: 14px;
margin-top: -1px;
position: relative;
text-align: center;
width: 202px;
}

.label {
    display: flex;
    width: 202px;
    height: 36px;
    flex-direction: column;
    justify-content: center;
  }
  
  .label{
    color: #FFF;
    text-align: center;
    font-family: Inter;
    font-size: 12px;
    font-style: normal;
    font-weight: 800;
    line-height: 14px; /* 116.667% */
  }
  
				
			

위와 같은 js 와 css 를 이용하여 위와 같은 버튼을 만들 수 있었습니다.

이제 이 버튼에 Props 를 추가하여 버튼을 클릭하면 특정 URL로 이동을 할 수 있도록 해볼까요?

				
					import React from "react";
import { useNavigate } from "react-router-dom";
import "./style.css";

const ButtonPrimary = ({ label, goal }) => {
  const navigate = useNavigate();

  const onClick = () => {
    navigate(goal);
  };

  return (
    <div className="button-primary">
      <div className="label" onClick={onClick}>
        {label}
      </div>
    </div>
  );
};

export default ButtonPrimary;

				
			
				
					import React from "react";
import ButtonPrimary from "./Component/ButtonPrimary";

const NewHome = () => {
  return (
    <>
      <div style={{ width: "500px" }}>
        <ButtonPrimary label={"구매하기"} goal={"/구매하기"} />
      </div>
    </>
  );
};

export default NewHome;

				
			

이를 위해서 버튼 컴포넌트를 왼쪽과 같이 변경해주고, 이를 사용하기 위해서는 오른쪽과 같이 사용하면 됩니다.

이번에는 다른 쪽 버튼 컴포넌트도 하나 만들어주겠습니다. 

				
					import React from "react";
import { useNavigate } from "react-router-dom";
import "./style.css";

const ButtonDetail = ({ label, goal }) => {
  const navigate = useNavigate();

  const onClick = () => {
    navigate(goal);
  };

  return (
    <div className="button-detail">
      <div className="label" onClick={onClick}>
        {label}
      </div>
    </div>
  );
};

export default ButtonDetail;

				
			
				
					.button-detail {
    align-items: center;
    border-radius: 100px;
    background: rgba(255, 111, 241, 0.60);
    display: inline-flex;
    gap: 10px;
    justify-content: center;
    padding: 10px;
    position: relative;
    cursor: pointer;
  }
  


.label {
    display: flex;
    width: 202px;
    height: 36px;
    flex-direction: column;
    justify-content: center;
    color: #FFF;
    text-align: center;
    font-family: Inter;
    font-size: 12px;
    font-style: normal;
    font-weight: 800;
    line-height: 14px; /* 116.667% */
  }
  
				
			

이런 식으로 새로운 디자인의 버튼도 하나 생성했습니다.

만들고 싶은 정형화된 페이지는 뒷배경에 특정한 이미지가 들어가고, 

그 이미지 앞에 타이틀 텍스트가 나타나며, 아래로 버튼 2개가 위치한 형태이죠?

필요한 버튼을 만들었으니, 이제는 이미지 박스 컴포넌트를 만들어보죠. 

				
					// ImageBox.js
import React from "react";
import ButtonDetail from "./ButtonDetail";
import ButtonPrimary from "./ButtonPrimary";

const ImageBox = ({
  title,
  primaryLabel,
  primaryGoal,
  detailLabel,
  detailGoal,
  backgroundImage,
}) => {
  return (
    <div
      className="LargePage"
      style={{ backgroundImage: `url(${backgroundImage})` }}
    >
      <div className="page-title">{title}</div>

      <div className="button-space">
        <ButtonPrimary label={primaryLabel} goal={primaryGoal} />
        <ButtonDetail label={detailLabel} goal={detailGoal} />
      </div>
    </div>
  );
};

export default ImageBox;

				
			
				
					/* Button Styles */
.button-primary {
    align-items: center;
    border-radius: 100px;
    background: rgba(134, 92, 255, 0.60);
    display: inline-flex;
    gap: 10px;
    justify-content: center;
    padding: 10px;
    position: relative;
    cursor: pointer;
  }
  
  .button-detail {
    align-items: center;
    border-radius: 100px;
    background: rgba(255, 111, 241, 0.60);
    display: inline-flex;
    gap: 10px;
    justify-content: center;
    padding: 10px;
    position: relative;
    cursor: pointer;
  }
  
  /* Label Styles */
  .label {
    display: flex;
    width: 202px;
    height: 36px;
    flex-direction: column;
    justify-content: center;
    color: #FFF;
    text-align: center;
    font-family: Inter;
    font-size: 12px;
    font-style: normal;
    font-weight: 800;
    line-height: 14px; /* 116.667% */
  }
  
  /* Page Styles */
  .LargePage {
    align-items: center;
    background-position: 50% 50%;
    background-size: cover;
    display: flex;
    flex-direction: column;
    height: 720px;
    justify-content: space-between;
    padding: 120px 202px 0px;
    position: relative;
  }
  
  .page-title {
    overflow: hidden;
    color: #FFF;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: Inter;
    font-size: 60px;
    font-style: normal;
    font-weight: 800;
    line-height: normal;
  
    display: flex;
    width: 796px;
    height: 161px;
    max-height: 400px;
    flex-direction: column;
    justify-content: center;
    flex-shrink: 0;
  }
  
  .LargePage .button-space {
    flex: 0 0 auto !important;
    align-items: flex-end;
    display: inline-flex;
    gap: 24px;
    justify-content: center;
    padding: 0px 0px 106px;
    position: relative;
  }
  

				
			

저는 위와 같이 ImageBox 라는 컴포넌트를 만들었습니다.

이곳에는 내가 넣고 싶은 이미지의 경로, 그 이미지의 타이틀, 들어갈 버튼 정보를 입력 가능합니다.

예시로 한 번 하나의 박스를 생성해보겠습니다. 

요런 식으로 제가 지정한 내용대로 나타나는 것을 확인했습니다.

만족스럽습니다.

고러믄 이제 이런 페이지를 아래로 쭉 내려보겠습니다. 

				
					import React from "react";

import "./style.css";

import ImageBox from "./Component/ImageBox";

import Model3 from "./Component/img/model3.png";
import Modelc from "./Component/img/modelc.png";
import Models from "./Component/img/models.png";
import Modelx from "./Component/img/modelx.png";

const NewHome = () => {
  return (
    <>
      <div className="home">
        <ImageBox
          title={"Model3"}
          primaryLabel={"구매하기"}
          primaryGoal={"/purchase"}
          detailLabel={"알아보기"}
          detailGoal={"/detail"}
          backgroundImage={Model3}
        />
        <ImageBox
          title={"ModelS"}
          primaryLabel={"구매하기"}
          primaryGoal={"/purchase"}
          detailLabel={"알아보기"}
          detailGoal={"/detail"}
          backgroundImage={Models}
        />
        <ImageBox
          title={"ModelX"}
          primaryLabel={"구매하기"}
          primaryGoal={"/purchase"}
          detailLabel={"알아보기"}
          detailGoal={"/detail"}
          backgroundImage={Modelx}
        />
        <ImageBox
          title={"CyberTruck"}
          primaryLabel={"구매하기"}
          primaryGoal={"/purchase"}
          detailLabel={"알아보기"}
          detailGoal={"/detail"}
          backgroundImage={Modelc}
        />
      </div>
    </>
  );
};

export default NewHome;

				
			

위와 같이 home 이라는 div 내부에 ImageBox 를 쭉 밀어 넣어주면, 알아서 줄줄이 세로로 ImageBox 가 배치됩니다.

 

위 영상처럼 잘 나타나는 것을 확인했습니다.

피드백 받을 부분이 있다면 Title 부분 색이 하얀 색이라서 보기가 안 좋네요. 

이건 이따가 고치도록 하죠. 

다음은 이제 어떤 식으로 반응형 페이지를 제작할 것인지 설계입니다. 

				
					import React, { useEffect, useState } from "react";
import ButtonDetail from "./ButtonDetail";
import ButtonPrimary from "./ButtonPrimary";

const ImageBox = ({
  title,
  primaryLabel,
  primaryGoal,
  detailLabel,
  detailGoal,
  backgroundImage,
}) => {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      setWindowWidth(window.innerWidth);
    };

    window.addEventListener("resize", handleResize);
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);

  if (windowWidth > 900) {
    return (
      <div
        className="LargePage"
        style={{ backgroundImage: `url(${backgroundImage})` }}
      >
        <div className="large-page-title">{title}</div>
        <div className="button-space">
          <ButtonPrimary label={primaryLabel} goal={primaryGoal} />
          <ButtonDetail label={detailLabel} goal={detailGoal} />
        </div>
      </div>
    );
  } else if (windowWidth > 360) {
    return (
      <div
        className="MediumPage"
        style={{ backgroundImage: `url(${backgroundImage})` }}
      >
        <div className="medium-page-title">{title}</div>
        <div className="button-space">
          <ButtonPrimary label={primaryLabel} goal={primaryGoal} />
          <ButtonDetail label={detailLabel} goal={detailGoal} />
        </div>
      </div>
    );
  } else {
    return (
      <div
        className="SmallPage"
        style={{ backgroundImage: `url(${backgroundImage})` }}
      >
        <div className="small-page-title">{title}</div>
        <div className="button-space-vertical">
          <ButtonPrimary label={primaryLabel} goal={primaryGoal} />
          <ButtonDetail label={detailLabel} goal={detailGoal} />
        </div>
      </div>
    );
  }
};

export default ImageBox;

				
			

저는 위와 같이 기존 ImageBox 내부에서 windowWidth 라는 값에 따라서 각기 다른 return값을 출력하도록 하였습니다.

windowwidth 가 900 초과에서는 계혹해서 LargePage 가 나타나게, 

만약 900~360 일 때는 MediumPage 가 나타나게, 그 외에는 SmallPage 가 나타나게 설정을 했습니다. 

각각의 return 값에 특정한 ClassName 을 넣어주고, 이를 CSS에 적용하면 되겠죠?

마치며...

이것으로 Figma 의 요소를 이용하여 화면 너비에 따라 어떻게 페이지를 구성할지 알아보았습니다.

해당 방법으로 보다 다양한 방식으로 디자인을 나타낼 수 있을 것 같습니다.^^