[인스타 클론] Like Button 기능 만들기

unu·2021년 4월 24일
4

JavaScript

목록 보기
3/4

♡ ⇢ ♥︎

컨테이너에 아이템들을 채우고 이제 기능을 만들고 있다.
가장 처음 만들 기능은 좋아요 버튼을 누르면 ♡가 ♥︎로 변하는 기능이다.

어떻게 만들었는지와 만들면서 새롭게 알게된 부분인 복수의 엘리먼트에 이벤트리스너를 붙이는 법, classList 메서드에 대해 적어보려한다.

어떻게 만들었나

  1. querySelectorheartSvgheartPath를 선언한다.
  2. heartSvg이벤트리스너를 달고 클릭하면 changeClass 함수가 실행되게 한다.
  3. changeClass(heartSvg, heartPath) {
    if (heartSvg가 "like-default"라는 클래스를 갖고 있다면,) {클래스를 "like-fill"로 교체하고 d속성값을 ♥︎로 설정한다.}
    else {클래스를 "like-default"로 교체하고 d속성값을 ♡로 설정한다.})
HTML
  <button class="feed-icon-btn">
    <svg class="feed-icon like-default" viewBox="0 0 48 48">
        <path d="M34.6 6..."></path>
    </svg>
  </button>
JAVASCRIPT

//FEED HEART COLOR CHANGING
const heartSvg = document.querySelector('.feed-icon.like-default');
const heartPath = document.querySelector('.feed-icon.like-default path');

heartSvg.addEventListener('click', () => changeClass(heartSvg, heartPath));
function fillHeartRed(heartSvg, heartPath){
	if(heartSvg.classList.contains("like-default")){
		heartSvg.classList.remove("like-default");
		heartSvg.classList.add("like-fill");
		heartPath.setAttribute('d','M34.6 3....')
	}
	else{
	 	heartSvg.classList.remove("like-fill");
	 	heartSvg.classList.add("like-default");
	 	heartPath.setAttribute('d','M34.6 6....')
	 }
}

classList 프로퍼티와 메서드

클래스와 관련된 메서드를 찾을 때, 가장 처음 발견한 것은 className 이었다.

className

특정 엘리먼트의 클래스 속성의 값을 가져오거나 설정하는 프로퍼티다.

//설정하기
const div = document.createElement('div');
div.className = 'foo';
//가져오기
const foo = document.getElementById('foo').className;
console.log(foo);

classList

Element.classList는 엘리먼트의 클래스 속성의 컬렉션인 활성 DOMTokenList를 반환하는 읽기 전용 프로퍼티이다. 이것은 또한 클래스리스트를 조작하는 데에도 사용된다.
classList 사용은 공백으로 구분된 문자열인 element.className을 통해 엘리먼트의 클래스 목록에 접근하는 방식을 대체하는 간편한 방법이다.

const heartSvgClassList = document.querySelector('.feed-icon.like-default').classList;
console.log(heartSvgClassList);
//DOMTokenList(2) ["feed-icon", "like-default", value: "feed-icon like-default"]

Element.classList.add/ remove(String [, String [, ...]])

지정한 클래스 값을 추가하거나 제거한다.

//classList API 를 사용해 class들을 제거하거나 추가한다.
div.classList.remove("foo");
div.classList.add("anotherclass");

//복수의 class를 추가하거나 제거하라
div.classList.add("foo", "bar", "baz");
div.classList.remove("foo", "bar", "baz");

//spread syntax를 이용하여 복수의 class를 추가하거나 제거한다.
const cls = ["foo", "bar"];
div.classList.add(...cls);
div.classList.remove(...cls);

Element.classList.replace(old class, new class)

기존의 클래스를 새로운 클래스로 대체한다.

// replace class "foo" with class "bar"
div.classList.replace("foo", "bar");

Element.classList.toggle( String [, force] )

하나의 인수만 있을 때: 클래스 값을 토글링한다. 즉, 클래스가 존재한다면 제거하고 false를 반환하며, 존재하지 않으면 클래스를 추가하고 true를 반환한다.

//visible이 설정되어있다면 없애고, 아니라면 추가하라
div.classList.toggle("visible");

두번째 인수가 있을 때: 두번째 인수가 true로 평가되면 지정한 클래스 값을 추가하고 false로 평가되면 제거한다.

//'i가 10보다 작다'는 테스트 상태에 따라 visible을 추가/제거하라
div.classList.toggle("visible", i < 10 );
//i가 10 미만일 떼 visible이 추가되며 초과시에 제거된다.

Element.classList.contains(String)

지정한 클래스 값이 엘리먼트의 class 속성에 존재하는지 확인한다.

//<svg class="feed-icon like-default" viewBox="0 0 48 48">...</svg>
const heartSvg = document.querySelector('.feed-icon.like-default');
console.log(heartSvg.classList.contains("like-default"));
//true

💣 문제 발생!

처음에 정적인 페이지를 만든다고 피드를 하나만 만든 것이.. 동적으로 많이 만들게 되면서 문제가 되었다. querySelector으로 되던 것들이 querySelectorAll로는 되지 않았고, 잘만 작동되던 이벤트리스너가 좋아요 버튼이 여러개 생기면서 먹히지 않게되었다.

