中文输入法中光标跟随能力触发的浏览器事件探究

news2024/10/2 6:33:05

:::tip 最近在着手腾讯文档的输入体验优化,在其中有一个不起眼的小需求引起了我的注意,并顺便研究了一些事件监听机制相结合的特点,特此记录一下填坑过程。 :::

模拟光标跟随

大部分的主流输入法都有这样一个特性,在输入中文时,可以通过左右方向键控制光标,移动至输入区中任意两个字符之间的位置,用户接下来的字符输入将在光标处直接插入。

由于腾讯文档的渲染的画布是完全自主实现的,为了在体验上与普通可编辑画布保持一致,我们需要自己来模拟这一光标的移动行为。

首先,我们需要确定的是输入法中的模拟光标进行更新的时机。经试验,用户在进行中文输入时,若使用了_方向键_移动光标,将会触发光标的移动行为。因此,首先要解决的是使用合适的事件监听来捕获这一行为,从而进行更新。既然是对输入框的行为进行模拟,自然而然的,我们首先想到的是输入框触发的监听器。

浏览器输入框对输入的监听机制

在浏览器对键盘的输入规范中,将键盘输入分为了直接输入与间接输入两种。直接输入将会触发输入框的 onInput 事件 (IE9 之前不支持该事件,只能用 onKeyUp 等键盘事件作为降级选择)。而对于间接输入,规范将事件监听分为了 onCompositionStart, onCompositionUpdate, onCompositionEnd 三个部分。

而间接输入的同时,中间态的写入也会导致输入框内容的变化,从而也会触发 onInput 事件。因此在间接输入中,事件的触发次序为:onCompositionStart, onCompositionUpdate, onInput, onCompositionEnd

需要注意的是,若输入完成时,输入框的内容没有发生变化,则 onChange 事件与 onCompositionEnd 事件都将不会被触发。

中文输入法在键入选词的过程属于间接输入情况,此时中间文本不会直接落盘在输入框内。而通过回车等按键退出中文输入选词后,中文文字将会落盘到输入框,此时属于直接输入情况。

而我们需要关注的光标事件显然是在间接输入中获取到的。在输入法选词光标左右移动时,由于内容不变,此时并不会触发 onInput 事件,但是会触发一次 onCompositionUpdate 事件,我们可以通过这个事件来判断光标位置,重置画布的光标位置。但最终我们并未使用这个事件做判断器,原因在下面会讲到。

判断当前光标的位置

解决了了光标的重置时机,接下来就该解决光标的位置判定了。由于 DOM 标准中并没有直接获取光标位置的方法,因此这一块也需要我们自主实现。我的思路是,通过选取光标到输入起始位置的字符串,判断选中的字符串长度,即可知道光标当前位置相对于起始位置的偏移量,从而确定光标位置。

对于普通的 input 输入框来说起始比较简单,输入框提供了 inputElement.selectionStart 属性作为当前光标位置距离输入起始点的偏移量,我们直接使用就可以了。但是对于 contentEditable=true 的 div 节点来说是没有这一属性的,我们得另想办法。

根据之前写 E2E 测试得来的灵感,我们可以模拟创建一个从当前光标位置到输入起始位置的选区,通过判断该选区的字符串长度即光标所在位置的偏移量。通过 window.getSelection() 方法能够得到 Selection 对象,这是一个表示当前文本选区的对象,由于我们正处在输入状态中,因此该选区位置就在当前的输入框中,从而能获取到上面所需的偏移量。

const selection = window.getSelection();
// 确定输入框在输入态,存在选区
if (selection.rangeCount > 0) {const range = selection.getRangeAt(0);return range.endOffset;
} 

获取完光标位置,还需要在我们的画布上重新设置回去。设置的思路其实是类似的,通过使用document.createRange方法新建一个选区范围,其起始位置设置为需要移动的目标位置,然后移除选区,即可使光标落在目标位置了。

性能优化

之前说到在光标移动时的确会触发一次onCompositionUpdate 事件。但是,onCompositionUpdate 事件是一个高频的操作,每一次间接输入时都会触发,这会导致光标不断地重置位置,带来不必要的性能损失。

