사용자가 폼 요소를 클릭하거나 Tab 키를 눌러 요소로 이동하면 해당 요소가 포커스(focus)됩니다. autofocus라는 HTML 속성을 사용해도 요소를 포커스 할 수 있는데 이 속성이 있는 요소는 페이지가 로드된 후 자동으로 포커싱 됩니다. 이 외에도 요소를 포커싱(focusing)할 수 있는 방법은 다양합니다.
요소를 포커싱한다는 것은 일반적으로 '여기에 데이터를 입력할 준비를 하라’는 것을 의미하기 때문에 요소 포커싱이 이뤄지는 순간엔 요구사항을 충족시키는 초기화 코드를 실행할 수 있습니다.
요소가 포커스를 잃는 순간(blur)은 요소가 포커스를 얻는 순간보다 더 중요할 수 있습니다. 사용자가 다른 곳을 클릭하거나 Tab 키를 눌러 다음 폼 필드로 이동하면 포커스 상태의 요소가 포커스를 잃게 됩니다. 이 외에도 다양한 방법을 사용해 포커스를 잃게 할 수 있습니다.
요소가 포커스를 잃는 것은 대개 '데이터 입력이 완료되었다’는 것을 의미하기 때문에 포커싱이 해제되는 순간엔 데이터를 체크하거나 입력된 데이터를 저장하기 위해 서버에 요청을 보내는 등의 코드를 실행할 수 있습니다.
포커스 이벤트를 다룰 땐 이상해 보이지만 중요한 기능이 있는데, 이번 챕터에선 이에 대해서 자세히 다뤄보도록 하겠습니다.
focus, blur 이벤트
focus 이벤트는 요소가 포커스를 받을 때, blur 이벤트는 포커스를 잃을 때 발생합니다.
두 이벤트를 입력 필드 값 검증에 사용해 봅시다.
예시에서 각 핸들러는 다음과 같은 역할을 합니다.
blur핸들러에선 필드에 이메일이 잘 입력되었는지 확인하고 잘 입력되지 않은 경우엔 에러를 보여줍니다.focus핸들러에선 에러 메시지를 숨깁니다(이메일 재확인은blur핸들러에서 합니다).
<style>
.invalid { border-color: red; }
#error { color: red }
</style>
이메일: <input type="email" id="input">
<div id="error"></div>
<script>
input.onblur = function() {
if (!input.value.includes('@')) { // @ 유무를 이용해 유효한 이메일 주소가 아닌지 체크
input.classList.add('invalid');
error.innerHTML = '올바른 이메일 주소를 입력하세요.'
}
};
input.onfocus = function() {
if (this.classList.contains('invalid')) {
// 사용자가 새로운 값을 입력하려고 하므로 에러 메시지를 지움
this.classList.remove('invalid');
error.innerHTML = "";
}
};
</script>
모던 HTML을 사용하면 required, pattern 등의 다양한 속성을 사용해 입력값을 검증 할 수 있습니다. HTML 속성만으로도 검증이 가능한거죠. 그럼에도 불구하고 자바스크립트를 사용하는 이유는 자바스크립트가 좀 더 유연하기 때문입니다. 여기에 더하여 자바스크립트를 사용하면 제대로 된 값이 입력되었을 때 자동으로 해당값을 서버에 보낼 수 있기 때문이기도 합니다.
focus, blur 메서드
elem.focus()와 elem.blur() 메서드를 사용하면 요소에 포커스를 줄 수도 있고 제거할 수도 있습니다.
사이트 방문자가 유효하지 않은 값을 입력하면 사이트를 떠나지 못하도록 하는 예시를 살펴봅시다.
<style>
.error {
background: red;
}
</style>
이메일: <input type="email" id="input">
<input type="text" style="width:220px" placeholder="이메일 형식이 아닌 값을 입력하고 여기에 포커스를 주세요.">
<script>
input.onblur = function() {
if (!this.value.includes('@')) { // 이메일이 아님
// 에러 출력
this.classList.add("error");
// input 필드가 포커스 되도록 함
input.focus();
} else {
this.classList.remove("error");
}
};
</script>
이 예시는 Firefox(bug)를 제외한 모든 브라우저에서 정상 동작합니다.
이메일이 아닌 값을 입력하고 Tab 키나 다른 곳을 클릭해 <input>을 벗어나려 하면 onblur 메서드가 동작해 포커스를 다시 입력 필드로 돌려놓습니다.
여기서 주의해야 할 점은 onblur는 요소가 포커스를 잃고 난 후에 발생하기 때문에 onblur 안에서 event.preventDefault()를 호출해 포커스를 잃게 하는걸 '막을 수 없다’라는 사실입니다.
A focus loss can occur for many reasons.
One of them is when the visitor clicks somewhere else. But also JavaScript itself may cause it, for instance:
- An
alertmoves focus to itself, so it causes the focus loss at the element (blurevent), and when thealertis dismissed, the focus comes back (focusevent). - If an element is removed from DOM, then it also causes the focus loss. If it is reinserted later, then the focus doesn’t return.
These features sometimes cause focus/blur handlers to misbehave – to trigger when they are not needed.
The best recipe is to be careful when using these events. If we want to track user-initiated focus-loss, then we should avoid causing it ourselves.
tabindex를 사용해서 모든 요소 포커스 하기
대다수의 요소는 기본적으로 포커싱을 지원하지 않습니다.
포커싱을 지원하지 않는 요소 목록은 브라우저마다 다르긴 하지만 한 가지 확실한 것은 <button>, <input>, <select>, <a>와 같이 사용자가 웹 페이지와 상호작용 할 수 있게 도와주는 요소는 focus, blur를 지원한다는 사실입니다.
반면 <div>, <span>, <table>같이 무언가를 표시하는 용도로 사용하는 요소들은 포커싱을 지원하지 않습니다. 따라서 이런 요소엔 elem.focus() 메서드가 동작하지 않고 focus, blur이벤트도 트리거 되지 않습니다.
그럼에도 불구하고 포커스를 하고 싶다면 tabindex HTML 속성을 사용하면 됩니다.
tabindex 속성이 있는 요소는 종류와 상관없이 포커스가 가능합니다. 속성값은 숫자인데, 이 숫자는 Tab 키를 눌러 요소 사이를 이동할 때 순서가 됩니다.
요소가 두 개 있다고 가정하고 첫 번째 요소의 tabindex엔 1을, 두 번째 요소의 tabindex엔 2를 할당하면 첫 번째 요소가 포커싱되어있는 상태에서 Tab을 눌렀을 때 두 번째 요소로 포커스가 이동합니다.
포커싱 되는 요소 순서는 다음과 같습니다. tabindex가 1인 요소부터 시작해 점점 큰 숫자가 매겨진 요소로 이동하고 그다음 tabindex가 없는 요소(평범한 <input> 요소 등)로 이동합니다.
tabindex가 없는 요소들은 문서 내 순서에 따라 포커스가 이동합니다(기본 순서).
그런데 tabindex를 사용할 땐 주의해야 할 사항이 있습니다.
-
tabindex가0인 요소 – 이 요소는tabindex속성이 없는것처럼 동작합니다. 따라서 포커스를 이동시킬 때tabindex가0인 요소는tabindex가 1보다 크거나 같은 요소보다 나중에 포커스를 받습니다.tabindex="0"은 요소를 포커스 가능하게 만들지만 포커스 순서는 기본 순서 그대로 유지하고 싶을 때 사용합니다. 요소의 포커스 우선 순위를 일반<input>과 같아지도록 하죠. -
tabindex가-1인 요소 – 스크립트로만 포커스 하고 싶은 요소에 사용합니다. Tab키를 사용하면 이 요소는 무시되지만elem.focus()메서드를 사용하면 잘 포커싱 됩니다.
예시를 살펴봅시다. 첫 번째 항목을 클릭하고 Tab 키를 눌러보세요.
첫 번째 항목을 클릭하고 Tab 키를 눌러보면서 포커스 된 요소 순서를 눈여겨보세요. 참고로 탭을 많이 누르면 예시 밖으로 포커스가 이동하니, 주의하세요.
<ul>
<li tabindex="1">일</li>
<li tabindex="0">영</li>
<li tabindex="2">이</li>
<li tabindex="-1">음수 일</li>
</ul>
<style>
li { cursor: pointer; }
:focus { outline: 1px dashed green; }
</style>
보시다시피 포커스는 tabindex가 1, 2, 0인 요소로 차례로 이동합니다. <li>는 기본적으로 포커스 할 수 없는 요소이지만 예시에서 tabindex를 사용해서 실제 포커스를 해보았고 포커스 된 요소는 :focus를 사용해서 스타일을 바꿔보았습니다.
elem.tabIndex 프로퍼티를 사용해도 됩니다.자바스크립트를 사용해 elem.tabIndex 프로퍼티를 추가해주면 tabindex 속성을 사용한 것과 동일한 효과를 볼 수 있습니다.
focusin과 focusout을 사용해 이벤트 위임하기
focus와 blur 이벤트는 버블링 되지 않습니다.
예시를 살펴봅시다. <form>에 onfocus를 추가해 강조 효과를 주려고 했는데, 의도한 대로 동작하지 않는 것을 확인할 수 있습니다.
<!-- 폼 안에서 포커스가 된 경우 클래스를 추가함 -->
<form onfocus="this.className='focused'">
<input type="text" name="surname" value="성">
<input type="text" name="name" value="이름">
</form>
<style> .focused { outline: 1px solid red; } </style>
의도한 대로 예시가 동작하지 않는 이유는 사용자가 <input>을 포커스 해도 focus 이벤트는 해당 입력 필드에서만 트리거 되기 때문입니다. focus 이벤트는 버블링 되지 않습니다. 따라서 form.onfocus는 절대 트리거 되지 않습니다.
이런 기본동작을 피해 이벤트 위임 효과를 주는 방법은 두 가지가 있습니다.
첫 번째 방법은 focus와 blur는 버블링 되지 않지만 캡처링은 된다는 점을 이용하면 됩니다.
아래 예시를 직접 실행해봅시다.
<form id="form">
<input type="text" name="surname" value="성">
<input type="text" name="name" value="이름">
</form>
<style> .focused { outline: 1px solid red; } </style>
<script>
// 캡쳐링 단계에서 핸들러가 트리거되도록 합니다(마지막 인수가 true)
form.addEventListener("focus", () => form.classList.add('focused'), true);
form.addEventListener("blur", () => form.classList.remove('focused'), true);
</script>
두 번째 방법은 focusin과 focusout을 이용하는 것입니다. 두 이벤트는 focus, blur와 동일하지만 버블링이 된다는 점에서 차이가 있습니다.
focusin과 focusout을 사용할 때 주의할 점은 on<event> 방식으로 핸들러를 추가하면 안 되고 elem.addEventListener 방식으로 핸들러를 추가해야 한다는 점입니다.
예시를 실행해 실제 이벤트가 버블링 되는지 확인해봅시다.
<form id="form">
<input type="text" name="surname" value="성">
<input type="text" name="name" value="이름">
</form>
<style> .focused { outline: 1px solid red; } </style>
<script>
form.addEventListener("focusin", () => form.classList.add('focused'));
form.addEventListener("focusout", () => form.classList.remove('focused'));
</script>
요약
focus와 blur 이벤트는 각각 요소가 포커스를 받을 때, 잃을 때 발생합니다.
두 이벤트를 사용할 땐 다음을 유의해야 합니다.
focus와blur이벤트는 버블링 되지 않습니다. 캡처링이나focusin,focusout을 사용하면 이벤트 위임 효과를 볼 수 있습니다.- 대부분의 요소는 기본적으로 포커스를 지원하지 않습니다. 그럼에도 불구하고 포커스 하고 싶은 요소가 있다면
tabindex를 사용하면 됩니다.
현재 포커스된 요소는 document.activeElement를 통해 확인할 수 있습니다.
댓글
<code>태그를, 여러 줄로 구성된 코드를 삽입하고 싶다면<pre>태그를 이용하세요. 10줄 이상의 코드는 plnkr, JSBin, codepen 등의 샌드박스를 사용하세요.