본문 바로가기
Frontend/JavaScript

문서 객체 모델 ② 이벤트 활용

by minhi 2024. 10. 26.

문서 객체 모델 ① 문서 객체 조작하기에서 addEventListener(), removeEventListener() 메소드를 사용해

이벤트를 연결하고 제거하는 방법에 대해 알아보았다.

 

이번 글에서는 이벤트 관련하여 더 자세히 알아보자.

 

이벤트 모델
이벤트 연결 방법


이벤트를 연결하는 방법인 이벤트 모델로는 표준 이벤트 모델, 고전 이벤트 모델, 인라인 이벤트 모델이 있다.

body 문서 객체에 keyup 이벤트를 연결하는 상황을 예로 들어보자.

표준 이벤트 모델

document.body.addEventListener('keyup', (event) => {})

 

이전 글에서 배운 addEventListener() 메소드를 사용해 이벤트를 연결하는 것을 표준 이벤트 모델이라고 한다.

고전 이벤트 모델

document.body.onkeyup = (event) => {}

 

문서 객체가 가지고 있는 on으로 시작하는 속성에 함수를 할당해 이벤트를 연결하는 것을 고전 이벤트 모델이라고 한다.

인라인 이벤트 모델

<script>
  const listener = (event) => {}
</script>
<body onkeyup="listener(event)">
</body>

 

인라인 이벤트 모델은 문서 객체가 가지고 있는 on으로 시작하는 속성에 함수를 할당해 이벤트를 연결하지만

함수 정의와 이벤트 연결 모두 <script> 태그에서 이루어지는 고전 이벤트 모델과 달리

함수 정의는 <script> 태그에서, 이벤트 연결은 HTML 요소에서 이루어진다.

오늘날에는 표준 이벤트 모델과 인라인 이벤트 모델이 많이 사용된다.

 

이벤트가 발생할 때 실행되는 함수를 이벤트 리스너, 이벤트 핸들러라고 하는데

위의 모든 이벤트 모델의 이벤트 리스너는 첫 번째 매개변수로 이벤트 객체 event를 받는다.

이벤트 객체에는 이벤트와 관련된 정보들이 들어있다.

이벤트 발생 객체


이벤트를 연결할 때는 어떤 이벤트가 발생했을 때 어떤 이벤트 리스너를 실행시킬 것인가에 대한 정의가 필요하다.

이때 이벤트 리스너의 위치는 크게 다음의 두 가지로 구분할 수 있다.

<script>
  document.addEventListener('DOMContentLoaded', () => {
    const textarea = document.querySelector('textarea')
    const h1 = document.querySelector('h1')

    const listener = (event) => {
      const length = textarea.value.length
      h1.textContent = `글자 수: ${length}`
    }

    textarea.addEventListener('keyup', listener)
  })
</script>
<script>
  const listener = (event) => {
    const length = textarea.value.length
    h1.textContent = `글자 수: ${length}`
  }

  document.addEventListener('DOMContentLoaded', () => {
    const textarea = document.querySelector('textarea')
    const h1 = document.querySelector('h1')

    textarea.addEventListener('keyup', listener)
  })
</script>


겉으로 보기에는 두 코드 모두 아무 문제 없어 보이지만,

두번째 코드는 이벤트 리스너 블록과 이벤트를 발생시키는 문서 객체 변수가 정의된 블록이 달라

listener() 함수 내부에서 textarea 변수에 접근할 수 없다.*

코드의 규모가 커질수록 두번째 코드와 같이 이벤트 리스너를 외부로 분리하는 경우가 많아지는데,

이벤트 리스너를 외부로 분리하는 경우에는 이벤트 리스너에서 이벤트를 발생시킨 문서 객체 변수에 어떻게 접근해야 할까?

 

* textarea.value.length에는 접근할 수 없지만 h1에는 접근할 수 있다.


event.curretTarget 속성

<script>
  const listener = (event) => {
    const length = event.currentTarget.value.length
    h1.textContent = `글자 수: ${length}`
  }

  document.addEventListener('DOMContentLoaded', () => {
    const textarea = document.querySelector('textarea')
    const h1 = document.querySelector('h1')

    textarea.addEventListener('keyup', listener)
  })
</script>
<script>
  const listener = function (event) {
    const length = event.currentTarget.value.length
    h1.textContent = `글자 수: ${length}`
  }

  document.addEventListener('DOMContentLoaded', () => {
    const textarea = document.querySelector('textarea')
    const h1 = document.querySelector('h1')

    textarea.addEventListener('keyup', listener)
  })
</script>


this 키워드

<script>
  const listener = function (event) {
    const length = this.value.length
    h1.textContent = `글자 수: ${length}`
  }

  document.addEventListener('DOMContentLoaded', () => {
    const textarea = document.querySelector('textarea')
    const h1 = document.querySelector('h1')

    textarea.addEventListener('keyup', listener)
  })
