🤹🏻‍♀️ Javascript

클릭하면 버튼 색깔 바꾸기

ji-hyun 2020. 5. 3. 07:30

javascript를 이용해서 '클릭하면 해당 버튼의 색깔을 바꾸는 실습'을 해볼 겁니다.

학습의 차원에서 해당 실습에 대한 여러 가지 방법을 나열하려고 합니다. 

아래의 사진은 메뉴 버튼을 클릭했을 때 버튼의 색깔이 바뀌어지는 결과를 캡쳐해둔 것입니다.

 

 

 

실습 결과(menu1 클릭 화면)

 

 

 

 

 

방법 1)

(스크립트를 중점적으로 보세요)

 

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>자바스크립트 기초</title>
        <style>
            body { font-size: 3rem;}
            h1 { background-color: #fff000;}
            .menu {
                display: flex;   
                border: 1px solid yellow;
            }
            .menu-link {
                margin: .1em;
                padding: .3em;
                color: #fff;
                text-decoration: none;
                background: dodgerblue;
                list-style: none;
            }
            .menu-active {
                background: orange;
            }
            .menu {
                background-color: yellowgreen;
            }
        </style>
    </head>
<body>
    <h1 id="main-title">Event</h1>

    <nav class="menu">
        <a href="#" class="menu-link" data-menu="1">menu 1</a>
        <a href="#" class="menu-link" data-menu="2">menu 2</a>
        <a href="#" class="menu-link" data-menu="3">menu 3</a>
    </nav>

    <script>
/* 메뉴 클릭 때 색깔 바꾸기 - 첫번째 방법(약간 비효율적) */
var menuLinks = document.querySelectorAll('.menu-link');

function clickMenuHandler() {   /* onclickMenu와 같은 on____ , _____listener, _____handler들은 이벤트 리스너들이라고 할 수 있고, 보통 이런 이름들을 가짐 */
     for (var i = 0; i < menuLinks.length; i++){
         menuLinks[i].classList.remove('menu-active');
     }
     this.classList.add('menu-active');
 }

 for (var i = 0; i < menuLinks.length; i++){
     menuLinks[i].addEventListener('click', clickMenuHandler);
 }

 

 

코드 해석

맨 아랫줄 for문을 보면 menuLink를 통해서 각 메뉴 변수에게 이벤트(click)가 실행되면 함수를 추가하는 모습을 볼 수 있습니다. clickMenuHandler함수는 버튼의 색깔을 오렌지 색으로 바꾸는 class인 'menu-active'를 추가해주어야 하는데요. 여기서 this라는 객체를 통해서 누른 버튼에다가 menu-active를 추가해주어야겠지요.

하지만 this.classList.add('menu-active')를 써주었다고 해서 완성되는건 아닙니다.

 

menu 1 버튼을 누르고 menu 2 버튼을 이어서 클릭했을 때 이런 화면을 볼 수 있습니다.

 

 

 

 

아까 눌렀던 menu 1의 menu-active가 계속 남아있는 것을 확인할 수 있습니다. 그렇다면 어떻게 해야 클릭한 해당 버튼 색깔을 오렌지 색으로 바꿀 수 있을 까요? 정답은 없습니다. 아주 많은 다양한 방법들이 있을 것입니다.

 

먼저 방법 1)은 '이벤트 함수가 실행될 때, 해당 class를 일일히 모두 없애버리기' 방법을 생각해보았습니다. 먼저 클릭과 동시에 이벤트 함수에서는 for문을 이용해서 모든 메뉴의 menu-active의 classList를 지우고 시작하는 방법이 있겠죠? 그 다음 클릭한 메뉴에만 클래스를 다시 추가해주는 겁니다.

 

하지만 이 방법은 비효율적이라고 볼 수 있습니다. 쓸데없는 for문의 반복이 많기 때문입니다.

만약 버튼이 10000000개라면 for문으로 오렌지 색깔이 적용된 클래스를 일일이 찾아서 삭제하는 것이 얼마나 많은 시간을 소비되게 할까요?

 

 

 

 

 

 

방법 2)

 

/* 두번째 방법(괜찮고 많이 쓰기는 하지만 역시나 단점이 존재) */
var menuLinks = document.querySelectorAll('.menu-link');

function clickMenuHandler(){
    var activeMenu = document.querySelector('.menu-active');  /* querySelector 자체가 dom을 다 뒤져야 되므로 이것도 비효율적이라고 할 수 ㅇ..dom에 의존적 */
    if (activeMenu){
        activeMenu.classList.remove('menu-active');
    }
    this.classList.add('menu-active');
}


