有些Web应用在触发诸如撤销、重做等机制过程中,凡涉及到中文输入法输入的内容,会在编辑器里呈现中文词条的输入法编码。以下案例虽然不是撤销重做,但也可以展现词条编码“上屏”的情形,案例中使用流行通用的防抖机制来限制输入文本的“上屏”操作,具体达成的是:用户的键盘输入会实时上屏,但连续的输入仅在两次输入之间间隔一秒,输入的内容才会“上屏”。点击“预览”进入效果演示页面,请尝试使用中文输入法输入任何文字(单字或词组都可以),故意让输入法候选词窗口停留一小段时间再选择输出的词条,此间会发现,“大屏”文本框里存在文字的输入法编码文本——
<style>
.tMid { text-align: center; margin: 20px; }
#showbox { width: 600px; height: 200px; padding: 12px; }
#inputbox { width: 300px; padding: 8px; }
</style>
<p class="tMid">
<textarea id="showbox" value="" readonly></textarea>
</p>
<p class="tMid">
<label for="inputbox" onclick="inputbox.value=''">请输入:</label>
<input id="inputbox" type="text" value="" />
</p>
<script>
// 防抖通用函数
function debounce(func, delay) {
let timer = null; // 定义定时器
return function (...args) {
clearTimeout(timer); // 清除之前的定时器
timer = setTimeout(() => {
func.apply(this, args); // 延迟执行函数
}, delay);
};
}
// 输入内容搬到“大屏幕”
inputbox.addEventListener('keyup', debounce((e) => {
showbox.value += inputbox.value + '\n';
}, 1000));
</script>
本文的任务就是避免上述案例展示的词条编码“上屏”现象的发生。
这首先需要理解输入法上屏的基本工作流程,分为三个步骤:
一、compositionstart(开始合成)
以汉字“我”为例,其完整拼音输入法编码是
wo,当输入第一个字母
w,合成工作开始,这回触发 compositionstart 流程。
JS compositionstart 事件
compositionstart 事件是在用户开始使用文本合成系统,如输入法编辑器(IME)开始新的文本合成会话时触发的。例如,当用户使用拼音输入法开始输入汉字时,就会触发此事件。这个事件对于处理输入法中的文本输入尤为重要,因为它允许开发者在文本的合成过程开始时执行特定的逻辑。
二、compositionupdate(合成更新)
全拼输入“我”字,下一个字母是
o,这一部分的输入本质上实在更新编码的合成,触发编码的合成更新事件。
JS compositionupdate 事件
当文本合成系统(如输入法编辑器)控制的文本合成会话中接收到新字符时,会触发 compositionupdate 事件。该事件可能多次触发,例如输入“我们”,
w之后的
omen这四个字母都会各自触发一次 compositionupdate 事件。
三、compositionend(合成结束)
拼音输入法输入环境下,通常文字的编码打完还不能上屏,需要按空格键或候选序号或以其它方式操作(如鼠标点击)才能让文字上屏;支持顶格上屏的输入法(例如五笔等)如果启用了顶格上屏机制则编码输入完毕会自动上屏。不论手动选择上屏还是自动上屏,上屏动作发生时会触发文本合成结束信息,触发 compositionend 事件。
JS compositionend 事件
当文本段落的组成完成或取消时,compositionend 事件将被触发 (具有特殊字符的触发,需要一系列键和其他输入,如语音识别或移动中的字词建议)。
知道了输入法上屏的机制,我们就可以找到实现的方法。现在,将上述案例的代码修改如下,我们的目标就可以实现:
<style>
.tMid { text-align: center; margin: 20px; }
#showbox { width: 600px; height: 200px; padding: 12px; }
#inputbox { width: 300px; padding: 8px; }
</style>
<p class="tMid">
<textarea id="showbox" value="" readonly></textarea>
</p>
<p class="tMid">
<label for="inputbox" onclick="inputbox.value=''">请输入:</label>
<input id="inputbox" type="text" value="" />
</p>
<script>
let composing = false; // 合成状态标记
function debounce(func, delay) {
let timer = null; // 定义定时器
return function (...args) {
clearTimeout(timer); // 清除之前的定时器
timer = setTimeout(() => {
func.apply(this, args); // 延迟执行函数
}, delay);
};
}
// 监听输入框键位输入 :内容搬到“大屏幕”
inputbox.addEventListener('keyup', debounce((e) => {
if (!composing) showbox.value += inputbox.value + '\n';
}, 1000));
// 监听输入框合成动作开始 : 正在合成,禁止上屏
inputbox.addEventListener('compositionstart', () => composing = true);
// 监听输入框合成动作结束 : 上屏
inputbox.addEventListener('compositionend', () => composing = false);
</script>
核心:使用自定义的 composing 变量来记录输入法输入特定文字的合成态和完成态,开始时值为假(false),这样:其一,英文输入状态下输入的文本正常上大屏;其二,中文输入法状态下,监听 compositionstart(合成开始)事件,只要该事件被触发(开始输入汉字),合成态就存在(
composing = true),此时会阻止上大屏的操作,同时监听 compositionend 事件,即,当输入法确定选词操作被触发时激活完成态,即令
composing = false,此时文本才可以上大屏。而 composing 变量必须作为一个开关作用于文本框的输入监听事件(详见案例二第 30 行代码的 if 语句,案例一对应语句即第 29 行代码中没有 if 条件判断)。