Experience/[Clone YouTube] GyuTube

[Clone YouTube] Contents

winCow 2021. 8. 13. 10:22

작업을 진행하다보니 며칠동안 기록을 남기지 않았다. 틈틈이 기록을 이어나가야겠다.

 

1. Main Page

import React from "react";
import ContentsBox from "./contentsComponent/ContentsBox";
import Header from "./headerComponents/Header";
import Navigator from "./navigatorComponents/Navigator";
import app from "../styles/app.css";

const Main = () => {
  return (
    <>
      <Header />
      <Navigator />
      <ContentsBox />
    </>
  );
};

export default Main;

메인 화면에서는 Header, Navigator, ContentsBox 세 개의 컴포넌트를 불러올 것이다. Header와 Navigator는 이전에 어느 정도 작업을 진행했기 때문에 ContentsBox를 마크업하고 기능을 추가했다.

 

 

2. ContentsBox

import React from "react";
import styled from "styled-components";
import FilterBar from "./filterComponents/FilterBar";
import VideoBox from "./videoBox/VideoBox";

const ContentsBoxWrap = styled.article`
  width: calc(100% - 72px);
  margin-left: 72px;
  padding-top: calc(56px + 56px);
  display: flex;
  justify-content: center;
`;

const ContentsBox = () => {
  return (
    <ContentsBoxWrap>
      <FilterBar />
      <VideoBox />
    </ContentsBoxWrap>
  );
};

export default ContentsBox;

ContentsBox는 StyledComponent를 이용해 틀을 잡고, 상단의 FilterBar와 비디오의 목록을 표시해 줄 VideoBox로 나눴다. 

 

파란 박스 안의 상단부가 FilterBar, 비디오들이 담긴 박스가 VideoBox이다.

 

 

3. FilterBar

import React from "react";
import styled from "styled-components";
import FilterBtns from "./filterBtns/FilterBtns";

const FilterBarWrap = styled.div`
  width: 92vw;
  height: 56px;
  position: fixed;
  top: 0;
  left: 72px;
  margin-top: 56px;
  border-top: solid 1px hsl(0, 0%, 80%);
  border-bottom: solid 1px hsl(0, 0%, 80%);
  display: flex;

  align-items: center;
  justify-content: flex-start;
  background-color: white;
  overflow-x: hidden;
  .first {
    margin: 12px 12px 12px 24px;
  }
`;

const FilterBar = () => {
  return (
    <FilterBarWrap>
      <FilterBtns></FilterBtns>
    </FilterBarWrap>
  );
};

export default FilterBar;

FilterBar 역시 StyledComponent로 버튼들을 담을 컨테이너를 만든 후, FilterBtns들을 담았다. 

 

import React from "react";
import styled from "styled-components";

const StyledBtns = styled.button`
  margin: 12px;
  padding: 0 12px;
  height: 32px;
  border: solid 1px hsl(0, 0%, 80%);
  border-radius: 16px;
  min-width: 52.67px;
`;

const FilterBtns = () => {
  return (
    <>
      <StyledBtns className="first">전체</StyledBtns>
      <StyledBtns>농구</StyledBtns>
      <StyledBtns>요리 프로그램</StyledBtns>
      <StyledBtns>음악</StyledBtns>
      <StyledBtns>믹스</StyledBtns>
      <StyledBtns>피트니스</StyledBtns>
      <StyledBtns>스포츠 비디오 게임</StyledBtns>
      <StyledBtns>배구</StyledBtns>
      <StyledBtns>침착맨</StyledBtns>
      <StyledBtns>케인인</StyledBtns>
      <StyledBtns>반려동물</StyledBtns>
      <StyledBtns>최근에 업로드된 동영상</StyledBtns>
      <StyledBtns>감상한 동영상</StyledBtns>
    </>
  );
};

export default FilterBtns;

마크업을 우선적으로 진행하기 위해, 버튼의 내용물은 일단 하나하나 작성하여 입력해 두었다.

 

 

4. VideoBox

import React from "react";
import VideoSummary from "./videoSummary/VideoSummary";
import styled from "styled-components";

const VideoBoxWrap = styled.ul`
  margin-top: 24px;
  max-width: 88vw;
  height: 100%;
  display: flex;
  flex-wrap: wrap;
`;

const VideoBox = () => {
  const getName = () => {
    const candidate = [
      "Cat",
      "DancingMan",
      "Firework",
      "GreenBoat",
      "OkinawaSun",
      "Sunset",
      "Train",
      "Lake",
      "KouheiMagic",
      "Funyafunya",
    ];
    return candidate;
  };

  return (
    <VideoBoxWrap>
      {getName().map((movie) => {
        return <VideoSummary key={movie} videoName={movie} />;
      })}
    </VideoBoxWrap>
  );
};