for (var i = 0; i < menuLinks.length; i++){
    menuLinks[i].addEventListener('click', clickMenuHandler);
}

 

 

 

코드 해석

앞서 방법 1)에서는 for문의 사용으로 비효율적이었다는 것을 알았죠? 그럼 이런 생각을 해볼 수 있습니다.

이벤트 함수가 실행될 때, menu-active가 적용된 class를 찾아 없애버리기

먼저 clickMenuHandler함수가 실행될 때, querySelector속성을 이용하여 ''menu-active'의 클래스를 찾아야 합니다. 그 다음 찾은 menu-active의 클래스를 지워주면 되겠죠. 이때 if문을 사용할 것을 주의하시기 바랍니다.

if문을 사용하지 않고 그냥 activeMenu.classList.remove('menu-active'); 한다면 에러가 납니다.

에러의 내용은 Uncaught TypeError: Cannot read property 'classList' of null
    at HTMLAnchorElement.clickMenuHandler 과 같습니다. 처음 실행했을 때(버튼 클릭이 아직 되지 않은 초기 상태) activeMenu의 값은 null로 존재합니다. null 값인데 menu-active classList를 remove할 수 없겠죠?

따라서 if문을 사용하여 이 값이 존재하는지 존재하지 않은지 먼저 확인하는 작업이 필요하답니다.

 

그러나 이 방법도 역시 아주 좋다고 할 수 없습니다. querySelector자체가 dom을 다 뒤져야 하기 때문이죠.

 

 

 

 

 

 

 

방법 3-1)

 

/* 현재 활성화된 메뉴를 담을 변수(dom에 의존적이지 않다. 범용적.) */

var currentMenu;
var menuLinks = document.querySelectorAll('.menu-link');

function clickMenuHandler(){
    currentMenu.classList.remove('menu-active');     /* 에러: currentMenu를 위에서 정의하긴 했지만 값이 안들어가 있다: 에러----> if문으로 check 필요. */
    this.classList.add('menu-active');
    currentMenu = this;
}


for (var i = 0; i < menuLinks.length; i++){
    menuLinks[i].addEventListener('click', clickMenuHandler);
}

 

 

코드 해석

앞서 방법 2)에서는 querySelector 자체가 dom을 다 뒤져야 하기 때문에 단점이 여전히 존재함을 확인하였습니다. 방법 3)은 dom에 의존적이지 않은 방법에 대해서 살펴보겠습니다. 이때는 '현재 활성화된 메뉴를 담을 변수'를 이용해보겠습니다. 변수 이름은 'currentMenu'라고 정의해줍니다. 

일단 시작할 때 currentMenu의 값은 아무것도 들어 있지 않으므로 if문을 이용해 check가 필요하겠죠?

 

 

 

 

 

 

방법 3-2)

 

var currentMenu;
var menuLinks = document.querySelectorAll('.menu-link');

function clickMenuHandler(){
    if (currentMenu){
        currentMenu.classList.remove('menu-active');  
    }   
    this.classList.add('menu-active');
    currentMenu = this;
}


for (var i = 0; i < menuLinks.length; i++){    /* 단점: 메뉴가 백만개라면....? 또한 addEventListener는 시스템 성능에 악영향을 끼친다고 함 */
    menuLinks[i].addEventListener('click', clickMenuHandler);        
}

 

 

코드 해석

방법 2)와 같이 먼저 if문으로 변수 check를 하면 시작할 때 아무런 에러 없이 작동되는 것을 확인할 수 있습니다. 

그 다음 this를 통해 클릭한 객체에다가 'menu-active'인 클래스를 추가하도록 코드를 작성하시구요. 이 this는 다시 '현재 활성화된 변수인 currentMenu'에 담아주도록 합니다. 

현재 클릭하고 작동되는 클래스가 이 변수 안에 담아 있다는 것을 알 수 있겠죠? 그렇다면 이 방법은 querySelector로 dom을 뒤지지 않아도 변수에 담아있음을 활용하여 더 빠르고 효율적으로 실행할 수 있습니다.

 

하지만 마지막 줄에 for문이 조금 걸리는군요. 방법 1)에서와 마찬가지로 만약 메뉴가 백만 개라면 일일히 다 돌려서 이벤트가 실행되면 이벤트 함수를 걸어줘야 하는 단점이 있었습니다. 이제 마지막으로 이 방법을 어떻게 개선하면 좋을지 다음 포스트에서 살펴보겠습니다.