본문 바로가기

DEVELOP

[React.js] style-component와 framer-motion 컴포넌트화 하기

728x90

페이지별 공통으로 사용하는 스크립트나 scss는 import를 시키면 코드를 효율적으로 줄일 수 있다. 

이처럼 style-comonponent와 framer-motion 또한 공통 파일로 꾸려서 import 하면 효과적으로 코드를 작성할 수 있다. 

예를 들어 270줄이였던 한 페이지를 94줄로 줄였었다. 

방법은 간단하다.

1. src 폴더 안에 js/styled.js 라는 임의의 js 파일을 생성

2. 공통으로 사용하는 스타일 컴포넌트와 프레이머 모션을 다 가져와서 styled.js에 저장한다. 

그후 사용하려는 페이지에 import 시킨다. 

 

주의할 점은 styled.js 파일 안에 const 변수를 꼭 export 시켜야 외부에서도 사용이 가능하다.

 

[src/js/styled.js]

import styled from 'styled-components';
import { media } from 'style/media_query';
import { motion } from 'framer-motion';

export const Spacing = styled.div`
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2%;
  padding: 110px 32px 0;
  ${media.large`
    padding-top: 70px;
  `};
  ${media.medium`
    padding: 70px 24px 0;
  `};
  ${media.small`
    flex-direction: column;
    padding: 40px 20px 0;
    gap: 15px 0;
  `};
`;
export const CaptureMove = styled.div`
  overflow: hidden;
  border: 2px solid #000;
  background-color: #000;
  border-radius: 7px;
  > video {
    width: 100%;
  }
`;
export const Device = styled.div`
  overflow: hidden;
  flex-basis: calc(40% - 10px / 3 * 2);
  &.mo {
    flex-basis: calc(20% - 10px / 3 * 2);
    /* ${media.small`
			width: 35%;
		`}; */
  }
`;
export const DeviceName = styled.p`
  color: ${(props) => props.theme.textColor.gray.first};
  font-size: 16px;
  font-weight: 700;
  text-align: right;
  width: 100%;
  padding: 0 12px 8px 0;
  opacity: 0.7;
`;
export const GridFrame = styled.div`
  flex: 0 0 auto;
`;

export const Grid = styled(motion.div)`
  overflow-x: hidden;
  overflow-y: auto;
  background-color: rgba(251, 234, 173, 0.7);
  border-radius: 7px;
  box-shadow: ${(props) => props.theme.shadow.box};
  cursor: pointer;
  max-height: 600px;
  &::-webkit-scrollbar {
    width: 8px;
  }
  &::-webkit-scrollbar-thumb {
    background-color: #807d7d;
    border-radius: 6px;
    border: 6px solid #fbeaad;
  }
  &::-webkit-scrollbar-track {
    background-color: rgb(128, 125, 125, 0.9);
    border-radius: 6px;
  }
  > img {
    display: block;
    width: 100%;
  }
`;

export const Modal = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  position: fixed;
  left: 0;
  top: 0;
  z-index: 9;
  height: calc(var(--vh, 1vh) * 100);
`;

export const Overlay = styled(motion.div)`
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  background-color: #000;
  opacity: 0.4;
  z-index: -1;
`;

export const GridWhole = styled(motion.div)`
  width: 95%;
  border-radius: 7px;
  position: relative;
  overflow: hidden;
  /* ${media.medium`
		border-bottom-left-radius: 16px;
		border-bottom-right-radius: 16px;
	`};
  ${media.small`
		border-bottom-left-radius: 14px;
		border-bottom-right-radius: 14px;
	`}; */
  > svg {
    position: absolute;
    right: 20px;
    top: 16px;
    width: 22px;
    height: 22px;
    padding: 10px;
    border-radius: 50%;
    background-color: rgba(66, 66, 66, 0.6);
    color: #fff;
    cursor: pointer;
  }
