자바스크립트

자바스크립트>이벤트 버블링 , 응용과 dateset

포폴연습 2024. 7. 9. 00:27

모달창 배경을 누르면 닫히는 기능을 추가해봅시다

 

앞선 강의에서 모달창을 하나 제작해서 띄워봤습니다. 

근데 모달창의 검은 배경을 누르면 모달창이 닫히는 기능을 추가해봅시다. 

 

설명하다
 
<div class="black-bg">
  <div class="white-bg">
    모달창 내용
  </div>
</div>

▲ 모달창 HTML은 대충 이런 식으로 코드가 짜여있고

모달창 오픈할 때 show-modal 클래스명을 넣어서 오픈했다면 

 

 

 

document.querySelector('.black-bg').addEventListener('click', function(){
  document.querySelector('.black-bg').classList.remove('show-modal');
})

이렇게 코드짜면 될듯요 

그럼 검은 배경 눌렀을 때 모달창이 잘 닫힙니다. 

 

근데 이상한 점이 하나 있습니다.

검은배경 뿐만 아니라 흰배경, input, 글자 등 모달창 내부의 어떤걸 눌러도 다 닫힙니다. 

뭔가 이상한듯 

 

 

 

 

 

 

 

이벤트 버블링

 

어떤 HTML 태그에 이벤트가 발생하면 그의 모든 상위요소까지 이벤트가 실행되는 현상을 이벤트 버블링이라고 합니다. 

click이라는 이벤트로 예를 들어보면,

HTML 태그에 클릭이 발생하면 그의 모든 상위요소까지 자동으로 클릭된다는 말입니다. 

 

 

설명하다
 
<div>
  <div>
    <p>안녕</p>
  </div>
</div>

▲위의 코드에서 p태그 안녕이라는 글자를 클릭하면 브라우저는 사용자가 클릭을 총 3번 했다고 인지합니다.

p랑 그 위의 div랑 그 위의 div랑 이렇게요.

이게 이벤트 버블링인데 브라우저는 원래 그렇게 동작하도록 되어있습니다.

이 사실을 모르고 코드짜다보면 가끔 이상한 현상이 발생할 수도 있습니다. 

 

 

 

 

그래서 아까 "검은배경 누르면 모달창 닫아주세요~" 코드를 다시 살펴보면 

왜 흰배경 눌러도 모달창이 닫히냐면 

 

설명하다
 
<div class="black-bg"> (← 이거 누르면 모달창 닫으라고 코드짰음)
  <div class="white-bg">
    모달창 내용
  </div>
</div>

유저가 <div class="white-bg"> 이거 클릭해도

이벤트 버블링 때문에 <div class="black-bg"> 이것도 클릭한 것입니다.

그래서 거기 붙어있던 이벤트리스너가 동작해서 모달창을 닫아주는겁니다. 

 

이 문제를 해결할 때 자주 사용하는

이벤트관련 함수/메소드들을 살펴보도록 합시다. 

 

 

 

 

 

 

이벤트리스너 안에서 쓰는 이벤트 함수들 

 

설명하다
 
document.querySelector('.black-bg').addEventListener('click', function(e){
  e.target;
  e.currentTarget;
  e.preventDefault();
  e.stopPropagation();
})

이벤트리스너의 콜백함수에 파라미터 아무거나 추가하면 

이벤트관련 유용한 함수들을 사용가능합니다. 

파라미터 이름은 아무렇게나 작명하면 됩니다. 보통 대충 e라고 함

 

 

e.target은 실제 클릭한 요소 알려줌 (이벤트 발생한 곳)

e.currentTarget은 지금 이벤트리스너가 달린 곳 알려줌 (참고로 this라고 써도 똑같음)

e.preventDefault() 실행하면 이벤트 기본 동작을 막아줌

e.stopPropagation() 실행하면 내 상위요소로의 이벤트 버블링을 중단해줌

몇개만 뽑아봤는데 필요할 때 가져다가 쓰면 됩니다. 

e.target 이런거 출력해보십쇼 진짜 그게 맞나 

 

 

여기서 중요한건 e.target인데 

이벤트 버블링이 일어난다고 해도

사용자가 실제로 클릭한 그 요소는 저 문법으로 찾아낼 수 있다는걸 기억해둡시다. 

 

 

 

 

 

 

그럼 모달창 닫기 버그를 해결해봅시다

 

코드를 대충 이렇게 수정하면 되지 않을까요? 

 

document.querySelector('.black-bg').addEventListener('click', function(e){
   지금 실제로 클릭한게 검은 배경일 때만 닫아라
})

한글먼저 짜놓으면 쉽습니다. 이제 자바스크립트로 번역만 잘하면 됩니다. 

 

