[JS] 인스타그램 따라만들기

목차

1. aside 부분 돔 구현
2. 댓글 추가, 삭제 및 좋아요 기능
3. 게시글에 좋아요 누르기
4. nav에 아이디 검색 기능
5. 후기

4월 12일부터 위코드 부트캠프 생활을 시작했다 !
바닐라 자바스크립트로 인스타그램 로그인페이지와 메인페이지를 만들었다.
다음주 3주차에는 인스타그램을 리액트를 이용해서 다시 구현해볼 것이다.

구현한 기능

  1. 댓글 추가 삭제 및 각각 댓글의 좋아요 누르기
  2. 아이디 검색 시 팝업창을 띄워서 현재 페이지에 보이는 아이디만 보이기

신경 쓴 부분

오른쪽 aside부분 프로필 리스트 추가 시 객체들의 배열을 만들고 배열을 반복하면서 돔을 그려나갔다. API를 받아와서 처리할 때 객체와 배열 사용에 익숙해져야 해서 하드코딩을 지양하고 유지보수, 확장성이 용이하게 만들려고 노력했다 !

1. aside 부분 돔 구현

const users = [
  { id: 'jiyon', img: './images/hao.jpg' },
  { id: 'Shaman_king', img: './images/hao2.jpg' },
  { id: 'iu', img: './images/iu.jpg' },
  { id: 'Jeon_dongseok', img: './images/dongseok.jpg' },
  { id: 'kakashi', img: './images/kakashi.png' },
  { id: 'gaara', img: './images/gaara.jpg' },
  { id: 'hyoshin', img: './images/hyoshin.jpeg' },
  { id: 'lock-li', img: './images/lock-li.jpg' },
  { id: 'naruto', img: './images/naruto.jpg' },
]
const userIds = users.reduce((pre, cur) => {
  return [...pre, cur.id]
}, [])
const userImgs = users.reduce((pre, cur) => {
  return [...pre, cur.img]
}, [])
  • 각각 id와 img를 받아올 users객체를 선언했다.
  • id만의 배열과 img만의 배열을 만들기 위해 고민하다가 머릿속 어딘가에서 reduce가 갑자기 떠올라서 reduce메서드를 사용해봤다. 아이디 검색기능 구현 시 사용했다.
const createProfileLists = (dom, imgsrc, profileName, postedTime) => {
  const asideMain = document.querySelector(`${dom} .asideMain`)
  const imgAndId = document.createElement('li')
  const asideProfileImg = document.createElement('img')
  const idAndTime = document.createElement('div')
  const profileId = document.createElement('div')
  const postTime = document.createElement('div')

  imgAndId.classList.add('imgAndId')
  asideProfileImg.classList.add('asideProfileImg')
  idAndTime.classList.add('idAndTime')
  profileId.classList.add('profileId')
  postTime.classList.add('postTime')
  postTime.classList.add('grey')

  idAndTime.append(profileId)
  idAndTime.append(postTime)
  imgAndId.append(asideProfileImg)
  imgAndId.append(idAndTime)
  asideMain.append(imgAndId)

  profileId.textContent = profileName
  postTime.textContent = postedTime
  asideProfileImg.setAttribute('src', imgsrc)
}

users.forEach(user => {
  createProfileLists('.story', user.img, user.id, '20분 전')
})
users.forEach(user => {
  createProfileLists('.recommend', user.img, user.id, '000님이 팔로우합니다.')
})
  • forEach 메소드로 users 객체를 돌면서 createProfileLists함수를 실행시켜서 돔을 그려냈다.


2. 댓글 추가, 삭제 및 좋아요 기능

postCommentBtn.addEventListener('click', () => {
  clickCommentBtn()
})

postCommentInput.addEventListener('keyup', e => {
  postCommentInput.value.length > 0
    ? postCommentBtn.classList.add('blue')
    : postCommentBtn.classList.remove('blue')
  if (e.keyCode !== 13) {
    return
  } else {
    clickCommentBtn()
  }
})
  • 댓글을 입력하고 게시 버튼을 누르거나 엔터버튼을 누르면 clickCommentBtn 함수를 실행시켰다.
