Instagram Clone Project(HTML/CSS/JS)

mementomori·2021년 1월 22일
0
post-thumbnail

프로젝트 소개

  • 인스타그램의 로그인 페이지 및 피드 화면(페이지)를 최대한 동일하게 구현하고,
    javascript를 통해 일부 기능 구현해보기

로그인 페이지

1. 화면 구현

: 최대한 Relative Unit을 적용하여 창 크기에 맞게 사이즈 자연스럽게 변화하도록 구현
: box-shadow 적용을 통해 box 그림자 효과 적용

2. input 란에 글자가 입력되면 로그인 버튼 활성화

: input 란에 글자 입력되면, 버튼 활성화 및 background color 변화

3. 이메일 및 비밀번호 validation

: 이메일 형식(@ 포함 등ㅇ) 맞고, 비밀번호는 최소 5이상의 숫자 또는 문자만 입력하도록 유효성 체크
: 유효성 통과 or 미통과에 따른 메세지 alert 창으로 보여주기

function validate(e) {
  e.preventDefault();
  const valEmail = document.querySelector('.email').value;
  const valPwd = document.querySelector('.password').value;
  console.log(valPwd);

  if (valEmail) {
    const regExp = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i;
    valEmail.match(regExp)
      ? alert('유효한 이메일 주소입니다.')
      : alert('올바른 이메일 주소를 입력해주세요');

    if (valPwd) {
      // 5자이상 숫자 또는 영자
      const regExp = '^[a-zA-Z0-9]{5,}$';
      valPwd.match(regExp)
        ? alert('유효한 패스워드 입니다.')
        : alert('올바른 패스워드를 입력해주세요');
    }
  } else {
    alert('이메일 주소를 입력해주세요.');
  }
}

input.addEventListener('click', validate);

피드(메인) 페이지

1. 화면 구현

: 우상단 nav, 좌측 feed 영역, 우측 추천 프로필 영역으로 나누어 구현
: relative unit을 통해 반응형에 대응

2. 새로운 댓글 작성 및 좋아요, 삭제 UI 및 기능 구현

: 신규 댓글 작성후 게시 버튼 클릭하면 하단에 표시
: 동시에 우측 '좋아요' 및 '삭제' 버튼 기능 구현

const feeds = document.querySelector('.feeds');
const input = document.querySelector('.postingCmt');
const button = document.querySelector('#post');

function addPost() {
  const cmtText = input.value;

  const newCmt = document.createElement('p');
  const likeImg = document.createElement('img');
  const likeCount = document.createElement('span');
  const removeCmt = document.createElement('button');

  if (cmtText) {
    likeImg.src = '/img/heart.png';

    removeCmt.innerText = '삭제';

    newCmt.classList.add('newCmt');
    newCmt.innerText = cmtText;
    newCmt.appendChild(removeCmt);
    newCmt.appendChild(likeCount);
    newCmt.appendChild(likeImg);

    feeds.appendChild(newCmt);

    input.value = '';

    // 댓글 좋아요 기능 추가(토글 버튼 방식)
    const likes = document.querySelectorAll('.newCmt img');
    let likeFlag = false;

    function likeChange() {
      if (!likeFlag) {
        this.src = '/img/heartRed.png';
        likeFlag = !likeFlag;
      } else {
        this.src = '/img/heart.png';
        likeFlag = !likeFlag;
      }
    }

    likes.forEach((like) => {
      like.addEventListener('click', likeChange);
    });

    // 댓글 삭제 기능 추가
    const removeCmts = document.querySelectorAll('.newCmt button');

    function deleteCmt() {
      this.parentNode.remove();
    }

    removeCmts.forEach((rmcmt) => {
      rmcmt.addEventListener('click', deleteCmt);
    });
  } else return;
}

button.addEventListener('click', addPost);

3. 중앙 상단 검색 창 ID 검색 기능

: 중앙 상단 검색 창에 찾고자 하는 id 입력하면, 해당 검색어를 포함하는 프로필 list를 표시
: 결과 리스트를 보여주는 container 위치는 창 크기 변경 등에 영향 받지 않도록 구현

// 검색 창 아이디 검색 기능
const ids = [
  'wecode',
  'jung',
  'hyun',
  'we',
  'code',
  'dasom',
  'cheerup',
  'letsgo',
  'wework',
];

const search = document.querySelector("input[type ='search']");
const nav = document.querySelector('.navContainer nav');

function findId(e) {
  if (e.keyCode == 13) {
    const targetId = search.value;
    const result = ids.filter((id) => id.includes(targetId));
    const listContainer = document.createElement('div');
    listContainer.classList.add('listContainer');

    if (targetId && result[0]) {
      for (var i = 0; i < result.length; i++) {
        const matchedList = document.createElement('p');
        const profileImg = document.createElement('img');

        matchedList.classList.add(`matchedIdName`);
        matchedList.innerText = result[i];

        profileImg.src = '/img/profile.jpeg';

        listContainer.appendChild(profileImg);
        listContainer.appendChild(matchedList);
      }

      nav.appendChild(listContainer);

      const coordinate = this.getBoundingClientRect();
      const targetBottom = coordinate.bottom;
      const targetLeft = coordinate.left;

      listContainer.top = `${targetBottom + 20}px`;
      listContainer.left = targetLeft;
    } else {
      alert('검색하신 ID가 존재하지 않습니다.');
    }
  } else return;
}

search.addEventListener('keydown', findId);
search.addEventListener('blur', () => window.location.reload());

4. nav 프로필 사진 클릭 시 메뉴 표시

: 우측 상단 nav 프로필 사진 클릭 시 메뉴 표시
: 새로 보여지는 메뉴 container는 창 크기 변경에 영향 받지 않도록 구현

// nav 프로필 사진 클릭 시 메뉴 박스 생성 (Mission 8)
const navProfile = document.querySelector('#profileImg');
const menuContainer = document.querySelector('.menuContainer');
const body = document.querySelector('body');

function showMenu(e) {
 if (e.target == navProfile) {
   const coordinate = this.getBoundingClientRect();
   const targetBottom = coordinate.bottom;
   const targetLeft = coordinate.left;

   menuContainer.top = `${targetBottom + 20}px`;
   menuContainer.left = targetLeft;

   menuContainer.style.display = 'block';
 } else {
   menuContainer.style.display = 'none';
 }
}

body.addEventListener('click', showMenu);

5. 반응형 UI (window width 일정 수준 이하면 변화) 구현

: css media query 기능을 이용하여, 창 너비가 800 px 이하로 떨어지면, 우측 추천 프로필 영역 숨기기

/* width가 800px 이하로 떨어지면 우측 박스(.main-right) 사라짐 */
@media screen and (max-width: 800px) {
  .mainRight {
    display: none;
  }
  .mainLeft {
    width: 100%;
  }
}
profile
21c Carpenter

0개의 댓글