javascript | 여러 개의 버튼 클릭 시 style, img src 경로 변경

    반응형

    안녕하세요 경아입니다 🖐

    이번 포스팅은 매우매우매우매우매우x100000 부끄러운 포스팅입니다....

     

    저는 현재 회사에서 마크업을 위주로 하고있습니다

    현재 개발 중인 사이트에 들어갈 탭메뉴 느낌의 여러 개 버튼을 만들고,

    클릭될 때 img 태그의 src 경로가 바뀌어야 하는 동작을 JS로 작성해야 했습니다

    바껴야하는 이미지가 다 동일하지 않고 각각 선택된 버튼마다 이미지가 다르게 있기 때문에

    어떻게 작성해야 할지 감을 못 잡았습니다

    그래도 대충 배열이.. 늘어난다...? 뭐... 이 정도 생각은 했습니다.. (이 말도 웃김...)

    쨌뜬 버튼이 클릭될 때 img src 경로를 바꾸는 기능 구현하면서

    매우 많은 시행착오와 바보 같은 짓을 많이 했습니다

    결론은 자바스크립트 기초가 매우 부족하니 삽질을 한 것이고...

    결국 나는 인강으로 코드를 아무 생각 없이 따라 쓰는 복붙 개발자였던건가? 하는 자괴감이 들었습니다

    그렇게 안되기 위해 노력을 했음에도... 부족했나 봅니다

    그 시행착오 과정을 적나라하게 쓰려고 합니다

     

     


     

     

    1. svg 이미지 사용

    img 태그 안에 들어갈 이미지들을 svg로 작업했습니다

    아무래도 공기업에 납품(?) 하는 페이지이고 우리나라는 ie 점유율이 아직도 높으니..

    확대해서 보시는 분들이 꽤 많다고 하더라고요? 그래서 이미지 화질을 위해 svg를 사용했습니다

    _selected 가 붙은 파일들은 클릭되었을 때 사용될 이미지입니다

    사진처럼 이미지마다 각각 있습니다

     

     

    2. html 작성

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <body>
     
        <div class="tab">
     
            <div class="tab-btn">
                <span class="tab-btn-check"></span>
                <img class="tab-icon" src="svg/icon1.svg" alt="아이콘">
                <class="tab-txt">
                    숙식<br />
                    <span>관련 데이터 확인 ▶</span>
                </p>
            </div>
     
            ...
     
        </div>
     
    </body>
    cs

    html은 같은 구조가 11개 반복됩니다

    class name 변동 없이 11개 반복입니다. img 태그의 src만 다릅니다

     

     

     

    3. style CSS

    위 사진과 같은 모양으로 CSS를 작성했습니다

    이런 모양의 버튼들이 11개가 있습니다

    각각 들어가는 img가 다르죠?

    제가 구현하고 싶은 모양은 이렇습니다

     

    왼 - 버튼 클릭 전 / 오 - 버튼 클릭 후

    버튼 클릭 후 바뀌어야 할 점이 무엇일까요?

    1. 오른쪽 상단 동그라미 모양이 체크 이미지로 바뀜

    2. 아이콘 색깔 바뀜

    3. 글씨 색깔 바뀜

    4. box-shadow가 생김

    1, 3, 4 번은 간단하게 해결할 수 있습니다

    JS로 CSS를 동적으로 변경하면 끝이니까요

    근데 2번 같은 경우 HTML img 태그의 src를 바꿔야 합니다

    그것도 선택된 것에 따라 각각 다르게... 그게 어려웠습니다

    검색을 해봐도 저처럼 버튼 여러 개에 각각 다른 이미지가 있을 때의 경우는 없더라고요

    응용력이 없는 저는... 비슷한 예시를 보고도 해결 방법을 못 찾았습니다ㅠㅠㅠ

     

     

     

    4. Javascript

    1
    2
    3
    4
    var tab = document.querySelectorAll(".tab");
    var tabCheck = document.getElementById("tab-btn-check");
    var tabIcon = document.getElementById("tab-icon");
    var tabText = document.getElementById("tab-txt");
    cs

    맨 처음에는 이렇게 태그들을 불러왔습니다....... 바본가봐요...

    이렇게 불러와서는 대충 아래와 같이 작성했었습니다

    1
    2
    3
    4
    5
    6
    7
    function handleClick() {
        tabCheck.style.backgroundImage = "url(svg/check_btn.svg)";
        tabIcon.src = tab + "_selected.svg";
        tabText.style.color = "#4288ea";
    }
     
    tab.addEventListener("click", handleClick};
    cs

    ㅋㅎ....ㅋ..... 진짜 내가 쓰면서도 웃겼습니다

    7~8개월 공부해서 쓴 코드라니.... 진짜 자괴감 든다....

    쨌뜬 이렇게 작성하니

    TypeError: tab.addEventListener is not a function 이란 오류가 떴습니다

    querySelectorAll 을 제대로 이해하지 못했다는 이야기인 거죠..

     

    ✅ querySelectorAll 란?

    개발자 도구에서 console에 tab을 치면 NodeList들이 나옵니다

    거기에서 배열 느낌으로 제가 불러온 div 태그의 class Name이 tab-btn들이 잘 들어와 있군요

    근데 NodeList는 배열이 아닌 유사배열이라고 불립니다

    키가 숫자고 length라는 속성을 가지고 있습니다

    배열도 객체라는 성질을 이용한 트릭이라고 하네요!

    배열과 유사배열의 차이점은 유사배열의 경우 배열의 메서드를 쓸 수 없다고 합니다

    더 자세한건 매우 쉽게 설명 된 사이트 두 곳을 첨부합니다

    im-developer.tistory.com/110

     

    [JS/DOM] 자바스크립트, 돔 조작 시 주의할 점 (Live Collection vs Static Collection)

    1번 2번 3번 만약에 위 HTML코드에서 태그를 자바스크립트를 이용하여 동적으로 제어하기 위해 돔을 가져온다고 해보자. 여러 가지 방법이 있겠지만 가장 흔하게 사용되는 방법은 아래와 같다. con

    im-developer.tistory.com

    www.zerocho.com/category/JavaScript/post/5af6f9e707d77a001bb579d2

     

    (JavaScript) 배열과 유사배열

    안녕하세요. 이번 시간에는 배열과 유사배열에 대해서 살펴보겠습니다. 배열은 다들 아실겁니다. 그런데 유사배열은 잘 모르는 입문자분들이 많이 계십니다. 한 번 둘의 차이를 알아봅시다. var

    www.zerocho.com

     

    쨌뜬...! 클릭 대상이 불명확한 유사배열에 두리뭉실하게 addEventListener를 넣으니

    addEventListener 는 뭔데? 뭐야? 너가 뭘 클릭한 건데?????? 저 많은 .tab-btn중에 뭔데;;;??? 라는 상황이 된 것 같습니다..

    보통 forEach, for문을 가장 많이 쓰는 것 같습니다 둘 차이는 뭐가 있을까요...? (정리 내용하다 보니 궁금해짐🤷‍♀️ 그러나.. 이건 다음에 다루는 걸로.....)

    저는 for문을 썼습니다

     

     

    ✅ for문

    for문은 반복문으로 어떤 특정한 조건이 거짓으로 판별될 때까지 반복합니다

    for(초기문; 조건문; 증감문) {
            // ....
    }

    아래와 같이 코드를 수정해봤습니다

    1
    2
    3
    for (var i = 0; i < tab.length; i++) {
        tab[i].addEventListener("click", handleClick);
    }
    cs

    i의 초기값을 0으로 설정하고 tab.length은 11이니 i가 11보다 작을 때까지 증가하는 반복문입니다

    이렇게 작성을 해줬더니 click event가 작동합니다!!!!!만........

    이번엔 TypeError: Cannot read property 'style' of null 오류 발생....

    style이 안 먹히는 겁니다 대체 와이??????? 위에 getElementById로 다 담아온 거 아닌가??

    했더니 이것 또한 문제였다고 한다....

     

     

    ✅ getElementBy*

    1
    2
    3
    4
    var tab = document.querySelectorAll(".tab");
    var tabCheck = document.getElementById("tab-btn-check");
    var tabIcon = document.getElementById("tab-icon");
    var tabText = document.getElementById("tab-txt");
    cs

    이 코드의 문제점

    1. 말이 안 됨

    2. 말이 안 됨

    3. 말이 안 됨

    아~ 이 사람은~ getElementById에대해 전혀 이해를 못했다고 봐야겠죠...😂.........

    id 자체가 그 문서에서 유일한 존재로 같은 id가 여러 개 있으면 안 되죠 (이건 알고 있었었습니다...)

    그래서 무슨 값을 가져와야 할지 모르게 된 겁니다 

    (저는 그래도 해당된 id를 가진 엘리먼트를 찾아서 이벤트 대상을 클릭하면 그 대상의 속으로(?) 들어가서 인식할 줄 알았습니다...?)

    그다음으로 getElementsByClassName 사용해봤습니다...ㅋ

    Element의 메서드 getElementsByClassName()는 주어진 클래스를 가진 모든 자식 엘리먼트의 실시간 
    HTMLCollection을 반환합니다.
    출처:MDN

    오 그럼 되는 걸까?! 했지만... 같은 오류가 떴습니다

    (이건 아마 먼저 처리되었어야 할 이벤트 대상이 없어서 오류가 뜬 것 같았습니다)

     

    결국... 선배님의 도움을 받았습니다

    🧑 "대상도 없이 모든 태그, id, class를 담아버리니 어디의 어디의 어디인지도 모르는 거다

    일단 가독성이 떨어지더래도 이해하기 쉽게 (어디).(어디).(어디) 식으로 점을 찍어가며

    그 안의 그 안의 그 안의를 일일이 들어가서 대상을 짚어라!"

    라고 하시더라고요

    저는 NodeList, HTMLCollection  존재 여부 조차도 몰랐다고 보면 됩니다..

    child NodeList 확인을 했냐고 하더라고요... 

    이렇게 콘솔에서 tab을 치면 확인이 가능합니다....

    그래서 코드를 수정해봤습니다

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function handleClick() {
      tab.childNodes[5].style.color = "rgb(80, 80, 80)";
      tab.childNodes[3].src = tab.childNodes[3].src.replace(".svg", "_selected.svg");
      tab.childNodes[1].style.backgroundImage = "none";
      tab.childNodes[1].style.border = "1px solid #ccc";
      tab.style.boxShadow = "none";
    }
     
    for (var i = 0; i < tab.length; i++) {
     tab[i].addEventListener("click", handleClick);
    }
    cs

    일일히 tab의 childNodes의 몇 번째로 들어가서 style을 수정해주는 방식입니다

    img src는 replace라는 메서드를 사용해 파일명 문자열인 .svg를 _selected,svg로 바꾸겠다는 뜻입니다

    이렇게 해줬더니!!!!!!! 됐습니다!!!!!!!!!만........

    모든 div 태그에 동시 작동^^..................................

    분명 저는 하나만 클릭했을 뿐인데... 모든 div태그의 style이 바뀌는 사태...

    클릭했을 때 이벤트가 발생한 target을 못 가져오는 것 같습니다

    그래서 클릭한 요소를 가져오는 방법으로 currentTarget을 사용해봤습니다

     

     

     

    ✅ currentTarget

    이벤트가 바인딩된 요소를 반환 (target이랑은 차이가 있습니다)

    1
    2
    3
    4
    5
    6
    7
    8
    function handleClick(element) {
      var btnTarget = element.currentTarget;
      btnTarget.childNodes[5].style.color = "#4288ea";
      btnTarget.childNodes[3].src = btnTarget.childNodes[3].src.replace(".svg""_selected.svg");
      btnTarget.childNodes[1].style.backgroundImage = "url(/svg/check_btn.svg)";
      btnTarget.childNodes[1].style.border = "none";
      btnTarget.style.boxShadow = "0 0 30px -10px #4288ea";
    }
    cs

    이렇게 handleClick 함수를 수정해줬더니!

    드디어 이렇게 클릭 이벤트가 제대로 먹히기 시작했습니다!!!! 👏👏👏

     

     

     

    그 러 나

     

     

    이 탭메뉴는... 중복 선택 후 다시 재 클릭했을 때 전 style로 초기화되어야 합니다...😂

    그래서... 별 뻘짓을 다 해봤습니다...

    1
    2
    3
    4
    5
    6
    7
    for (var i = 0; i < tabBtns.length; i++) {
      if (click) {
        tab[i].addEventListener("click", handleClick);
      } else {
        tab[i].removeEventListener("click", handleClick);
      }
    }
    cs

    이렇게도 작성해보고.... (말이 안 됨 click이 뭔데...)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    if (!click) {
      for (var i = 0; i < tabBtns.length; i++) {
       tab[i].addEventListener("click", handleClick);
      }
    else {
      for (var i = 0; i < tabBtns.length; i++) {
       tab[i].addEventListener("click", handleResetClick);
      }
    }
     
    cs

    이렇게 아예 리셋되는 함수 만들어서 적용도 해보고.... (그래도 말이 안 됨)

    도대체 저 조건문 click이 뭔지를 지칭해주지 않았으니....

    그렇게 검색 후 알아낸 게 이 방식입니다

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    var tab = document.querySelectorAll(".tab-btn");
    var click = true;
     
    function handleClick(element) {
      var btnTarget = element.currentTarget;
      if (btnTarget.click) {
        btnTarget.childNodes[5].style.color = "#4288ea";
        btnTarget.childNodes[3].src = btnTarget.childNodes[3].src.replace(
          ".svg",
          "_selected.svg"
        );
        btnTarget.childNodes[1].style.backgroundImage = "url(/svg/check_btn.svg)";
        btnTarget.childNodes[1].style.border = "none";
        btnTarget.style.boxShadow = "0 0 30px -10px #4288ea";
      } else {
        btnTarget.childNodes[5].style.color = "rgb(80, 80, 80)";
        btnTarget.childNodes[3].src = btnTarget.childNodes[3].src.replace(
          "_selected.svg",
          ".svg"
        );
        btnTarget.childNodes[1].style.backgroundImage = "none";
        btnTarget.childNodes[1].style.border = "1px solid #ccc";
        btnTarget.style.boxShadow = "none";
      }
      //클릭했을때 : false / 선택해지됐을때 : true //원래 기본 상태 true
      btnTarget.click = !btnTarget.click;
      console.log(btnTarget.click);
    }
     
    for (var i = 0; i < tab.length; i++) {
      tab[i].addEventListener("click", handleClick);
    }
    cs

    그랬더니 드디어... 재 클릭 시 스타일이 리셋된다

    여기서 childNodes[] 이런 식으로 작성하니 가독성과 어떤 태그인지 파악이 어려워서 

    코드를 조금 더 보기 좋게 개선해봤습니다

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    var tab = document.querySelectorAll(".tab-btn");
    var click = true;
     
    function handleClick(element) {
      var btnTarget = element.currentTarget;
      var checkIcon = btnTarget.querySelector(".tab-btn-check");
      var tabText = btnTarget.querySelector(".tab-txt");
      var iconImg = btnTarget.querySelector(".tab-icon");
     
      if (btnTarget.click) {
        tabText.style.color = "#4288ea";
        iconImg.src = iconImg.src.replace(".svg""_selected.svg");
        checkIcon.style.backgroundImage = "url(svg/check_btn.svg)";
        checkIcon.style.border = "none";
        btnTarget.style.boxShadow = "0 0 30px -8px #4288ea";
      } else {
        tabText.style.color = "rgb(80, 80, 80)";
        iconImg.src = iconImg.src.replace("_selected.svg"".svg");
        checkIcon.style.backgroundImage = "none";
        checkIcon.style.border = "1px solid #ccc";
        btnTarget.style.boxShadow = "none";
      }
      btnTarget.click = !btnTarget.click;
    }
     
    for (var i = 0; i < tab.length; i++) {
      tab[i].addEventListener("click", handleClick);
    }
    cs

    클릭된 타겟의 childNodes를 따로 뺐습니다! 훨씬 더 보기 좋군요 😁

    분명 이거보다 더 좋은 방법이 있을 겁니다 😢

    제가 봐도 중복되는 게 많은 느낌이라... 그렇다고 뭘 어떻게 더 개선해야 할지 모르겠고....

     

     

     

     

    ✍ 마무리

    이 간단한 기능을 구현하면서 제가 얼마나 얼마나 얼마나x1000 무지한지를 깨달았습니다

    정말 제대로 알고 넘어가겠다고 해놓고 두리뭉실하게 넘겼던 것 같습니다

    (포인트를 정확히 짚어주시는 대리님.. 감사합니다... 정말 제대로 깨닫게 되었습니다)

    쪽팔리고 부끄럽고 수치스러운 코드인데... 이렇게 공개적으로 망신을 당해봐야...

    더 발전할 수 있을 것 같아서 기록하게 되었습니다

    몇 년 뒤에 이 게시물을 다시 보고서는

    "캬~ 내가 이딴 식으로 코드를 쓰는 날도 있었지~" 라며 가볍게 웃는 날이 오길 바랍니다...😂

     

    반응형

    댓글