const clickCommentBtn = () => {
  if (postCommentInput.value.length == 0) {
    return
  }

  const commentWrap = document.createElement('div')
  const commenter = document.createElement('span')
  const comment = document.createElement('span')
  const commentDeleteBtn = document.createElement('button')
  const commentLikesBtn = document.createElement('button')

  commentWrap.classList.add('commentWrap')
  commenter.classList.add('commenter')
  comment.classList.add('comment')
  commentDeleteBtn.classList.add('commentBtn')
  commentDeleteBtn.classList.add('commentDeleteBtn')
  commentLikesBtn.classList.add('commentBtn')
  commentLikesBtn.classList.add('commentLikesBtn')

  commentWrap.append(commenter)
  commentWrap.append(comment)
  commentWrap.append(commentDeleteBtn)
  commentWrap.append(commentLikesBtn)
  articleComment.append(commentWrap)

  commentDeleteBtn.innerHTML = `<i class="far fa-trash-alt"></i>`
  commentLikesBtn.innerHTML = `<i class="emptyHeart far fa-heart"></i>
<i class="redHeart fas fa-heart red hide"></i>`
  commenter.textContent = 'Shaman_king'
  comment.textContent = postCommentInput.value

  postCommentInput.focus()
  commentTime.textContent = '방금'
  postCommentBtn.classList.remove('blue')
  postCommentInput.value = ''

  commentLikesBtn.addEventListener('click', e => {
    let emptyHeart = e.currentTarget.querySelector('.emptyHeart')
    let redHeart = e.currentTarget.querySelector('.redHeart')
    emptyHeart.classList.toggle('hide')
    redHeart.classList.toggle('hide')
  })

  const commentDeleteBtns = document.querySelectorAll('.commentDeleteBtn')
  commentDeleteBtns.forEach(commentDeleteBtn => {
    commentDeleteBtn.addEventListener('click', () => {
      commentDeleteBtn.parentNode.remove()
    })
  })
}
  • fontawsome에서 하트모양을 가져왔다. 속이 빈 하트와 속이 찬 하트를 가져와서 좋아요를 누를 때마다 토글이 되게 하였다.
  • 돔을 만들면서 이벤트리스너를 붙여주었다.

3. 게시글에 좋아요 누르기

pushLike.addEventListener('click', e => {
  e.preventDefault()
  if (pushLike.style.backgroundPosition.slice(0, 4) === '-156') {
    pushLike.style.backgroundPosition = `-130px -478px`
    likeNum.textContent = Number(likeNum.textContent) + 1
  } else {
    pushLike.style.backgroundPosition = `-156px -478px`
    likeNum.textContent = Number(likeNum.textContent) - 1
  }
})
  • 이 하트는 스프라이트 이미지로 구현했기 때문에 좋아요를 누를 때마다 backgroundPosition이 변경되도록 하였다.

4. nav에 아이디 검색 기능

const createSearchLists = searchInputValue => {
  if (searchPopup.childNodes.length > 0) {
    return
  }
  const searchedList = document.createElement('li')
  const profileImg = document.createElement('img')
  const idAndNickname = document.createElement('div')
  const userid = document.createElement('div')
  const nickname = document.createElement('div')

  userid.textContent = searchInputValue
  let userIdIndex = userIds.indexOf(searchInputValue)
  profileImg.setAttribute('src', users[userIdIndex].img)
  // ----------------------------------------
  // ex) iu의 위치를 받아서 그 위치의 이미지를 넣어줬다.
  // setAttribute를 처음 사용해보았다.
  // ----------------------------------------

  idAndNickname.append(userid)
  idAndNickname.append(nickname)
  searchedList.append(profileImg)
  searchedList.append(idAndNickname)
  searchPopup.append(searchedList)
}

navSearch.addEventListener('input', e => {
  if (e.target.value.length === 0) {
    searchPopup.innerHTML = ''
  } else {
    for (i = 0; i < userIds.length; i++) {
      if (userIds[i].startsWith(e.target.value)) {
        createSearchLists(userIds[i])
        break
      } else {
        searchPopup.innerHTML = ''
      }
    }
    // ------------------------------------------------
    // forEach 사용시 break 가 되지 않아서 for loop을 돌렸다.
    // ------------------------------------------------
  }
})

navSearch.addEventListener('blur', () => {
  searchPopup.innerHTML = ''
})
  • String.startsWith 메소드로 iu의 i만 쳐도 나오게끔 했다.

5. 후기

착착 진행되는 부분도 있었지만 막히는 부분들도 많았다. 그래도 로직 생각하고 구현하는게 재밌었다. 마지막에는 조금 조급해 지기도 했다. 취업하면 회사에 도움이 되는 개발자가 되어야 하는데 .. 내가 도움이 될 수 있을까 등등 생각이 들었다.
정확히 빠르게 꼼꼼히 버그없이 해보자 아자아자 !

로직은 다 구현이 된 상태니 아마 다음주 리액트로 구현하는 것은 금방 할 수 있지 않을까?!! (허튼 생각이 아니길!)

Written by@Jiyon Lee
뜨거운 코드를 가르며

GitHub