</script>


event.currentTarget 속성은 이벤트 리스너의 형태가 () => {}일 경우, function () {}일 경우 모두 사용 가능하지만

this 키워드는 이벤트 리스너의 형태가 function () {}일 경우에만 사용 가능하다.

지금부터는 구체적인 이벤트와 함께 앞서 배운 내용을 적용해보자.

키보드 이벤트
이벤트 설명
keypress 키가 입력될 때 실행된다.
keydown 키가 눌릴 때 실행된다.
keyup 키가 떨어질 때 실행된다.


이때 keypress와 keydown이 비슷해보일 수 있는데,

keypress는 실제로 어떤 값이 입력되었는지를, keydown은 물리적으로 어떤 키가 눌렸는지를 구분한다.

예를 들어 shift 키를 누를 경우 keydown은 동작하는 반면 keypress는 동작하지 않고,

'a'와 'A'를 입력할 경우 keydown은 동일한 keyCode를 반환하지만 keypress는 상이한 keyCode를 반환한다.

keypress와 keydown은 웹 브라우저에 따라 아시아권 문자를 제대로 처리하지 못 하는 문제가 있어 일반적으로는 keyup을 사용한다.

더보기
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const textarea = document.querySelector('textarea')
    const h1 = document.querySelector('h1')
  
    const listener = (event) => {
      const length = textarea.value.length
      h1.textContent = `글자 수: ${length}`
    }
  
    textarea.addEventListener('keyup', listener)
  })
</script>
<body>
  <h1></h1>
  <textarea></textarea>
</body>

키보드 이벤트가 발생할 경우 이벤트 객체는 어떤 키가 눌렸는지와 관련된 속성들을 담고있기 때문에

키가 눌렸는지 떨어졌는지 뿐만 아니라 어떤 키가 눌렸는지에 따른 이벤트 설정도 가능하다.

이벤트 속성 설명
code 입력한 키
keyCode 입력한 키를 나타내는 숫자
altKey alt키 입력 여부
ctrlKey ctrl키 입력 여부
shiftKey shift키 입력 여부
더보기
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const h1 = document.querySelector('h1')
    
    const print = (event) => {
      let output = ''
      output += `alt: ${event.altKey}<br>`
      output += `ctrl: ${event.ctrlKey}<br>`
      output += `shift: ${event.shiftKey}<br>`
      output += `code: ${typeof(event.code) !== 'undefined' ? event.code : event.keyCode}<br>`
      h1.innerHTML = output
    }
    
    document.addEventListener('keyup', print)
    document.addEventListener('keydown', print)
  })
</script>
<body>
  <h1></h1>
</body>

 

글자 입력 양식 이벤트


사용자로부터 어떠한 입력을 받을 때 사용하는 요소를 입력 양식이라고 한다.

입력 양식으로는 <input>, <button>, <textarea>, <select> 태그 등이 있는데

 

앞서 배운 내용들을 활용하면 사용자의 입력에 따른 이벤트를 연결할 수 있다.

 

<input> 태그

더보기
  • <input type="text">
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const input = document.querySelector('input')
    const p = document.querySelector('p')

    const isEmail = (value) => {
      return (value.indexOf('@') > 1 && value.split('@')[1].indexOf('.') > 1)
    }

    input.addEventListener('keyup', (event) => {
      const value = event.currentTarget.value

      if (isEmail(value)) {
        p.style.color = 'green'
        p.textContent = `이메일 형식입니다: ${value}`
      } else {
        p.style.color = 'red'
        p.textContent = `이메일 형식이 아닙니다: ${value}`
      }
    })
  })
</script>
<body>
  <input type="text"></input>
  <p></p>
</body>
  • <input type="checkbox">
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const input = document.querySelector('input')
    const h1 = document.querySelector('h1')

    let [timerId, timer] = [0, 0]

    input.addEventListener('change', () => {
      if (event.currentTarget.checked) {
        timerId = setInterval(() => {
          timer += 1
          h1.textContent = `${timer}초`
        }, 1 * 1000)
      } else {
        clearInterval(timerId)
      }
    })
  })
</script>
<body>
  <input type="checkbox"> 타이머 활성화
  <h1></h1>
</body>
  • <input type="radio">
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const inputs = document.querySelectorAll('input')
    const output = document.querySelector('#output')

    inputs.forEach((input) => {
      input.addEventListener('change', (event) => {
        if (event.currentTarget.checked) {
          output.textContent = `좋아하는 애완동물은 ${event.currentTarget.value}이시군요!`
        }
      })
    })
  })
</script>
<body>
  <h3># 좋아하는 애완동물을 선택해주세요</h3>
  <input type="radio" name="pet" value="강아지"> 강아지
  <input type="radio" name="pet" value="고양이"> 고양이
  <input type="radio" name="pet" value="햄스터"> 햄스터
  <input type="radio" name="pet" value="기타"> 기타
  <hr>
  <h3 id="output"></h3>
