[React] - FE 신입 포트폴리오 (준비 과정 기록용)

ain·2022년 8월 23일
0

포트폴리오

목록 보기
4/4
post-thumbnail

유투브에서 우연히 velopert님의 좋아요 기능 구현 라이브를 보게 되었는데 앱 페이지 기획부터 시작해서 피그마로 직접 디자인하시는 것을 보고 나도 무작정 시작하지 말고 피그마로 디자인부터 짜야겠다고 생각을 했다.

하지만...바로바로 아이디어가 떠오르지 않았다(디자인 생각보다 어렵다고 생각하게 됨).

<극초기 디자인>
포트폴리오 극초기 디자인

설명을 해보자면 왼쪽은 고정되어있는 '사이드바'이고 스크롤을 내리거나 올려도 나의 깃헙, 블로그, 그리고 연락방식을 항상 접근할 수 있도록 만든 것이다.
그리고 오른쪽에 모여있는 스택들은 왼쪽 사이드바에 들어가는 아이콘 모으다가 스택들도 아이콘이 필요하니까 미리 가져오려고 일단 모아두었었다.

그러다가...직접 만들면서 구상을 해야 아이디어가 떠오를 것 같아서 디자인을 하다말고 중간에 바로 코드를 치기 시작했다.

사용 언어 및 라이브러리


JS 라이브러리

  • React

React 라이브러리

  • react-router-dom (SPA를 위한 라우터)
  • react-router-hash-link (하나의 페이지에서 카테고리간의 이동을 위한 라이브러리)
  • react-media (react에서 쓸 수 있는 미디어쿼리)

CSS in JS

  • styled-components

아이콘 라이브러리

  • react-bootstrap-icons

애니매이션 라이브러리

  • framer-motion
  • typeit-react

사이드 Bar (헤더 Bar로 변경)


처음에는 위의 극초기 디자인 그대로 화면을 만드려고 했지만 막상 만들어보니 너무 붕 떠있는 느낌이여서 헤더에 고정되어있게 변경하였다.
여기에는 Contact(깃허브, 블로그, 이메일) 아이콘을 제외하고, 포트폴리오 목록도 해당목록으로 이동할 수 있게 만들 것이기 때문에 고정된 헤더바를 만들기로 결정하였다.
그렇게 해서 아래 사진처럼 만들게 되었다.
헤더바 초기 디자인

이 부분을 작업할 때 기억에 남는게 있다면...

1. svg 컴포넌트

아이콘을 svg로 넣을때 코드가 너무 길어서 따로 SVG 컴포넌트를 만들었다.

export const SVG = (props) => {
  return (
    <svg
      width={props.size ? props.size : '2rem'}
      height={props.size ? props.size : '2rem'}
      role='img'
      viewBox='0 0 24 24'
      fill={props.color}

      <title>{props.name}</title>
      <path d={svg[props.name]} />
    </svg>
  );
};

이렇게 틀을 만든 뒤, 아이콘을 사용할 다른 컴포넌트에서 <SVG />에 props로 전달할 값들을 넣어 재사용이 가능하게 만들었다. svg 속성으로 들어가는 path부분은 SVG컴포넌트 안에 객체로 만들어서 사용하면 된다.
블로그: SVG 컴포넌트 만들기

2. 화면 크기

화면 사이즈를 줄이거나, 모바일로 봤을 때는 헤더 배치를 어떻게 할 것인지 고민이 많이 되었다.
우선, 나의 이름이 화면에서 계속 보였으면 좋겠어서 로고를 하나 새로 만들었고, 포트폴리오 목록은 오른쪽에, 그리고 컨택트 아이콘들은 헤더 우하단에 고정시켜놓았다.
화면을 줄여도 모든 목록과 컨텍트 아이콘들이 나왔으면 좋겠어서 일단 웹 버전은 이렇게 만들었다. 앱 버전은 화면 너비가 더 좁아지기 때문에 추후에 다시 만들 예정이다.
최종 헤더 바
위 디자인이 최종적으로 만들어진 화면이다.
당장은 한국에 있는 회사에 지원할 예정이기 때문에 목록도 영어가 아닌 한국어로 쓰는게 적절한 것 같아 바꾸었다.