여러 엘리먼트에 이벤트리스너 붙이기

querySelectorAll로 얻은 복수의 엘리먼트에 EventListener를 어떻게 붙일까?

구글링 하다가 잘 정리된 블로그를 하나 발견했다. @Flavio Copes
이 블로그는 두 가지 방법을 소개한다.

1. Loop을 이용한 방법

loop를 이용한 방법이 가장 간단하다.
querySelectorAll()로 노드리스트를 불러내고 forEach()로 반복시키는 것이다.

document.querySelectorAll('.some-class').forEach(item => {
  item.addEventListener('click', event => {
    //handle click
  })
})
If you don’t have a common class for your elements you can build an array on the fly:

[document.querySelector('.a-class'), document.querySelector('.another-class')].forEach(item => {
  item.addEventListener('click', event => {
    //handle click
  })
})

2. event bubbling을 이용한 방법

다른 옵션은 이벤트 버블링을 사용해 이벤트 리스너를 body 엘리먼트에 붙이는 것이다.
이벤트는 항상 가장 정확한 엘리먼트에 의해 관리되므로, 여러 엘리먼트 중에서도 이벤트를 관리해야하는 역할을 가진 특정 엘리먼트인지 바로 체크가 가능하다.

const element1 = document.querySelector('.a-class')
const element2 = document.querySelector('.another-class')

body.addEventListener('click', event => {
  if (event.target !== element1 && event.target !== element2) {
    return
  }
  //handle click
}

3. 내가 사용한 for 문을 이용한 방법

결국 위 두 방법 다 안썼다..🙄
나는 heartSvg에 forEach를 붙인 다음 이벤트리스너도 붙이고 싶은데,
첫 번째 방법은 forEach의 대상과 eventlistener가 달라서 내 상황과 조금 안 맞았다.
두 번째 방법인 버블링은 body에 붙이는게 좀 오버같아서 그냥 for문으로 반복시켰다.
그리고 하는 김에 add, remove도 합쳐서 replace로 대체했다.

//FEED HEART COLOR CHANGING
const heartSvg = document.querySelectorAll('.feed-icon.like-default');
const heartPath = document.querySelectorAll('.feed-icon.like-default path');
for(let i = 0; i< heartSvg.length; i++) {
	heartSvg[i].addEventListener('click', function(e){
		if(heartSvg[i].classList.contains("like-default")){
		heartSvg[i].classList.replace("like-default", "like-fill");
		heartPath[i].setAttribute('d','M34.6 3...');
		}
		else{
		heartSvg[i].classList.replace("like-fill", "like-default");
		heartPath[i].setAttribute('d','M34.6 6...');
		})
};


잘 돌아가고 있는 것 같다 ^^

같은 방법으로 네비게이션 바에 있는 하트 모양의 활동피드 버튼도 기능을 붙여준다.

♡ ⇢ ♥︎

잘 보면 최초의 클릭에는 이벤트리스너가 반응하지 않는다.
HTML
<button class="nav-icon-btn like" type="button">
     <svg class="nav-icon like" fill="#262626" viewbox="0,0,48,48">
          <a href="">
             <path d="M34.6 6"></path>
          </a>
     </svg>
</button>
//NAV HEART COLOR CHANGING
const navIconBtnLike = document.querySelector('.nav-icon-btn.like');
const navHeartSvg = document.querySelector('.nav-icon.like');
const navHeartPath = document.querySelector('.nav-icon.like path')
const cList =  navHeartSvg.classList;
navIconBtnLike.addEventListener('click', (e) => {fillHeartBlack(cList,navHeartSvg,navHeartPath); preventRefresh(e);});
function fillHeartBlack(cList,navHeartSvg,navHeartPath){
	if(cList.contains("nav-icon") && cList.contains("like")){
		navHeartSvg.classList.remove("nav-icon", "like");
		navHeartSvg.classList.add("nav-icon", "like-fill");
		navHeartPath.setAttribute('d','M34.6 6....')
	}
	else{
	 	navHeartSvg.classList.remove("nav-icon", "like-fill");
	 	navHeartSvg.classList.add("nav-icon", "like");
	 	navHeartPath.setAttribute('d','M34.6 3....')
	 }
}
function preventRefresh(e){
	e.preventDefault()
}

💣 문제발생

처음에는 버튼 클릭할 때마다 페이지가 리로드되는 문제가 있었는데,preventRefresh 함수에 있는 e.preventDefault로 해결했다
다만, 아직도 최초의 클릭에는 반응하지 않는다는 문제가 있다.

마치면서

  • addEventListener에 콜백함수가 들어가야 하는데 또 일반 함수 실행을 넣어서 멘토에게 혼이 났다..
    Element.addEventListener(type, myFunction(param))myFunction(param)은 함수 실행 결과값을 전달해주는 것이기 때문에 차이가 있다.

  • querySelector/ All 을 자꾸 헷갈려한다. 자주 써보면 익숙해지겠지만..

  • 구글링 능력이 점점 올라가는 것 같다!

  • 마지막 문제는 멘토를 통해서라도 꼭 해결하자
profile
나 미대 나온 개발자야~

0개의 댓글