并且,onCompositionUpdate 事件的入参只有更新的中间字符串值,只能用来判断输入中间字符串是否发生变化。移动光标行为本身并不会导致字符串发生改变,但反过来,使字符串不发生改变的操作一定是移动光标操作这一说法并不成立。因此,尽管移动光标会触发该事件,但我们仍然没有有效的手段去判断是输入法中的光标移动导致的事件触发。

那么,之前用很大篇幅讲过光标变动的本质实际上是选区变化,那么,输入法触发的光标移动会不会给输入框发出选区变更通知呢?很不幸,目前绝大多数的输入法都是不支持的。并且由于光标移动被视为输入法内部的行为,因此在输入框中光标所进行的移动,不会有事件主动抛出。因此,输入框中的选区变更事件 onSelectionChange 事件也无法被触发。

既然输入框中的事件监听无法准确判断光标的移动,我们只能退而求其次,从更低层次的逻辑,通过监听键盘的按键输入来尝试还原这一行为了。优化思路是这样的,触发光标跟随的时机规则为:用户输入时,若使用了_左方向键_移动光标,将会开启光标跟随的能力,随着输入不断更新的光标位置,直到光标再次被移动到末尾位置结束。由于中文输入时按下_左方向键_的行为是一个低频操作,这样一来,大部分的输入操作都不需要执行判断并重置光标,提高普通输入下的性能表现。

附上最终的判断逻辑吧:

那么,如何获取并判断用户输入时的按键信息呢?当然是使用更第一层级的事件接口 KeyboardEvent 了。

键盘输入事件对中文输入法的支持

KeyboardEvent 在低层级下提示用户与一个键盘按键的交互是什么,不涉及这个交互的上下文含义。一般来说当你需要处理文本输入的时候,应当使用上节所说的输入框监听事件代替。例如当用户使用其他方式输入文本时,如平板电脑的手写系统等,键盘事件可能不会触发。

> KeyboardEvent 对象描述了用户与键盘的交互。 每个事件都描述了用户与一个按键(或一个按键和修饰键的组合)的单个交互;事件类型 keydown,keypress 与 keyup 用于识别不同的键盘活动类型。

键盘输入事件的设计思路与间接输入的钩子类似,浏览器中对于键盘输入同样分为 onKeyDown, onKeyPress, onKeyUp 三个阶段的事件触发,分别对应按键不同的行为触发时机。(注:onKeyPress 事件高度依赖设备支持,所以尽量不要使用该钩子)

这三个事件都传入了 KeyboardEvent 入参,帮助我们了解当前执行该事件时触发的按键信息。MDN 上该入参具有如下属性支持:

在文档规范中,我们可以发现许多对问题的解决十分有用的新属性,例如 event.isComposing 属性用于判断当前是否会触发 onCompositionUpdate 事件,event.code 用于判断与键盘布局与输入状态无关的当前按键输入,获取中文输入中的按键轻而易举。我们可以利用这两个状态帮助我们完成按键监听与事件触发。

兜底方案支持

之前说过, KeyboardEvent 是一个十分依赖软硬件支持的事件,不仅需要浏览器的能力支持,与输入法甚至键盘类型都有关系。经试验后发现,这些新属性在许多浏览器与输入法的组合中都无法通过onKeyDown正确获取,在 Windows 下部分中文输入法甚至都无法支持 event.key 属性。为了达到最大的兼容性,在兜底的方法下,仅能用 event.keyCode 这种已经被 deprecated 的方法来勉强替代使用了。

兜底方案的使用问题就此解决了吗?并没有。中文拼音的输入中间字符是系统无法识别的。在 Windows 桌面应用程序对键盘输入规范中,我们发现 Windows 将所有未识别的设备输入都设置为 VK_PROCESSKEY 229,浏览器的 event.keyCode 复用了这一规范,因此在中文输入过程中,无论按下什么按键,返回的 event.keyCode 永远是 229。