3. 포트폴리오 목록

목록 부분을 클릭하면 해당 목록 부분으로 가도록 리액트 라이브러리인 react-router-hash-link를 사용하였다. react-router-dom에 있는 Link로는 페이지간 이동만 가능하고, 목록으로는 이동을 할 수가 없기 때문에 hash-link를 사용하였다.


자기소개


자기소개 부분은 한 문장으로 간결하게 작성하였다. 더 자세한 소개는 다른 페이지로 들어가서 볼 수 있도록 로고 부분과 이미지 우하단에 위치한 +버튼을 클릭하면 나에 대해 더 자세히 알 수 있는 페이지로 이동하게 구현하였다.
자기소개 화면

자기소개 부분에 애니매이션이 많아서 재밌었던 부분이 많았는데, 몇가지로 추려보자면...

1. 이름

  • 이름을 돋보이게 하기 위해 다채로운 색상과 애니매이션도 적용하였다.
    우선, 이름은 상대방에게 강렬하게 기억이 남도록 하고 싶었기 때문에 이름만은 톡 튀는 색상으로 주었다. 마우스를 올리면 애니매이션이 동작하는데 이 애니매이션은 그렇게 중요한 것은 아니여서 '어쩌다' 발견할 수 있도록 하기 위해 애니매이션을 부각시키진 않았다.
    이름 애니매이션

2. 자세히 보기

  • 프로필이미지 우하단에는 나의 상세한 소개를 볼 수 있도록 링크를 버튼으로 만들어 걸어두었다.
    프로필 이미지 부분에 +버튼이 그냥 미동도 없이 붙어있으면 보이지도 않을뿐더러 흥미로워 보이지가 않아서 애니매이션을 넣기로 하였다.
    여기서 framer-motion을 처음 써보게 되었다.
    velopert님의 유튜브 라이브에서 framer-motion을 쓰시는 것을 본 후로 또 어디선가 본 적이 있어서 (어디였는지 기억이 안남...) framer-motion을 써보았다.
    써봤는데...진~~~~짜 간편하고 모션이 이쁘게 나온다.↓
    플러스 버튼 애니메이션
    그리고 마우스를 올리면 '자세히' 라는 텍스트가 나오게 만들었다.
    플러스 hover시 애니매이션

3. typeit

  • typeit 애니매이션으로 타이핑을 하는 느낌으로 나를 형용하는 문구를 작성해주었다.
    '저는'과 '아인입니다' 사이에는 typit 애니매이션 라이브러리를 사용해서 타자를 라이브로 치고 있는 것 같은 느낌을 주었고, 하나의 문장이 써졌다가 사라지면 다음에는 다른 내용이 나올 수 있도록 만들었다. 나는 나를 형용하고 싶은 문장이 많은데 그걸 다 나열하자니 읽기 싫어질것 같고, 하나만 적자니 너무 뻔할 것 같아 typeit 애니매이션을 쓰게 되었다.
    (실제로 이런 내용으로 쓰진 않았다. 예시일뿐!)
    소개부분 애니매이션
    처음엔 이 라이브러리를 썼더니 글씨가 써질 때마다 저 밑줄 부분의 높이(height)가 위아래로 왔다갔다하는 바람에 페이지 전체가 움직이는 상황이 있었다. 그래서 간단하게 저 typeit 부분에 div박스를 하나 만들고 height을 지정해 주었더니 해결이 되었다.

기술스택

기술 스택 부분에서는 그냥 기술 아이콘만 띄울지, 내가 이 기술을 어디까지 써봤는지 상세 내용도 적어볼지 고민을 해봤는데 그냥 아이콘만 띡 띄우면 내 실력이 어디까지인지 알 수가 없을 것이기 때문에 상세내용도 적자고 결단을 내렸다.