</body>

<button> 태그

더보기
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const input = document.querySelector('input')
    const button = document.querySelector('button')
    const p = document.querySelector('p')

    button.addEventListener('click', () => {
      const inch = Number(input.value)

      if (isNaN(inch)) {
        p.textContent = '숫자를 입력해주세요.'
        return
      }

      const cm = inch * 2.54
      p.textContent = `${cm} cm`
    })
    
  })
</script>
<body>
  <input type="text"> inch
  <button>계산</button>
  <p></p>
</body>

<textarea>

더보기

키보드 이벤트를 활용한 글자 수 세기는 상황에 따라 제대로 작동하지 않는다. 이때 focus 이벤트, blur 이벤트를 사용하면 어떤 상황에서 어떤 언어를 사용하든 글자 수를 제대로 셀 수 있다.

<script>
  document.addEventListener('DOMContentLoaded', () => {
    const h1 = document.querySelector('h1')
    const textarea = document.querySelector('textarea')

    let timerId

    textarea.addEventListener('focus', (event) => {
      timerId = setInterval(() => {
        h1.textContent = `글자 수: ${event.currentTarget.value.length}`
      }, 50)
    })

    textarea.addEventListener('blur', (event) => {
      clearInterval(timerId)
    })
  })
</script>
<body>
  <h1></h1>
  <textarea></textarea>
</body>

<select> 태그

더보기
  • <select>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const input = document.querySelector('input')
    const select = document.querySelector('select')
    const span = document.querySelector('span')

    let calculated
    let constant = 10

    const calculate = () => {
      span.textContent = (calculated*constant).toFixed(2)
    }

    select.addEventListener('change', (event) => {
      const options = event.currentTarget.options
      const index = options.selectedIndex

      constant = Number(options[index].value)
      
      calculate()
    })

    input.addEventListener('keyup', (event) => {
      calculated = Number(event.currentTarget.vaule)

      calculate()
    })
  })
</script>
<body>
  <input type="text"> cm =
  <span></span>
  <select>
    <option value="10">mm</option>
    <option value="0.1">m</option>
    <option value="0.393701">inch</option>
  </select>
</body>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const select = document.querySelector('select')
    const p = document.querySelector('p')

    select.addEventListener('change', (event) => {
      const options = event.currentTarget.options
      const index = options.selectedIndex

      p.textContent = `선택: ${options[index].textContent}`
    })
  })
</script>
<body>
  <select>
    <option>떡볶이</option>
    <option>순대</option>
    <option>오뎅</option>
    <option>튀김</option>
  </select>
  <p>선택: 떡볶이</p>
</body>
  • <select multiple>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const select = document.querySelector('select')
    const p = document.querySelector('p')

    select.addEventListener('change', (event) => {
      const options = event.currentTarget.options
      let list = []

      for (const option of options) {
        if (option.selected) list.push(option.textContent)
      }

      p.textContent = `선택: ${list.join(', ')}`
    })
  })
</script>
<body>
  <select multiple>
    <option>떡볶이</option>
    <option>순대</option>
    <option>오뎅</option>
    <option>튀김</option>
  </select>
  <p>선택: 떡볶이</p>
</body>

 

기본 이벤트 막기

 

웹 브라우저에서 이미지에 마우스 우버튼을 클릭하면 컨텍스트 메뉴를 출력한다.

 

이처럼 어떤 이벤트가 발생했을 때 웹 브라우저가 기본적으로 처리해주는것을 기본 이벤트라고 한다. 

 

링크를 클릭했을 때 이동하는 것, 제출 버튼을 눌렀을 때 이동하는 것 등은 모두 기본 이벤트에 해당한다.

 

이러한 기본 이벤트를 제거할 때는 event 객체의 preventDefault() 메소드를 사용한다.

더보기
  • 컨텍스트 메뉴 막기
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const img = document.querySelector('img')

    img.addEventListener('contextmenu', (event) => {
      event.preventDefault()
    })
  })
</script>
<body>
  <img src="https://dbscthumb-phinf.pstatic.net/2741_000_1/20130924141049649_3O0FKR6B9.jpg/ia13_21_i2.jpg?type=m251&wm=Y">
</body>
  • 링크 이동 막기
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const input = document.querySelector('input')
    const a = document.querySelector('a')

    let status = false

    input.addEventListener('change', (event) => {
      status = event.currentTarget.checked
    })

    a.addEventListener('click', (event) => {
      if (!status) {
        event.preventDefault()
      }
    })
  })
</script>
<body>
  <input type="checkbox">
  <span>링크 활성화</span>
  <a href="https://www.naver.com">네이버</a>  
</body>

 

 

 

 

 


이 글은 혼자 공부하는 자바스크립트 (윤인성 저)를 바탕으로 공부한 내용을 작성한 글입니다.