`;
export const GridBody = styled.div`
  height: calc(var(--vh, 1vh) * 82);
  overflow-x: hidden;
  overflow-y: auto;
  &::-webkit-scrollbar {
    width: 10px;
  }
  &::-webkit-scrollbar-thumb {
    background-color: #807d7d;
    border-radius: 6px;
    border: 6px solid #fbeaad;
  }
  &::-webkit-scrollbar-track {
    background-color: rgb(128, 125, 125, 0.9);
    border-radius: 6px;
  }
  > img {
    display: block;
    width: 100%;
    border-bottom-left-radius: 20px;
    border-bottom-right-radius: 20px;
    ${media.medium`
			border-bottom-left-radius: 16px;
			border-bottom-right-radius: 16px;
		`};
    ${media.small`
			border-bottom-left-radius: 14px;
			border-bottom-right-radius: 14px;
		`};
  }
`;

// motion
export const overlay = {
  hidden: { backgroundColor: 'rgba(0, 0, 0, 0)' },
  visible: { backgroundColor: 'rgba(0, 0, 0, 1)' },
  exit: { backgroundColor: 'rgba(0, 0, 0, 0)' },
};

export const girdVariants = {
  start: {
    border: '3px solid transparent',
  },
  hover: {
    borderColor: '#ffcc42',
    y: -20,
    scale: 1.05,
  },
};

 

[sub_pages/Example.tsx]

import { AnimatePresence } from 'framer-motion';
import { Spacing, CaptureMove, DeviceName, GridFrame, Grid, Modal, Overlay, GridWhole, GridBody, overlay, girdVariants } from 'js/styled.js';
import '../style/sub.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faXmark } from '@fortawesome/free-solid-svg-icons';
import fullMp4 from 'img/sub_pages/event/full.mp4';
import mainKo from 'img/sub_pages/event/page.webp';
import mainMo from 'img/sub_pages/event/page_mo.webp';
import { useEffect, useState } from 'react';
import { focusHandler, resetHandler } from 'function/ModalScroll';

function Roball() {
  const [data, setData] = useState<any[]>();
  const [id, setId] = useState<null | string>(null);
  const [device, setDevice] = useState<null | number>(null);
  const [func, setFunc] = useState<any>({ on: null, off: null });
  useEffect(() => {
    // data
    const imgArr = [mainKo, mainMo];
    let isMount = true;
    if (isMount) {
      setData(imgArr);
      setFunc({ on: focusHandler, off: resetHandler });
    }
    return () => {
      isMount = false;
      setData([]);
      setFunc({});
    };
  }, []);
  return (
    <div className="sub">
      <div>
        <DeviceName>인터렉션 영상</DeviceName>
        <CaptureMove>
          <video muted autoPlay loop>
            <source type="video/mp4" src={fullMp4} />
          </video>
        </CaptureMove>
      </div>
      <Spacing>
        {data?.map((val: any, i: any) => (
          <GridFrame key={i} className={i === 0 ? 'first' : 'second'}>
            <DeviceName>{i === 0 ? 'PC' : 'Mobile'}</DeviceName>
            <Grid
              layoutId={i}
              onClick={() => {
                setId(val);
                setDevice(i);
                func.on();
              }}
              variants={girdVariants}
              initial="start"
              whileHover="hover"
            >
              <img src={val} alt="작업물 이미지" />
            </Grid>
          </GridFrame>
        ))}
      </Spacing>
      <AnimatePresence>
        {id ? (
          <Modal>
            <Overlay
              variants={overlay}
              onClick={() => {
                setId(null);
                func.off();
              }}
              initial="hidden"
              animate="visible"
              exit="exit"
            />
            <GridWhole layoutId={id} style={{ width: device == 1 ? '35%' : '' }}>
              <FontAwesomeIcon
                icon={faXmark}
                onClick={() => {
                  setId(null);
                  func.off();
                }}
              />
              <GridBody>
                <img src={id} alt="작업물 이미지" />
              </GridBody>
            </GridWhole>
          </Modal>
        ) : null}
      </AnimatePresence>
    </div>
  );
}

export default Roball;

 

import { Spacing, CaptureMove, DeviceName, GridFrame, Grid, Modal, Overlay, GridWhole, GridBody, overlay, girdVariants } from 'js/styled.js';

 

위와 같이 import 시키면 페이지 어디에서나 사용이 가능하다. 

중괄호 {} 안에는 js/styled.js 파일안에서 외부로 사용하려는 변수 명들을 언급해야 한다.