Q. 아니 한글짜는게 쉽다는건 알겠는데 저 논리를 한글로 어떻게 생각해냄? 그냥 답을 미리 아니까 그런거아님?

-> 이건 e.target같은 개념을 알고 있으면 누구나 생각가능한것입니다 저도 답 모름

 

 

 

코드는 알아서 짜봅시다

 

 

설명하다
 
document.querySelector('.black-bg').addEventListener('click', function(e){
  if (지금 실제로 클릭한거 == 검은 배경) {
    document.querySelector('.black-bg').classList.remove('show-modal');
  }
})

이 경우에 이거 해주세요~ 라고 코드짜려면 if문 밖에 없습니다.

그리고 조건식안에 들어가는건 거의 등호/부등호 이런거래서 등호로 바꿔봤습니다. 

 

지금 실제로 클릭한건 자바스크립트로 뭐죠?

e.target일듯 

 

검은배경은 자바스크립트로 뭐죠?

document.querySelector('.black-bg') 일듯

 

 

 

설명하다
 
document.querySelector('.black-bg').addEventListener('click', function(e){
  if ( e.target == document.querySelector('.black-bg') ) {
    document.querySelector('.black-bg').classList.remove('show-modal');
  }
})

이러면 될 것 같은데

코드만 따라치지말고 언제나 의심을 해야합니다.

진짜 저거 두개를 등호로 == 비교해도 됩니까

둘 다 콘솔창에 출력해보고 == 비교해보는 버릇을 가지도록 합시다. 

 

 

 

(참고)

저기서 e.currentTarget 출력해보면 검은배경이 나오기 때문에 

e.target == e.currentTarget 이렇게 써도 될듯요

아니면 e.target == this 이렇게 써도 될듯요 

 

 

 

(참고2)

jQuery 셀렉터로 찾은 결과와

querySelector 셀렉터로 찾은 결과는 다르게 생겼습니다.

출력해보면 전자는 이상한 object 이런게 나오고 후자는 <html> 이런게 나옵니다. 

그래서 e.target == $('.black-bg') 이건 사용불가능합니다.

그리고 애초에 jQuery 셀렉터끼리 등호비교는 불가능합니다. 

 

$('.black-bg').is($('.black-bg')) 이런 비교용 함수쓰든가 하면 됩니다.

위 예제에선 $(e.target).is($('.black-bg')) 이러면 됩니다. 

 

 

 

 

 

이벤트버블링을 알고계시면 이벤트리스너를 줄여가며 개발할 수도 있습니다. 

필수나 그런건 아니고 알면 언젠가 도움되는 내용이기 때문에

전에 만들었던 탭기능을 다시 만들어봅시다. 원래 복습을 해야 실력이 느는 것임 

 

전에 만들어둔 탭기능 코드

 

설명하다
 
for (let i = 0; i < $('.tab-button').length; i++){

  $('.tab-button').eq(i).on('click', function(){
    $('.tab-button').removeClass('orange');
    $('.tab-button').eq(i).addClass('orange');
    $('.tab-content').removeClass('show');
    $('.tab-content').eq(i).addClass('show');
  })

});

 

[collapse]

 

 

 

전에 만들었던 탭기능 함수로 축약해보기

 

함수쓰면 긴 코드를 짧은 단어하나로 축약할 수 있다고 했습니다.

그래서 탭만들 때 썼던 4줄의 코드를 함수로 축약해볼 것입니다.

별 이유는 없고 깔끔해보일 것 같으니까요 

실은 함수로 넣으면 1. 재사용이 편리하고 2. 나중에 남이 읽었을 때도 뭔가 이해가 쉬울 수 있습니다. 

 

 

설명하다
 
for (let i = 0; i < $('.tab-button').length; i++){

  $('.tab-button').eq(i).on('click', function(){
    탭열기(i)
  })

});


function 탭열기(구멍){
  $('.tab-button').removeClass('orange');
  $('.tab-button').eq(구멍).addClass('orange');
  $('.tab-content').removeClass('show');
  $('.tab-content').eq(구멍).addClass('show');
}

이러면 될듯요 

 

Q. 왜 구멍뚫음?

A. 함수로 코드를 싸맬 때 안에 변수가 들어있으면 변수를 전부 파라미터로 바꾸는게 좋습니다.

그래야 잘 동작합니다. 그래서 i 부분을 전부 파라미터로 바꿨습니다.

이제 함수 사용할 때

탭열기(0) 이러면 0번 탭이 열림

탭열기(1) 이러면 1번 탭이 열림

탭열기(2) 이러면 2번 탭이 열림

 

 

 

 

 

이벤트버블링을 알면 이벤트리스너 줄일 수 있음

 

지금 탭을 만들 때 이벤트리스너를 3개나 부착했습니다. (버튼이 3개니까요)