export default VideoBox;

VideoBox 역시 레이아웃을 잡기 위해 우선 일일이 데이터를 입력했다. 먼저 VideoBoxWrap을 정의하여 각각의 목록이 담길 컨테이너를 만들고, 이 안에 개별 비디오의 썸네일, 제목 등이 들어갈 VideoSummary 컴포넌트를 입력했다. 지금은는 미리 가지고 있는 동영상의 이름들로 candidate 배열을 만들었으나, 이후에는 백엔드에서 받아온 데이터를 입력하는 형식으로 수정할 예정이다. 또, 반VideoSummary들의 너비가 VideoBox의 너비를 초과하지 못하도록 flex-wrap을 wrap으로 설정했다.

VideoBox 컴포넌트에서 사실상 처음으로 리액트의 기능을 사용했는데, getName을 통해 얻은 candidate 배열의 모든 각각의 요소에 대해, map AIP를 이용해 VideoSummary에 movie의 이름을 Props로 전달할 것이다.

 

import React from "react";
import styled from "styled-components";
import VideoExplanaion from "./videoExplanaion/VideoExplanaion";
import { Link } from "react-router-dom";

const VideoBoxWrap = styled.li`
  list-style: none;
  max-width: 359px;
  max-height: 298px;
  min-width: 250px;
  min-height: 140px;
  width: 20%;
  margin: 0 8px 40px 8px;
  video {
    width: 100%;
    height: 54.3%;
    background-color: black;
  }
  :hover {
    cursor: pointer;
  }
  :hover .fa-ellipsis-v {
    display: flex;
  }
`;

const onMouseEnterVideo = (e) => {
  const playPromise = e.target.play();
};

const onMouseOutVideo = (e) => {
  e.target.pause();
  e.target.currentTime = 0;
};

const VideoSummary = ({ videoName }) => {
  return (
    <VideoBoxWrap>
      <Link to={`/playing/${videoName}`}>
        <video
          src={`/videos/${videoName}.mp4`}
          onMouseEnter={onMouseEnterVideo}
          onMouseOut={onMouseOutVideo}
          muted={true}
        />
      </Link>
      <VideoExplanaion />
    </VideoBoxWrap>
  );
};

export default VideoSummary;

VideoSummary 컴포넌트는 위와 같이 작성했다. 클릭하면 동영상 페이지로 이동시키기 위해 React Router를 사용했는데, 이는 나중에 추가하였으므로 추후에 다시 포스팅하기로 한다. VideoBoxWrap은 반응형으로 만들기 위해 with와 height의 min, max값을 지정해 주었다. 유튜브에서는 마우스가 영상 위로 올라가면 자동 재생이 되므로, 이를 구현하기 위해 onMouseEnter, onMouseOut 이벤트에 각각 onMouseEnterVideo, onMouseOutVideo 함수를 만들어 전달했다. onMouseEnterVideo는 해당 비디오에 play() 메서드를 입력해 플레이되도록 했다. 프로미스를 작성했으나 이는 콘솔 창의 경고 메세지를 없애기 위한 것으로 아직 공부가 안됐다. 이 부분은 추가적인 공부가 필요하다. onMouseOutVideo는 타겟 비디오에 pause()를 걸어 멈추고, 이후 currentTime을 0으로 바꿔 영상을 초기화하도록 했다. 

여기서 생각지도 못한 문제가 발생했는데, 크롬 정책에 의해 video의 자동 재생이 금지되어 있다는 것이었다. 사용자가 원치 않는 소음에 노출되지 않도록 하기 위해 이러한 정책을 사용한다고 하는데, muted 속성을 true로 부여함으로써 해결할 수 있었다.

이렇게 VideoSummary 컴포넌트에 Video를 입력했고, 그 아래에는 VideoExplanation 컴포넌트를 만들어 추가했다.

 

 

5. VideoExplanaion

import React from "react";
import styled from "styled-components";
import MenuBox from "./videoExplanaionDetails/MenuBox";
import TitleBox from "./videoExplanaionDetails/TitleBox";
import UserIcon from "./videoExplanaionDetails/UserIcon";

const VideoExplanationWrap = styled.div`
  width: 100%;
  display: flex;
`;

const VideoExplanaion = () => {
  return (
    <VideoExplanationWrap>
      <UserIcon />
      <TitleBox />
      <MenuBox />
    </VideoExplanationWrap>
  );
};

export default VideoExplanaion;

VideoExplanaion은 다시 UserIcon, TitleBox, MenuBox로 세분화했다. 이 세 컴포넌트는 최하위 컴포넌트로 특별한 것이 없으므로 생략한다.