网上对于该问题的解决方案都是建议使用 onKeyUp 代替 onKeyDown。但首先,这不满足对于一个要求实时体现输入的光标移动操作要求。第二,使用 onKeyUp 会有更多的问题,在 Windows 下进行中文输入时,由于不同的输入法回调 onKeyUp 的实现不同,该事件可能会被触发一次或两次,要么全为 229,要么一次为 229,另一次为正确的 key(对,说的就是你,搜狗)。为了避免我们去不断去填五花八门的第三方输入法实现的坑,兜底方案采用了当检测到输入了未识别的按键时,也启用光标跟随能力。

结语

一套操作下来,这套中文输入法下光标跟随的功能算是完美实现了。回顾一下我们解决这个问题所趟过的坑,实际上也反映着浏览器 JS DOM 标准在不断进化,不断补足历史遗留的坑点。当然,它还远远称不上完美,仍然存在大量的能力缺失,如我们在这个问题中遇到的判断光标偏移量的解决方案,本质上还是一种 hack。而扩展 JS 的能力边界,使其变得更强大,更好用,这正是我们作为前端开发人员需要努力的方向。

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/95483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

昆仑天工开源的AIGC

🍿*★,*:.☆欢迎您/$:*.★* 🍿 https://github.com/SkyWorkAIGC/SkyCode 技术优势一 :涵盖多种编程语言 不同的编程语言着重于解决不同平台、环境下的问题,不同的编程语言都有自己存在的理由。奇点智源SkyCode能够生成的代码,不仅包括使用广泛的JavaScript、python、Jav…

(六)springcloud之Nacos集群与持久化配置-3

模块: 1.父工程(管理版本) 2.公共模块:Common-API 3.测试模块:NacosClusterConfigConsumer80 版本: springboot:2.7.6 springcloud:2021.0.5 spring-cloud-alibaba-dependencies:2021.0.1.0 nacos:2.1.2 ng…

RCFSNet

搬来了一种结合道路上下文信息与多尺度特征的道路遥感图像道路提取方法,与常见的道路提取算法相比,RCFSNet能够获取完整的路网标签,在遮挡场景中表现出色 本人是太原理工大学大数据学院在2022年发表在《IEEE Geoscience and remote sensing …

SpringBoot网站开发常用工具类(自己写的适合入门)

目录 字符集转换工具 适用 代码 JWT工具类 适用 代码 七牛云文件上传工具类 适用 代码 文件类型转换工具类 适用 代码 session,cookie工具类 适用 代码 字符集转换工具 适用 主要是完成与前端配合,共同实现特殊字符串传输过程中被转译的问…

数字藏品系统应用场景介绍——元宇宙NFG

数字藏品系统应用场景: 1.虚实结合产品营销:品牌企业限量发行产品,比如限量珍藏版产品,茅台酒,耐克鞋,劳力士手表,爱马仕包包,钻石珠宝,结合元宇宙热点营销,…

Crane 发布国内首个云原生应用碳排放计算优化器

为了共同应对气候变化挑战,减缓全球变暖趋势,2015年12月,近200个缔约方共同通过了《巴黎协定》(The Paris Agreement),对2020年后全球如何应对气候变化做出了行动安排。为实现这一目标,全球多个…

我国液化石油气行业SWOT分析:产销规模持续上涨 供需缺口劣势明显

液化石油气是在炼油厂内,由天然气或者石油进行加压降温液化所得到的一种无色挥发性液体,它极易自燃,当其在空气中的含量达到了一定的浓度范围后,它遇到明火就能爆炸。 一、优势分析 根据观研报告网发布的《中国液化石油气市场发展深度分析与…

【Unity】UI ToolKit 学习记录

Unity推出的这个 UI ToolKit,据说是要用来替代UGUI。既然这么有野心,那肯定要搞来看一看。这次使用目标就是用这个 UI ToolKit 生成一堆类似HUD的头标,然后看看使用难易程度和性能如何。 本文对应Unity版本 :2020.3.41f1c1 1、安装…

四十六——五十一

