가끔 사이트 이용 중에 한국어 입력 시, 마지막 글자가 2번 입력이 되는 모습을 본 적이 있을 것이다.
(아래 짤은 한국어 입력 후 Enter를 눌렀을 때, 마지막 글자가 한번 더 입력되는 모습)
해당 문제는 조합 중인 글자에서 발생하는 IME 문제이므로 compositionstart
(조합시작), compositionend
(조합 완성) 이벤트를 이용하여 '글자 조합 확정 이벤트'를 예외처리 해주면 된다.
IME에 대해 좀 더 알아보고 싶다면, 아래의 코드를 실행하여 영어로도 입력해보길 추천
(영어는 조합형 글자가 아니기 때문에 composition evnet가 발생하지 않음)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>IME 이벤트 디버깅</title>
<style>
body {
font-family: sans-serif;
padding: 20px;
}
#input {
font-size: 1.2em;
padding: 8px 16px;
width: 300px;
}
#stack {
margin-top: 20px;
border: 1px solid #ccc;
padding: 10px;
overflow-y: auto;
background-color: #f9f9f9;
font-family: monospace;
}
.event-line {
padding: 4px;
border-bottom: 1px dotted #ddd;
}
#list {
margin-top: 30px;
padding-left: 20px;
}
#list li {
font-size: 1.1em;
margin-bottom: 4px;
}
</style>
</head>
<body>
<h2>IME 이벤트 디버깅</h2>
<input type="text" id="input"/>
<div id="stack"></div>
<h3>📋 Todo List</h3>
<ul id="list"></ul>
<script>
const input = document.getElementById("input");
const stack = document.getElementById("stack");
const list = document.getElementById("list");
function pushToStack(message) {
const time = new Date().toLocaleTimeString();
const div = document.createElement("div");
div.className = "event-line";
div.textContent = `[${time}] ${message}`;
stack.prepend(div); // 최신 이벤트를 위로 쌓기
console.log(`[${time}] ${message}`);
}
function addTodo() {
const value = input.value;
const li = document.createElement("li");
li.textContent = value;
list.appendChild(li);
input.value = "";
pushToStack(`[addTodo] '${value}' 추가됨`);
}
let isComposing = false;
input.addEventListener("compositionstart", () => {
isComposing = true;
pushToStack("compositionstart");
});
input.addEventListener("compositionend", (e) => {
isComposing = false;
pushToStack(`compositionend → data: '${e.data}'`);
});
input.addEventListener("compositionupdate", (e) => {
pushToStack(`compositionupdate → data: '${e.data}'`);
})
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
if (isComposing === true) {
pushToStack(`OS가 조합 중인 글자를 확정 짓는 이벤트는 예외처리: isComposing = ${isComposing}`);
return;
}
pushToStack(`조합 완료된 글자 (${input.value})만 Todo에 반영: isComposing = ${isComposing}`);
addTodo();
}
});
</script>
</body>
</html>
한글 (정확히는 조합형 글자)을 입력하고 Enter 키를 누르면 마지막 글자가 한 번 더 입력되는 문제가 발생한다.
Enter
키로 입력을 완료하는 기능 구현 시, 글자 조합 과정과 맞물려 2가지의 이벤트가 수행된다.
1. Keydown 이벤트: js로 등록해둔 keydown 이벤트가 수행된다.
2. 글자 조합 확정 이벤트: IME는 확정된 문자열을 커밋(commit)한다.
"사용자가 입력한 문자열"과 "확정된 문자열 커밋"이라는 두 이벤트로 인해 위 예시처럼 (한국어) + (어)가 발생하게 된 것이다.
한국어는 자모 (字母)를 조합해서 하나의 글자로 만드는 "조합형 글자"이다.
단계 | 입력 | 자모 구성 | 조합 중 글자 상태 | IME 이벤트 |
---|---|---|---|---|
1 | ㅎ 입력 | 초성: ㅇ | ㅎ (조합 시작) | compositionstart + update |
2 | ㅏ 입력 | 중성: ㅏ | 하 (ㅎ + ㅏ) | compositionupdate |
3 | ㄴ 입력 | 종성: ㄴ | 한 (하 + ㄴ) | compositionupdate |
4-1 | ㄱ 입력 | 초성: ㄱ | 한 (조합 완성) | compositionend |
4-2 | ----- | 초성: ㄱ | ㄱ (조합 시작) | compositionstart + update |
조합 중인 글자는 사용자의 키 입력 순서대로 compositionstart -> compositionupdate -> compositionend 순으로 이벤트가 발생한다.
그리고 조합된 글자는 일반적으로 텍스트 커서(Text Cursor)가 다음 글자로 넘어가는 순간이라고 이해하면 된다 (위 표에서 4-1,4-2). 위 상황 외에도 Enter
키를 누르면 OS가 내부적으로 해당 문자를 확정 짓는다 (이로인해 IME 문제가 발생하게 된다).