https://heeccup.tistory.com/72
이전 포스팅에서 모달창을 하나 만들어서 띄웠었다.
오늘은 모달창의 검은 배경을 누르면 모달창이 닫히는 기능을 추가해 보자.
<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');
});
이렇게 코드를 짜면 검은 배경을 눌렀을 때 모달창이 닫힌다.
그런데, 검은 배경뿐 아니라 흰배경, 글자 등 모달창의 내부의 어떤 걸 눌러도 다 모달창이 닫힌다.
이벤트 버블링
- 어떤 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">도 클릭한 것이 된다. 그래서 거기에 붙어 있던 이벤트 리스너가 동작해서 하얀 배경을 눌러도 모달창이 닫아지는 것이다.
이런 문제를 해결할 때 자주 사용하는 이벤트 관련 함수/ 메소드들을 살펴보도록 하자.
이벤트 리스너 안에서 쓰는 이벤트 함수들
이벤트 콜백 함수에 파라미터 아무거나 추가하면 이벤트 관련 유용한 함수들을 사용할 수 있다. 파라미터 이름은 자유롭게 작명할 수 있는데, 보통 e라고 한다.
document.querySelector('.black-bg').addEventListener('click', function(e){
e.target;
e.currentTarget;
e.preventDefault();
e.stopPropagation();
})
- e.target : 실제 클릭한 요소 알려 줌(이벤트 발생한 곳)
- e.currentTarget : 지금 이벤트 리스너가 달린 곳 알려 줌 (this라고 써도 똑같음)
- e.preventDefault() : 실행하면 이벤트 기본 동작을 막아 줌
- e.stopPropagation() : 실행하면 내 상위 요소로의 이벤트 버블링을 중단해 줌
e.target을 이용하면 이벤트 버블링이 일어난다고 해도 사용자가 실제로 클릭한 그 요소를 찾아낼 수 있다.
그렇다면 모달창 닫기 버그를 해결해 보자.
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을 출력해 보면 .black-bg가 나오기 때문에 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')) 이렇게 사용하면 된다.
이벤트 버블링을 알고 있으면 이벤트 리스너를 줄여서 개발할 수도 있다.
https://heeccup.tistory.com/96
이전 포스팅에서 만들었던 탭 기능을 다시 보자.
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');
})
});
▲ 탭 기능을 for 반복문을 사용해 만들었다.
함수를 쓰면 긴 코드를 짧은 단어로 축약할 수 있다고 했다. 그래서 위의 코드를 함수로 축약해 보겠다.
for (let i = 0; i < $('.tab-button').length; i++){
$('.tab-button').eq(i).on('click', function(){
tabOpen(i)
})
});
function tabOpen(a){
$('.tab-button').removeClass('orange');
$('.tab-button').eq(a).addClass('orange');
$('.tab-content').removeClass('show');
$('.tab-content').eq(a).addClass('show');
}
▲ 함수 내에 파라미터를 넣은 이유는?
함수로 코드를 축약할 때는 안에 변수가 들어 있으면 변수를 전부 파라미터로 바꾸는 게 좋다. 그래야 잘 동작한다.
위의 코드에서 i 부분을 전부 파라미터 a로 바꿨다. 이제 함수 사용할 때 tabOpen(0) → 0번 탭 열림, tabOpen(1) → 1번 탭 열림 tabOpen(2) → 2번 탭 열림이 된다.
이벤트 버블링을 알면 이벤트 리스너를 줄일 수 있다.
위에서 탭을 만들 때 이벤트 리스너를 3개 부착했다. (버튼이 3개이기 때문)
그런데 이벤트 리스너 1개만 써도 충분히 기능 구현이 가능하다.
버튼 3개의 부모인 <ul class="list">에 이벤트 리스너 1개만 있어도 탭 기능을 만들 수 있다. 이벤트 버블링이 일어나기 때문에 어떤 버튼을 눌러도 <ul class="list">에 붙은 이벤트 리스너도 동작하기 때문이다.
$('.list').click(function(){
지금 누른게 버튼 0이면 tabOpen(0) 실행
지금 누른게 버튼 1이면 tabOpen(1) 실행
지금 누른게 버튼 2이면 tabOpen(2) 실행
})
▲ 이렇게 탭 기능을 만들어도 잘 동작한다는 말이다.
(jQuery 셀렉터에는 .click()이라고 써도 간단하게 click 이벤트 리스너 부착이 가능하다.)
Q. 굳이 이벤트 리스너를 줄여서 코드를 짜는 이유는?
- 버튼이 수십 개 있다면 코드를 이렇게 짜는 게 덜 복잡하고, 이벤트 리스너를 줄이면 램 용량을 절약할 수 있다. 성능 개선의 일환이다.
$('.list').click(function(e){
if (e.target == document.querySelectorAll('.tab-button')[0]){
tabOpen(0);
}
if (e.target == document.querySelectorAll('.tab-button')[1]){
tabOpen(1);
}
if (e.target == document.querySelectorAll('.tab-button')[2]){
tabOpen(2);
}
});
function tabOpen(i){
$('.tab-button').removeClass('orange');
$('.tab-button').eq(i).addClass('orange');
$('.tab-content').removeClass('show');
$('.tab-content').eq(i).addClass('show');
}
▲ 그래서 이렇게 코드를 작성해도 탭 기능은 잘 동작한다.
그런데 코드 양은 똑같다. dataset 문법을 알면 위 코드를 조금 더 짧게 바꿀 수도 있다.
dataset
<div data-데이터이름="값"></div>
html 안에 사용자 몰래 정보를 숨겨 놓을 수 있다. 데이터 이름을 자유롭게 작명하고 값을 넣으면 된다.
document.querySelector().dataset.데이터이름;
이렇게 하면 html 요소에 숨겨 놨던 데이터가 이 자리에 남는다. 출력해 보면 숨겨 놓은 값이 남는다.
그래서 dataset 문법을 알고 있으면 위에서 만들었던 코드를 더 짧게 축약할 수 있다.
<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 누르면 tabOpen(0) 실행, 버튼 1 누르면 tabOpen(1) 실행, 버튼 2 누르면 tabOpen(2) 실행
$('.list').click(function(){
tabOpen(지금 누른 버튼에 숨어 있던 data-id);
});
▲ 이렇게 코드를 짜면 if문 없이 한 줄로 해결할 수 있다.
지금 누른 버튼에 숨어 있던 data-id는 어떻게 알 수 있을까?
$('.list').click(function(){
tabOpen(지금 누른 버튼에 숨어 있던 data-id);
});
▲ 지금 누른 버튼을 찾고 싶으면 e.target이고, 거기에 숨어 있는 data-id를 꺼내고 싶으면 .dataset.id를 붙이면 된다.
그래서 이렇게 코드를 짜도 탭 기능은 잘 동작한다.
dataset 문법이 인터넷 익스플로러 11+에서 동작하는데, 옛 브라우저까지 호환 잘 되는 jQuery 함수 이용하는 방법도 있다.
$(셀렉터).data('데이터이름'. '값') 이렇게 저장하고, $(셀렉터).data('데이터이름') 이렇게 출력한다.
* 이 포스팅은 코딩애플 강의를 토대로 작성하였습니다.