상세내용이 있기 때문에 자리를 꽤 차지해서 정렬을 어떻게 할지 고민하다가 최종적으로는 한줄에 4개씩 배치하기로 했다.
기술스택 정렬

각 기술 아이콘의 박스에 상세내용을 적고, 이를 클릭하면 전체화면으로 펼칠 예정이다. 전체화면으로 펼치는 이유는 더 많은 상세내용을 볼 수 있도록 하기 위함이다.

현재 화면을 보면 우하단에 조그만한 삼각형 버튼이 있다. 이 부분은 맨 위로 가는 버튼이다.

맨위로 가기 버튼


화면 높이가 500까지 내려가면 이 버튼이 아래에서 위로 귀엽게 튀어나온다.(framer-motion 사용) 여기에 걸어놓은 이벤트는 scroll 이벤트인데, 스크롤 할때마다 이벤트가 발생하기 때문에 debounce를 적용할 예정이다.
이 버튼을 누르면 화면의 최상단으로 이동하게 된다. (정확히는 자기소개 부분으로 이동한다.)

처음에는 DOM을 이용해서 scrollTop이 0이 되게 하였는데 hash-link를 사용하는게 더 부드러워 보여서 이로 변경하였다.

맨위로 가기 버튼


프로젝트


+(2022. 08. 25 업데이트)

프로젝트 부분에는 프로젝트 규모에 따라 필터링해서 볼 수 있도록 만들어보았다.
초기화면에서는 아래 사진처럼 전체보기가 눌려져 있는 것처럼 보이고, 프로젝트도 다 나열되어 있다.

프로젝트 파트

만약 사이드 프로젝트만 보고싶거나 미니 프로젝트만 보고 싶다면 그에 해당하는 카테고리 버튼을 누르면 그 카테고리에 해당하는 프로젝트만 볼 수 있게 코드를 짜보았다!
프로젝트 카테고리

카테고리 버튼 색상 바뀌는 것과 카테고리에 맞게 렌더링 되는 것은 useReducer를 사용하여 카테고리를 클릭할 때마다 state가 바뀌도록 해주었다.

// 프로젝트 카드 ui
<ProjectCategoryAll>
  <span
    style={{
      backgroundColor: state.btnColor.all,
      color: state.fontColor.all,
      fontWeight: state.fontBold.all,
    }}
    onClick={() => {
      dispatch({ type: 'ALL' });
    }}

    전체보기
  </span>
  <span
    style={{
      backgroundColor: state.btnColor.side,
      color: state.fontColor.side,
      fontWeight: state.fontBold.side,
    }}
    onClick={() => {
      dispatch({ type: 'SIDE' });
    }}

    사이드 프로젝트
  </span>
  <span
    style={{
      backgroundColor: state.btnColor.mini,
      color: state.fontColor.mini,
      fontWeight: state.fontBold.mini,
    }}
    onClick={() => {
      dispatch({ type: 'MINI' });
    }}

    미니 프로젝트
  </span>
</ProjectCategoryAll>

1. 카테고리 버튼

reducer에서는 버튼의 배경 색상, 버튼 텍스트 색상, 텍스트 굵기, 그리고 category를 변경해주는 일을 한다. 초기 화면에서는 모든 프로젝트가 나와야 하기 때문에 초기 상태값은 '전체보기'를 눌렀을 때와 똑같은 상태가 된다. 그리고 다른 버튼이 눌렸을 때 전체보기에 적용되었던 값은 눌려진 버튼에 적용되어야 한다.

2. 프로젝트 카드

프로젝트 카드의 ui는 똑같아서 프로젝트의 정보를 객체로 된 static data로 만들었고, 이 data를 map으로 돌려서 컴포넌트를 반복해주었다.

