规避输入法编码上屏问题
<style>.artBox { font: normal 18px/1.5 sans-serif; overflow: auto; position: relative; }
.artBox p { margin: 10px 0; }
.artBox h1, .artBox h2 { margin: 8px 0; }
.artBox code { background: #f7f4f3; padding: 2px 6px; tab-size: 4; }
.artBox blockquote { margin: 10px 20px; padding: 2px 15px; border-left: 3px solid skyblue; background: #e7e5e3; }
</style>
<div class="artBox">
<p>有些Web应用在触发诸如撤销、重做等机制过程中,凡涉及到中文输入法输入的内容,会在编辑器里呈现中文词条的输入法编码。以下案例虽然不是撤销重做,但也可以展现词条编码“上屏”的情形,案例中使用流行通用的防抖机制来限制输入文本的“上屏”操作,具体达成的是:用户的键盘输入会实时上屏,但连续的输入仅在两次输入之间间隔一秒,输入的内容才会“上屏”。点击“预览”进入效果演示页面,请尝试使用中文输入法输入任何文字(单字或词组都可以),故意让输入法候选词窗口停留一小段时间再选择输出的词条,此间会发现,“大屏”文本框里存在文字的输入法编码文本——</p>
<div class="hEdiv" data-prev="1"><pre class="hEpre">
<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>
</pre></div>
<p>本文的任务就是避免上述案例展示的词条编码“上屏”现象的发生。</p>
<p>这首先需要理解输入法上屏的基本工作流程,分为三个步骤:</p>
<h2>一、compositionstart(开始合成)</h2>
<p>以汉字“我”为例,其完整拼音输入法编码是
<code>wo</code>,当输入第一个字母
<code>w</code>,合成工作开始,这回触发 compositionstart 流程。
</p>
<blockquote>
<p>
<strong>JS compositionstart 事件</strong>
</p>
<p>compositionstart 事件是在用户开始使用文本合成系统,如输入法编辑器(IME)开始新的文本合成会话时触发的。例如,当用户使用拼音输入法开始输入汉字时,就会触发此事件。这个事件对于处理输入法中的文本输入尤为重要,因为它允许开发者在文本的合成过程开始时执行特定的逻辑。</p>
</blockquote>
<h2>二、compositionupdate(合成更新)</h2>
<p>全拼输入“我”字,下一个字母是
<code>o</code>,这一部分的输入本质上实在更新编码的合成,触发编码的合成更新事件。
</p>
<blockquote>
<p>
<strong>JS compositionupdate 事件</strong>
</p>
<p>当文本合成系统(如输入法编辑器)控制的文本合成会话中接收到新字符时,会触发 compositionupdate 事件。该事件可能多次触发,例如输入“我们”,
<code>w</code>之后的
<code>omen</code>这四个字母都会各自触发一次 compositionupdate 事件。
</p>
</blockquote>
<h2>三、compositionend(合成结束)</h2>
<p>拼音输入法输入环境下,通常文字的编码打完还不能上屏,需要按空格键或候选序号或以其它方式操作(如鼠标点击)才能让文字上屏;支持顶格上屏的输入法(例如五笔等)如果启用了顶格上屏机制则编码输入完毕会自动上屏。不论手动选择上屏还是自动上屏,上屏动作发生时会触发文本合成结束信息,触发 compositionend 事件。</p>
<blockquote>
<p>
<strong>JS compositionend 事件</strong>
</p>
<p>当文本段落的组成完成或取消时,compositionend 事件将被触发 (具有特殊字符的触发,需要一系列键和其他输入,如语音识别或移动中的字词建议)。</p>
</blockquote>
<p>知道了输入法上屏的机制,我们就可以找到实现的方法。现在,将上述案例的代码修改如下,我们的目标就可以实现:</p>
<div class="hEdiv" data-prev="1"><pre class="hEpre">
<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>
</pre></div>
<p>核心:使用自定义的 composing 变量来记录输入法输入特定文字的合成态和完成态,开始时值为假(false),这样:其一,英文输入状态下输入的文本正常上大屏;其二,中文输入法状态下,监听 compositionstart(合成开始)事件,只要该事件被触发(开始输入汉字),合成态就存在(
<code>composing = true</code>),此时会阻止上大屏的操作,同时监听 compositionend 事件,即,当输入法确定选词操作被触发时激活完成态,即令
<code>composing = false</code>,此时文本才可以上大屏。而 composing 变量必须作为一个开关作用于文本框的输入监听事件(详见案例二第 30 行代码的 if 语句,案例一对应语句即第 29 行代码中没有 if 条件判断)。
</p>
</div>
<script type="module">
import hl from 'https://638183.freep.cn/638183/web/js/hl.js';
const divs = document.querySelectorAll('.hEdiv');
const pres = document.querySelectorAll('.hEpre');
divs.forEach((div,idx) => hl.hl(div, pres));
</script> 谢谢黑黑老师辛苦,学习了。{:4_187:} 去试了一下,输入后需要停1秒就上屏了,如果一直输入就可以是很长一段文字。 不仅是输入的,复制过去的文字停1秒也能上屏呢。
黑黑讲解辛苦了{:4_190:} 红影 发表于 2026-6-18 19:29
不仅是输入的,复制过去的文字停1秒也能上屏呢。
黑黑讲解辛苦了
核心问题不是这个 红影 发表于 2026-6-18 19:24
去试了一下,输入后需要停1秒就上屏了,如果一直输入就可以是很长一段文字。
这事防抖机制,不是本文的核心内容 梦江南 发表于 2026-6-18 18:08
谢谢黑黑老师辛苦,学习了。
{:4_180:} 马黑黑 发表于 2026-6-19 21:39
核心问题不是这个
嗯,主要是学习如何上屏的事。 马黑黑 发表于 2026-6-19 21:39
这事防抖机制,不是本文的核心内容
有很多事都以为是天然的,实际不是,比如输入后的空格上屏,以为天生就是这样的。 红影 发表于 2026-6-20 22:41
有很多事都以为是天然的,实际不是,比如输入后的空格上屏,以为天生就是这样的。
所以很多东东是不能想当然的 红影 发表于 2026-6-20 22:40
嗯,主要是学习如何上屏的事。
酱紫 马黑黑 发表于 2026-6-20 22:58
所以很多东东是不能想当然的
一直使用的东西总是不知不觉就认为是天生的{:4_173:} 红影 发表于 2026-6-21 20:04
一直使用的东西总是不知不觉就认为是天生的
这是地道的想当然 马黑黑 发表于 2026-6-22 19:32
这是地道的想当然
人总有很多想当然的时候啊{:4_173:} 红影 发表于 2026-6-24 13:31
人总有很多想当然的时候啊
那倒也是 马黑黑 发表于 2026-6-24 22:37
那倒也是
不能够意识到是想当然,就比一直想当然的好{:4_173:} 红影 发表于 2026-6-25 20:57
不能够意识到是想当然,就比一直想当然的好
好那么一丁点儿 马黑黑 发表于 2026-6-26 20:40
好那么一丁点儿
好一点也是好{:4_173:} 红影 发表于 2026-6-28 17:30
好一点也是好
那是,不好也好 马黑黑 发表于 2026-6-28 22:18
那是,不好也好
能好着为什么要不好啊。