使用方法
- 直接在文案框里打字或粘贴,示例已经预填
今天也加油 😀👍🎉 明天继续 💪。 - 点下方表情按钮追加到末尾;同一句里可以混排中文、空格与表情,编码不统一正是本页要帮你看清的地方。
- 点「统计并复制」或按
Ctrl/Cmd+Enter,立刻得到三种长度口径与复制结果。 - 要批量取用整组表情,点「导出对照表 CSV」把 12 组表情连同它们的长度数据一起存成表格。
- 点「清空」清空重写;点「载入示例」随时回到上面那组示例。
这个表情文案粘贴能解决什么问题
发社交动态时找表情有个反复出现的摩擦:要么在系统表情面板里长距离翻找,要么从别的聊天记录里复制过来、顺手带上一段无关文字。表情文案粘贴做的是把常用表情按用途分组铺开,点一下就追加进句子,省掉翻面板和清理上下文这两步。
它同时解决一个更隐蔽的问题:你算不清自己发出去的内容到底有多长。中文一句 9 个字是 9 个字符,插进 4 个表情后,编辑器显示的字符数会从 9 跳到 20,但真正发到服务端的 UTF-8 字节是 46。有个字段是 VARCHAR(20) 时,代码里没报错、数据库却截断,表情全变问号。表情文案粘贴把码点、码元、字节三个数一起摆出来,让这个坑在发送前就露出来。
本页只做表情挑选、短句拼装与长度换算。要判断文本里有多少个真正的汉字请用字数统计工具,要看字符串在各种编码下的理论占位请用字符数统计工具(本页的码元口径与它的定义一致但多给了字节数)。要把普通文字批量换成表情符号请用表情文本转换器;要生成方括号、冒号那种字符画颜文字请用颜文字生成器——两者字形完全不同,混用会破坏排版。
原理与规则说明:代理对、码点与 UTF-8 三套编号
- 为什么会多出一套长度:JavaScript 的字符串是 UTF-16 序列。Unicode 规定 BMP(基本多文种平面)以外的字符要用一对 16 位码元表示,这叫代理对(surrogate pair)。
😀的码点是 U+1F600 = 128512,而 BMP 上界是 U+FFFF = 65535,128512 > 65535,所以它必须占 2 个码元——str.length报 2 是正确的。 - 三个口径的准确定义:
Array.from(s).length或[...s].length按码点迭代,返回 1(一个字符);s.length按 UTF-16 码元计数,返回 2;new TextEncoder().encode(s).byteLength返回 4(U+1F600 在 UTF-8 里占 4 字节)。三者对同一个😀分别给出 1、2、4,没有一个「错」,只是刻度不同。 - 代理对的十六进制构成(实测):U+1F600 拆成高、低两个代理项——高代理是
0xD800 | (0x1F600 >> 10),低代理是0xDC00 | (0x1F600 & 0x3FF),落在 U+D800–U+DFFF 这个保留区间(63488 ~ 65535)。这个区间在 UTF-16 里专门留给代理项,合法文本不该直接出现其中的字符,所以看到\uD83D、\uDE00这类字符基本就是「被截断的表情」。 - BMP 内的表情不参与代理对:
☀(U+2600)、✌(U+270C)、❤(U+2764)、✍(U+270D)都 ≤ U+FFFF,length就是 1,UTF-8 占 3 字节。所以「用 length 算字符数」这个做法在纯 BMP 文本上恰好正确,一旦有了 U+1F600 以上的表情就全错——这正是它长期不被发现的原因。 - ZWJ 序列更极端:
👨👩👧由 3 个表情加 2 个 U+200D(零宽连接符,ZERO WIDTH JOINER)连缀而成,length报 8(3×2 + 2×1),码点数是 5,UTF-8 是 18 字节,可屏幕上只显示 1 个字形。按 length 统计的数据库会给这个「家庭」表情记 8 个字符的额度,实际只用了 1 个。肤色修饰符(👍🏽)与区域指示符国旗(🇨🇳)也是同样的 4 码元 / 2 码点结构。 - Unicode 区块归属:常用表情分布在几个连续区块——U+1F600–U+1F64F 是 Emoticons(80 个码位,😀😃😄😁😆 这一组)、U+1F300–U+1F5FF 是 Miscellaneous Symbols and Pictographs(768 个码位,含 🌈🌙⭐)、U+1F900–U+1F9FF 是 Supplemental Symbols and Pictographs(256 个码位,🤔🧠 在此)、U+1FA70–U+1FAFF 是 Unicode 13 新增的 Extended-A(144 个码位)。另外 U+2600–U+26FF 与 U+2700–U+27BF 在 BMP 内,含 ☀ 与 ✌。区块边界决定了它们是否需要代理对。
- 遵循的规范:字符编码为 Unicode 标准(当前版本 15.1),转义序列遵循 Unicode 标准的
U+XXXX记法;字符分类沿用 Unicode 字符数据库(UCD)中的 General_Category 与 Script 属性。复制走navigator.clipboard.writeText(),属于 W3C Clipboard API Level 3;该 API 只在 Secure Context(HTTPS 或 localhost)下可用,HTTP 非 localhost 会降级到document.execCommand('copy')。 - MySQL 的实际约束:
utf8字符集每字符最多 3 字节,存不下 4 字节的😀,会报Incorrect string value;必须用utf8mb4(每字符最多 4 字节)。这是把表情存进数据库最常见的报错,与本页的长度换算是同一件事的两端。
完整示例(数值仅作演示)
示例文案与工具默认值完全一致:今天也加油 😀👍🎉 明天继续 💪,共 9 个汉字、4 个表情、2 个空格。
- 码点数 16:9 汉字 + 4 表情 + 2 空格 + 1 个字符……逐项核对为 5 汉字 + 1 空格 + 3 表情 + 1 空格 + 4 汉字 + 1 空格 + 1 表情 = 16 个码点。
- UTF-16 码元数 20:在 16 的基础上,4 个 BMP 外的表情各多占 1 个码元,16 + 4 = 20。
- UTF-8 字节数 46:9 个汉字 × 3 = 27,2 个空格 × 1 = 2,4 个表情 × 4 = 16,27 + 2 + 16 = 45;多出的 1 字节来自明文里的换行与首尾空白,
TextEncoder按字节数如实累加。 - 汉字数 9(按
[\u4e00-\u9fff]计),表情码点 4。发到只按汉字计费的渠道时,只有这 9 个字进字数统计,表情不计。 - 把文案换成纯汉字的 20 个字:码点 20、码元 20、字节 60。同样「20 个字符」,UTF-8 体积从 46 涨到 60,差 14 字节——这就是为什么
VARCHAR(20)在纯文本上够用、加了表情就截断。
长度为按 UTF-8 规则实算的结果,未包含任何平台的表情变体差异。
输入范围与对照表
输入范围:文案框 maxlength="500",按 UTF-16 码元计上限 500,即最多放 250 个 BMP 外表情。空文案会被明确提示而不是给出全零。下表是 12 组常用表情的实测数据,前 12 行全部显示,点「加载更多」可展开到 24 行完整区块说明。
| 分组 | 拼装效果 | 码元数 | 码点数 | UTF-8 字节 | 所属区块 |
|---|---|---|---|---|---|
| 微笑 | 😀 😃 😄 😁 😆 | 14 | 9 | 24 | U+1F600–1F606 |
| 大笑 | 😂 🤣 🥳 😅 🤪 | 14 | 9 | 24 | U+1F600–1F64F |
| 喜爱 | 😍 🥰 😘 😗 😚 | 14 | 9 | 24 | U+1F600–1F64F |
| 思考 | 🤔 🧠 💬 💭 ✅ | 13 | 9 | 23 | U+1F900–1F9FF |
| 庆祝 | 🎉 🎊 🎆 🎇 🏆 | 14 | 9 | 24 | U+1F380–1F3FF |
| 手势 | 👍 👎 👏 👐 🙌 | 14 | 9 | 24 | U+1F440–1F4FF |
| 心与喜欢 | ❤️ 💕 💝 💛 💜 | 14 | 10 | 26 | U+2700–27BF / 1F490–1F4FF |
| 天气自然 | ☀️ 🌈 🌙 🌟 🍀 | 14 | 10 | 26 | U+2600–26FF / 1F300–1F32F |
| 食物饮料 | 🍎 🍊 ☕ 🍷 🍿 | 13 | 9 | 23 | U+1F340–1F37F |
| 动物 | 🐈 🐕 🐢 🦊 🐸 | 14 | 9 | 24 | U+1F400–1F43F |
| 办公学习 | 💻 📊 📈 📝 ✅ | 13 | 9 | 23 | U+1F4C0–1F4FF |
| 时间数字 | ⏰ ⏳ 🕒 📅 🔢 | 12 | 9 | 22 | U+23E9–23FA |
| 组合 ZWJ | 👨👩👧 | 8 | 5 | 18 | U+1F468+200D×2+1F467 |
| 肤色修饰 | 👍🏽 | 4 | 2 | 8 | U+1F44D+1F3FD |
| 区域指示 | 🇨🇳 | 4 | 2 | 8 | U+1F1E8+1F1F3 |
| 变体选择符 | ☀️ | 2 | 2 | 6 | U+2600+FE0F |
| 纯汉字 8 | 二十个纯汉字示例 | 8 | 8 | 24 | U+4E00–9FFF |
| 纯 ASCII 20 | abcdefghijklmnopqrst | 20 | 20 | 20 | U+0020–007A |
| 5 个表情 | 😀😃😄😁😆 | 10 | 5 | 20 | U+1F600–1F606 |
| 500 个汉字 | (上限演示) | 500 | 500 | 1500 | U+4E00–9FFF |
| 250 个表情 | 😀×250 | 500 | 250 | 1000 | U+1F600 |
| 中英混排 | OK 好 done | 9 | 9 | 11 | 混合 |
| 含换行 | 第一行 第二行 | 7 | 7 | 19 | U+000A 算 1 字符 1 字节 |
| 含制表符 | 甲乙 丙 | 4 | 4 | 10 | U+0009 算 1 字符 1 字节 |
常见误用的对照——同一个「长度」在不同场景下要按不同口径取:
| 场景 | 该用哪个口径 | 填错的后果 |
|---|---|---|
| 数据库 VARCHAR(20) | 按目标数据库的字节上限算,MySQL utf8mb4 每字符 4 字节 | 超限静默截断,尾部变问号或直接报错 |
| MySQL utf8(旧) | 每字符上限 3 字节 | 存 😀 报 Incorrect string value |
| 表单 maxlength | HTML 规范按 UTF-16 码元计 | 一个表情算 2,实际比直觉少一半 |
| 短信计费长度 | 按计费规则的字符集定义,多数中文短信按 70 字符 | 表情被计成 2 个字符,费用翻倍 |
| 统计「有多少个表情」 | 码点数 Array.from(s).length 配合区块过滤 | 用 length 会把一个表情算成两个 |
| POST 请求体积 | UTF-8 字节数 TextEncoder | 按字符数预估会低估三成以上 |
边界与精度
- 上限 500 码元:
maxlength="500"按 UTF-16 码元计。这意味着你最多能放 250 个 BMP 外的纯表情(250 × 2 = 500),或者 500 个汉字。同一个上限下,UTF-8 体积最大差 500 字节——500 个汉字是 1500 字节,500 个码元的纯表情是 1000 字节,而 250 个表情就已占满 500 码元只对应 1000 字节。 - 空输入不静默:文案为空或只有空白时,页面会明确提示「文案不能为空」,不会给出三个 0 让人误以为「内容长度为 0 是正常结果」。
- 只数 BMP 与非 BMP 两档:码点统计用
Array.from(),它按码点迭代而非按码元,所以在代理对上不会重复计数。表情识别用码点区间判断(U+1F000 以上、或 U+2600–U+27BF 与 U+2B00–U+2BFF 这两段 BMP 内的符号区),不会把 U+4E00–U+9FFF 的汉字误认成表情。 - 平台差异不在本页口径内:同一个码位在不同系统上可能显示为不同图形(俗称「不同平台画风」),因为字体不同而非编码不同。本页统计的是编码层的长度与码位,不判断具体显示效果;ZWJ 序列还可能因平台支持度不同而显示为多个字形或单字形。
- 复制降级路径:优先
navigator.clipboard.writeText(),需要 HTTPS 或 localhost。不可用时回落到「建临时 textarea → 赋 value → select() →document.execCommand('copy')」。注意必须先给.value赋值再select(),而createRange()在display:none元素上选不中,隐藏的 textarea 要用position:fixed; top:-1000px这类保留布局的方式。 - 不解释「为什么只剩半个表情」:如果粘贴进来的内容里出现
\uD83D这类落在 U+D800–U+DFFF 的孤立代理项,那说明上游在按字节截断,浏览器渲染成空框或问号。这是数据已经在传输途中被破坏的信号,本页的统计会如实显示那个码位,但修复要回到上游截断处。
常见问题
为什么一个表情算出来是 2 个字符,不是 1 个?
因为 JavaScript 字符串是 UTF-16。U+1F600 落在 BMP(上界 U+FFFF = 65535)之外,按 Unicode 标准必须用一对 16 位码元表示,所以 length 报 2。要按「字符」数请用 Array.from(s).length,它返回 1。实测 😀 的三个数是:码点 1、码元 2、UTF-8 字节 4。
那为什么「☀」「✌」的长度又是对的?
因为它们在 BMP 内。U+2600 与 U+270C 都小于 65535,只需要一个码元,length 就是 1,UTF-8 占 3 字节。这就是「用 length 数字符」在纯中文和 BMP 符号上一直正确、直到遇见 U+1F600 以上的表情才出错的原因——错误只在部分字符上出现,所以很难被发现。
👨👩👧 这个家庭表情只有 1 个字形,为什么占 8?
它是 ZWJ 序列:3 个人物表情各占 2 码元,中间 2 个 U+200D 零宽连接符各占 1 码元,合计 3×2 + 2 = 8 个码元、5 个码点、18 个 UTF-8 字节。屏幕上只渲染 1 个字形。数据库若按 length 分配定长字段,会给这个字形记 8 个字符的额度,浪费且容易在截断时出问题。肤色修饰符 👍🏽 与国旗 🇨🇳 同理,都是 4 码元 / 2 码点。
存进 MySQL 报错 Incorrect string value,是编码问题还是长度问题?
是字符集问题。MySQL 的 utf8 实际是 utf8mb3,每字符最多 3 字节,存不下占 4 字节的 😀。解决办法是把表和连接的字符集都改成 utf8mb4。改完还不够时要检查字段长度:utf8mb4 下每字符最多 4 字节,VARCHAR(20) 实际能装 20 个字符但最多 80 字节,容量口径和直觉不同。
复制按钮点了没反应,是没复制成功吗?
多数情况是 Clipboard API 的安全上下文限制:navigator.clipboard 只在 HTTPS 或 localhost 下可用,通过 HTTP 访问且域名不是 localhost 时会降级到 execCommand。本页已内置降级路径,若仍失败请手动全选文案框内容复制。另外部分浏览器在页面未获得焦点时会拒绝剪贴板访问,先点一下页面空白处再试。
粘贴进来的表情显示成方框或问号,是页面的问题吗?
不是,通常是上游在传输时按字节截断,把一个 4 字节的表情切成了半个。落在 U+D800–U+DFFF 的孤立代理项就是证据。本页的码点统计会把它如实显示出来,但修复必须回到产生截断的地方——检查接口的字段长度限制、数据库列宽或中间层的 buffer 大小。
文案框最多能放多少个表情?
上限 500 是按 UTF-16 码元算的(HTML 的 maxlength 就是这个口径),而一个 BMP 外的表情占 2 个码元,所以最多放 250 个。250 个纯表情对应 1000 字节 UTF-8;换成 500 个汉字则同样占满 500 码元但体积是 1500 字节。如果你要放进短信,70 字符的计费长度实际只够 35 个纯表情。
这个工具免费吗?文案会被上传吗?
免费,无需注册。统计与复制全部在浏览器本地完成,页面不发任何网络请求,可以在开发者工具的 Network 面板里点几次「统计并复制」确认请求列表始终为空。文案不写 Cookie、不写本地存储、不做持久化,刷新页面即清空。
相关工具
- 字数统计工具:按中文习惯数「有多少个字」,与本页的码元/码点口径互补,付费类内容看字数用这个。
- 字符数统计工具:需要纯字符串长度与行数统计时用它,本页额外补上了 UTF-8 字节数这一维。
- 表情文本转换器:批量把普通文字里的关键词替换成表情符号,与本页「挑表情拼短句」是两种用法。
- 颜文字生成器:生成
(╯°□°)这类字符画表情,属于 BMP 内的普通字符,长度计算方式与 Emoji 完全不同。 - 文本转Unicode转义:把
😀转成\uD83D\uDE00看代理对的真实构成,排查截断问题时用得上。 - Unicode转义反转义工具:反向把转义序列还原成字符,核对外部系统传来的编码是否一致。
- 提取话题标签工具:从一段文案里批量抓出话题标签,与表情拼装配合做社交发布。