처음엔 어떻게 코드를 짜야할까 고민을 많이 했다.
'전체보기' 버튼을 누르면 프로젝트 카드가 전부 렌더링 되어야 하는데, data.category 객체를 전부 배열로 만들고 '전체보기' 문자열을 넣으면 나중에 진행중인 프로젝트가 많아졌을때 전부 다 '전체보기'를 넣어야 하니 좋지 않을 것이다.
그러다 떠오른 최후의 수단은 전체보기의 카테고리는 배열로 ['사이드 프로젝트', '미니 프로젝트'] 이렇게 주고 '사이드 프로젝트'나 '미니 프로젝트'는 문자열로 주면 렌더링 할 때 '이 state.category에 data.category가 포함되어 있는가?'로 필터링 할 수 있다. 포함 되어있다면 렌더링, 없다면 렌더링 하지 않게 includes 메서드를 사용해 주었다.

'전체보기'가 눌려진 상태로 초기화 해준다.

// ProjectReducer.js (initArg)
export const initCategory = {
  btnColor: {
    all: palette.greenColor,
    side: palette.deeperWhite,
    mini: palette.deeperWhite,
  },
  fontColor: {
    all: palette.bgColor,
    side: palette.fontColor,
    mini: palette.fontColor,
  },
  fontBold: {
    all: '700',
    side: null,
    mini: null,
  },
  category: ['사이드 프로젝트', '미니 프로젝트'],
};

action type이 'SIDE'일 때 사이드 프로젝트의 버튼이 눌려져야 하고, 렌더링은 카테고리가 '사이드 프로젝트'인 데이터(프로젝트)만 렌더링 되어야 한다.

// ProjectReducer.js (reducer)
export const categoryReducer = (state, action) => {
  switch (action.type) {
    case 'SIDE':
      return {
        btnColor: {
          all: palette.deeperWhite,
          side: palette.greenColor,
          mini: initCategory.btnColor.mini,
        },
        fontColor: {
          all: palette.fontColor,
          side: palette.bgColor,
          mini: initCategory.fontColor.mini,
        },
        fontBold: {
          all: null,
          side: '700',
          mini: null,
        },
        category: '사이드 프로젝트',
        /* 여기 있는 category가
        ui를 그릴때 접근할 data의 category와 같은지,
        또는 포함되어 있는지 확인 한 후 렌더링 함. */
      };
      ...
      ...
  }
}
// ProjectData.js (data)
export const projectData = [
  {
    id: 1,
    category: '미니 프로젝트',
    name: '프로젝트 이름',
    description: '프로젝트 설명',
    img: projectImg,
    imgSize: {
      width: 240,
      height: 370,
    },
  },
  ...
}
// Project.js (jsx) -> ui 그리는 코드
export const Project = () => {
    return (
     {data.map(
      (d) =>
        state.category.includes(d.category) && (
          <EachProject key={d.id}>
            // 프로젝트 이미지
            <ProjectImgBox>
              <ProjectImg
                src='https://via.placeholder.com/240x370'
                alt='프로젝트 이미지'
                width={d.imgSize.width}
                height={d.imgSize.height}
              />
            </ProjectImgBox>
            <ProjectDesc>
              // 프로젝트 카테고리
              <ProjectCategory>
                <span>{d.category}</span>
              </ProjectCategory>
              // 프로젝트 이름
              <ProjectName>
                <span>
                  {d.name}
                </span>
              </ProjectName>
              // 프로젝트 상세설명
              <Projectintro>
                <span>
                  {d.description}
                </span>
              </Projectintro>
            </ProjectDesc>
          </EachProject>
        )
    )}
  )
}

// +(2022. 08. 25 업데이트)


남은 작업


  • 각 모바일 기종별 화면 너비에 맞는 화면 만들기.
  • 새로고침 시 url은 초기화 하기. (현재는 hash-link로 이동한 상태에서 새로고침을 하면 그 해쉬가 url에 남아있음.) -> 다른 사이트를 보니 url은 초기화 하지 않는 걸 보아 굳이 초기화는 안해도 될 것 같다.
  • <프로젝트> 부분 구현하기.
  • 스크롤 이벤트에 Debounce 걸기.
  • 상세소개 부분 작업.
  • 기술스택 상세내용 적기.
  • 기술스택 전체화면 버튼 만들기.
profile
프론트엔드 개발 연습장 ✏️ 📗

0개의 댓글