四十六、JavaScript——对象 一、对象 数据类型:原始值: 1. 数值 Number 2. 大整数 BigInt 3. 字符串 String 4. 布尔值 Boolean 5. 空值 Null 6. 未定义 Undefinded 7. 符号 Symbol 除了七种原始值之外,后面所用到的数据类型,都…

吐槽嫌弃测试周期太长?开发自测一下

互联网产品竞争激烈,在生存的巨大压力之下,策划和运营人员们恨不得每一个需求都能秒级上线,这就给研发团队带来了巨大的压力。 有时候,产品的老大很关注某一个功能点,希望能尽快上线,可是,他发…

基于nodejs电影售票后台管理的设计和实现.zip(论文+源码+ppt文档+视频录制)

相关资料下载地址:请点击下载》》》 一、 项目介绍 5 二、 需求分析 6 1、 前端需求 6 2、 后端需求 6 3、 开发环境 7 三、 技术介绍 7 1、 Vue 7 2、 ElementUI 7 3、 NodeJS 7 4、 MySQL 8 四、 功能实现 8 1、 前端服务构建 8 2、 前端API接口封装 8 3、 前端路…

技术分享 | 掌握高频 Docker 命令,夯实内功基础

本文为霍格沃兹测试学院学院学员 Docker 实战课程学习笔记,供各位同学参考。 在 Dokcer 横空出世之前,应用打包一直是大部分研发团队的痛点。在工作中,面对多种服务,多个服务器,以及多种环境,如果还继续用传…

我的头条四面:测试工程师调岗测试开发工程师,发生了什么?

早就听说头条是算法大厂手撕代码恐怖如斯,进入玻璃房之前做好了心理准备,本次头条面试总共是四面,一二三技术面HR面,总体感觉也还不错,面试的是测试工程师,最后拿到的offer却是测试开发工程师,从…

Mysql主从同步时Slave_SQL_Running状态为Yes , 但是Slave_IO_Running状态为Connecting以及NO的情况故障排除

1. 环境说明 主机IP : 192.168.154.146 从机1IP : 192.168.154.147 从机2IP : 192.168.154.148 2.故障记录 当使用Navicat工具打开这三个数据库时 , 发现主库和从库的数据不同 3.排查过程-(Slave_IO_Running状态为Connecting) 3.1 网络是否互通 互ping三台机器 , 看机器…

Python自由职业可以做什么?副业月入3000的快乐你根本想象不到

很多有时间的程序员都会在业余时间接一些“私活”,也就是我们说的副业! 毕竟虽然程序员加班时间长,但是也不是所有程序员都是需要997的…许多事业编制或者说一部分公司并不会出现特别夸张的加班时长。平常周末的时候也就会接一些副业&#x…

如何用Python操作PDF制作数据报告?

大家好,你是否会发现 Python 操作PDF文档内容,主要围绕PDF文档的内容提取、合并与拆分、加密与解密、添加水印以及不同文档格式相互转换来展开。 但大家会发现,其中并没有有太多直接操作PDF并向其写入的内容。这是因为我们更推荐大家Python自…

C语言练习之计算一个数的每位之和(递归实现)

目录 前言 一、思路 二、源代码以及运行截图 源代码: 运行截图: 总结 前言 写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和 例如,调用DigitSum(1729),则应该返回1 7 2 9&#…

LeetCode HOT 100 —— 297.二叉树的序列化与反序列化

题目 序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。 请设计一个算法来实现二叉树的序列化与反序列化。…

你还以为格子衫头发的就是程序员?这些特征都没有别说你是程序员

周一写几百个bug,周二到周四拼命改bug,周五总结bug,总结的好有周末,总结的不好周末无休! 咳咳,这样的周末我可以不休息! 有很多想要学习Python却找不到途径的朋友,我这里整理了一…

.NET Framework杂记

这篇博客主要记录在用C#编写上位机时,不会的知识点,随时更新,方便查阅。 C#语法操作杂记c#中让textbox选中不选中C#无法使用实例引用来访问成员解决方法针对不同定义情况的引用解释C# 字符串分割用字符串分割用多个字符串分割用单字符分割C#中…