前言
近期需要在系统中需要适配emoji。但发现不同的emoji的字符长度不同,比如 👩👩👧👧的长度为11,而😂的长度为2,但针对客户来说他认为一个表情就应该只占一个长度,于是产生了一系列的问题
为什么不同的emoji所占的长度不同?
每个emoji实际上是由一个或多个Unicode字符组成的。简单的emoji,例如“😊”,是单个Unicode字符。而复杂的emoji,例如“👨👩👧👦” (家庭),是由多个Unicode字符组合而成的。
在js中使用UTF-16进行处理字符串,对于大多数常见的字符都处理为一个16位的码元,但对于emoji以及一些汉字则需要使用两个16位的码元甚至更多。这就是所谓的为什么不同的emoji所占的长度不一样。
如何计算emoji的长度
可以看到在emoji表情中由"\uDxxx"组成的占一位,那么😂的实际组成也就是"\uD83D\uDE02"
解决办法
最简单的处理方式就是引用lodash,使用其中的toArray函数
npm install lodash
测试
this.testEmojiLength("😂");
this.testEmojiLength("👩👩👧👧");
testEmojiLength(emoji){
console.log(`${emoji}length`,lodashFn.toArray(emoji).length)
}
此时也就达到了最开始的要求,在客户眼中一个表情占一个长度。
如何在程序中兼容现有的maxLength?
场景很简单,在表单中需要限制maxLength为10
思路
监听表单相应的控件,当输入的内容超过最大长度时进行截断
代码实现
ngAfterViewInit(): void {
const telephoneControl = this.validateForm.get("telephone");
telephoneControl.valueChanges.subscribe((currentValue) => {
const currentLength = lodashFn.toArray(currentValue).length;
// 如果当前值长度超过10,则进行截断处理
if (currentLength > 10) {
const truncatedValue = this.truncateToMaxLength(currentValue, 10);
telephoneControl.setValue(truncatedValue);
}
});
}
//截取函数
truncateToMaxLength(str, maxLength) {
// 正则表达式,用于匹配 Unicode 字符和 emoji
const reUnicode =
/\ud83c[\udffb-\udfff](?=\ud83c[\udffb-\udfff])|(?:[^\ud800-\udfff][\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]?|[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?)*/g;
let length = 0; // 当前已处理的字符长度
let result = ""; // 截取后的结果字符串
let match; // 正则匹配的结果
// 逐个匹配字符串中的字符和 emoji
while ((match = reUnicode.exec(str)) !== null) {
// 如果再添加一个字符会超过最大长度,则停止
if (length + 1 > maxLength) {
break;
}
result += match[0]; // 将匹配的字符或 emoji 添加到结果中
length += 1; // 更新当前长度
}
return result; // 返回截取后的字符串
}
发现的问题
发现如果从头输到尾,没有什么问题,但发现输入到最大时,移动光标还能输入,此时会覆盖最后一位的内容。如下图
解决方式
通过监听键盘输入,当字符串长度超过maxLength时,阻止事件的触发
onKeyDown(event: KeyboardEvent): void {
//允许的键位
const allowedKeys = ["Backspace", "ArrowLeft", "ArrowRight", "a"];
const currentValue = this.validateForm.get("telephone").value;
const currentLength = lodashFn.toArray(currentValue).length;
if (event.ctrlKey && event.key === "a") {
// 允许 Ctrl+A 进行全选
return;
}
if (!allowedKeys.includes(event.key) && currentLength >= 10) {
event.preventDefault();
}
}
别忘了在html上加上
<input id="ipt" type="text" style="width:100%" dw-input formControlName="telephone" (keydown)="onKeyDown($event)">