maxlength 按 UTF-16 码元计数而非用户感知字符数,导致 emoji 或生僻汉字(如 ?、?)因代理对占用 2 码元而被误限;应使用 Array.from(str).length 或 Intl.Segmenter 按图形单位校验,并前后端统一用 Unicode 字符数而非字节或码元长度。

maxlength 对中文、emoji 等多字节字符“数错了”
maxlength 是按 UTF-16 码元(code unit)计数,不是按字符(grapheme cluster)或字节数。在 JavaScript 和 HTML 渲染引擎中,一个 emoji(如 ?)或某些生僻汉字(如 ?)会占用 2 个 UTF-16 码元(即代理对,surrogate pair),但 maxlength="10" 仍只允许输入 10 个码元 —— 意味着最多只能输 5 个这类字符,而不是 10 个“人眼可见的字符”。
常见现象:用户粘贴一段含 emoji 的文本,明明只看到 8 个字符,却提示“超出最大长度”,控制台里 input.value.length 返回 12;或者用手机键盘输入“?”(U+34000,需代理对表示),maxlength="1" 直接拦住。
- 浏览器原生
maxlength无法识别 Unicode 字符边界,它只做简单的String.prototype.length比较 - 该行为在所有主流浏览器(Chrome/Firefox/Safari/Edge)中一致,不是 bug,是规范定义
- 服务端若也用
strlen()或类似字节长度校验,可能和前端不一致,导致“前端拦不住、后端报错”
用 JavaScript 手动校验时别直接用 .length
想真正按“用户感知的字符数”限制,必须用能识别 Unicode 字符边界的方案,而不是依赖 str.length。
基于HTML5和CSS3的Emoji自定义表情编辑器,初始默认是一个人脸,你可以在编辑器中为该人脸表情添加和改变各种面部表情,例如带上帽子,噘嘴等,甚至可以将其他任意Emoji表情添加上去,用起来非常不错。
-
Array.from(str).length可正确返回 Unicode 字符数(ES2015+,支持大多数 emoji 和增补平面字符) - 更健壮的替代:使用
Intl.Segmenter(现代浏览器支持):const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });<br>const count = [...segmenter.segment(str)].length; - 兼容性要求高时,可用
grapheme-splitter库(注意体积),避免自己实现正则拆分 —— Unicode grapheme cluster 规则太复杂,正则易漏 - 校验时机建议放在
input和paste事件里,且要防绕过(比如拖拽文本、开发者工具改 DOM)
后端必须独立校验,不能信任前端 maxlength
前端 maxlength 只是体验优化,完全可被禁用或绕过。后端若按字节长度(如 UTF-8 下 len(bytes))校验,和前端的“字符数”逻辑天然错位。
- 例如:字符串
"??"(ZWNJ 连接的 emoji 组合)在 UTF-8 中占 11 字节,但用户认为是 1 个字符;maxlength="10"前端放行,后端若设max_bytes=10直接拒绝 - 推荐后端统一用“Unicode 字符数”校验(Python 用
len(list(grapheme.graphemes(s))),Go 用golang.org/x/text/unicode/norm+iter,Node.js 可复用Intl.Segmenter或grapheme-splitter) - 数据库字段长度也要匹配语义:MySQL 的
VARCHAR(10)是按字符数(启用utf8mb4时),但 PostgreSQL 的character varying(10)同样按字符,不是字节 —— 别被旧文档误导
移动端输入法和剪贴板行为加剧问题
iOS 和 Android 输入法常把连续 emoji 或带修饰符的序列(如 ???)作为单次输入,但底层仍是多个码元;粘贴时,系统可能插入零宽连接符(ZWJ)、变体选择符(VS16),进一步拉长 .length 值,而用户毫无感知。
- 测试时务必用真机 + 实际输入法(尤其 iOS 的 emoji keyboard、三星键盘),模拟器常不还原真实行为
- 监听
paste事件时,event.clipboardData.getData('text/plain')可能已被系统标准化,但getData('text/html')有时保留原始编码细节,慎用 - 若业务强依赖精确字符数(如短信字数、微博发布),必须在光标位置实时计算 grapheme 数,并动态截断 —— 不能等表单提交才校验
Array.from(value).length 校验后,仍需在 beforeinput 或 input 中同步截断值,否则用户看到光标卡住但输入框内容已超限。**

