근데 잘 생각해보면 이벤트리스너 1개만 써도 충분히 기능구현이 가능합니다.

 

버튼 3개의 부모인 <ul class="list"> 여기에 이벤트리스너 1개만 있어도 탭기능만들 수 있을듯요 

버튼 뭘 누르든 간에 <ul class="list">에 붙은 이벤트리스너도 동작하니까요. 

왜냐면 이벤트버블링이 항상 일어나기 때문입니다. 

 

설명하다
 
$('.list').click(function(){
  지금 누른게 버튼 0이면 탭열기(0) 실행
  지금 누른게 버튼 1이면 탭열기(1) 실행
  지금 누른게 버튼 2이면 탭열기(2) 실행
})

그래서 이렇게 탭기능 만들어도 잘 동작한다는 소리입니다.

(jQuery 셀렉터엔 .click() 이라고 써도 간단하게 click 이벤트리스너 부착가능)

 

 

 

Q. 왜 굳이 이벤트리스너 줄여서 코드 짜냐고요? 

- 버튼이 몇십개 있다면 이렇게 짜는게 덜 복잡하고

- 이벤트리스너를 줄이면 램용량을 절약할 수 있습니다. 성능개선의 일환입니다. 

그래서 위처럼 코드짜서 탭기능 완성해보도록 합시다. 

 

탭기능 완성해보고 눌러봅시다

 

설명하다
 
$('.list').click(function(e){
  if (e.target == document.querySelectorAll('.tab-button')[0] ){
    탭열기(0)
  }
  if (e.target == document.querySelectorAll('.tab-button')[1] ){
    탭열기(1)
  }
  if (e.target == document.querySelectorAll('.tab-button')[2] ){
    탭열기(2)
  }
})

function 탭열기(){
  생략
}

이러면 탭기능 잘됩니다 

근데 이러나 저러나 코드 양은 똑같군요

dataset 문법을 알면 위 코드를 조금 더 짧게 바꿀 수도 있습니다.

 

 

[collapse]

 

 

 

 

 

 

 

dataset 문법 (잡기술)

 

<div data-데이터이름="값"></div> 

html 안에 유저 몰래 정보를 숨겨놓을 수 있습니다. 

데이터이름 아무렇게나 작명하고 값도 넣으면 됩니다.

일반 유저들은 모름 

 

 

document.querySelector().dataset.데이터이름;

이러면 html 요소에 숨겨놨던 데이터가 이 자리에 남습니다.

출력해보면 진짜로 아까 숨겨놓은 값이 남습니다.

 

 

이런 잡기술을 알고있으면 위에서 만들었던 코드를 조금 더 짧게 축약가능합니다.

 

 

<li class="tab-button" data-id="0">Products</li> 
<li class="tab-button orange" data-id="1">Information</li> 
<li class="tab-button" data-id="2">Shipping</li> 

▲ 우선 탭의 버튼들에 이렇게 데이터를 숨겨봅시다. 따라하셈  

그리고 아까 코드를 다시 살펴보면 if문이 3개였습니다. 

버튼0 누르면 탭열기(0) 실행해주세요~

버튼1 누르면 탭열기(1) 실행해주세요~

버튼2 누르면 탭열기(2) 실행해주세요~

 

 

 

$('.list').click(function(){
  탭열기(지금누른버튼에 숨어있던 data-id)
});

▲ 근데 이렇게 코드짜면 굳이 if문이 필요없이 한 줄로 해결아닙니까

맞습니다.

지금누른버튼에 숨어있던 data-id를 알려주는 코드같은게 있습니까 

있습니다. 방금 배운듯 

 

 

 

 

$('.list').click(function(e){
  탭열기(e.target.dataset.id)
});

▲ 지금누른 버튼을 찾고 싶으면 e.target이고 

거기 숨어있는 data-id 꺼내고 싶으면 .dataset.id 붙이면 됩니다.

그래서 위처럼 코드짜도 탭기능 완성인 것입니다.

이전 코드가 좋으면 그거 쓰면 되고 취향것 쓰시길 바랍니다.

 

 

 

dataset 문법이 인터넷익스플로러 11+ 에서 동작하는데 그게 마음에 안들면 

옛 브라우저까지 호환 잘되는 jQuery 함수 이용하는 방법도 있습니다.

$(셀렉터).data('데이터이름', '값') 이렇게 저장하고

$(셀렉터).data('데이터이름') 이렇게 출력합니다. 

 

 

 

 

 

오늘의 교훈 :

1. 함수로 축약할 때 변수같은게 있으면 파라미터로 바꾸는게 좋음 

2. 이벤트리스너 줄이면 이점이 있음

3. dataset 잡기술 알면 이벤트리스너 적게 사용할 때 내가 뭐 눌렀는지 쉽게 파악할 수도 있음