我一直很喜欢 Microsoft Fluent Emoji。

在 Microsoft 推出包含 Fluent Emoji 的 Windows 11 开发预览时,我第一时间升级了系统 ✨。
我随即产生了在网页上显示 Fluent Emoji 的想法,于是 rehype-fluent-emoji 诞生了。
复制这件小事
得益于 Twemoji 作为开源 Emoji 的先行者,已经有相关生态能实现将网页上 emoji 替换为 Twemoji。
但方案大多都是直接替换为图片。
替换为图片会让复制行为变怪,比如选中文字的情况下复制不到,选中时文字与图片间样式也会需要对齐。

通过 JavaScript 监听复制 Event 来动态替换复制内容也能走通这条路,但这好脏。
我一直很反感复制一段文字,还在里面塞一段版权声明的行为……本质上和那些劫持剪贴板的流氓软件没什么区别。
因为 emoji 本身是文字,复制我文章里的一句话,粘贴到别处后,应该得到同一句话,而不是丢掉里面的表情。
最后比较好的实现方案是把文本层、视觉层都分开:
- 文本层保留 Unicode,用来参与选择和复制。
- 视觉层负责显示 Fluent Emoji。
- 选中状态通过共享 CSS 继续让 Unicode 字形保持透明,但不抢页面自己的选区背景。
图片从哪来
解决了布局设计上,接着就是 Fluent Emoji 的图片从哪来的问题。
Microsoft 确实十分慷慨得提供了 Fluent Emoji,但它的仓库结构并不适合直接拿来用。
开源社区也有人做出了 fluent-emoji-webfont,但因为还有兼容性问题,我也没有优先考虑。

于是在先后考察 LobeHub、emoji-regex-xs、unicode-emoji-json 这些库后,我终于找到了 fluentui-emoji-unicode 这个项目。
它完美符合我的预期,把 Fluent Emoji 仓库里的图片转换为 Unicode 字符编码的文件名。
不过有一个问题,因为这个仓库只做了重命名,文件大多还是 SVG 格式,而 Safari 对于 Fluent Emoji 这种包含 filter 的 SVG 图片支持特别差,导致显示的效果不如预期。

于是我 fork 并加上了格式转换,把图片格式统一为了 WebP。
做什么,不做什么
unified 下的 remark 和 rehype 其实分工明确,所以做成 rehype 插件更符合语义。
图片下载到本地 public 下也是一个需求,因为我不想强依赖 CDN。
不过这超出了 rehype 的工作范畴,所以我把它做成了一个单独的命令。
rehype-fluent-emoji sync content --out public/emoji --style 3d显式执行同步命令,把文章里用到的 emoji 资产下载到指定目录。
然后渲染时只需要配置插件:
{ assetBase: '/emoji'}也就是说,插件只负责生成 URL。图片在哪里、什么时候同步、是否放 CDN,都交给使用者自己决定。
这也给你一些自由度,你完全可以把我的 fluentui-emoji-unicode 部署到自己的服务器,或是 fork 到 GitLab,Gitee。
当然你也可以略过拉取到本地这一步,直接用 https://raw.githubusercontent.com 或是 https://cdn.jsdelivr.net 来引用。
来试试吧
欢迎你去 GitHub 看源码,也可以去 npmx.dev 来安装这个 rehype 插件。
pnpm add -D rehype-fluent-emoji在 Astro 里大概是这样配置:
import rehypeFluentEmoji from 'rehype-fluent-emoji'
export default { markdown: { rehypePlugins: [ [ rehypeFluentEmoji, { assetBase: '/emoji', style: '3d', }, ], ], },}在这个站点里的用法
现在这个站点会先把文章里用到 Emoji 图片同步到本地:
{ "emoji:sync": "rehype-fluent-emoji sync content --out public/emoji --style 3d"}然后 MDX 里使用 rehype-fluent-emoji 渲染文章里的 emoji。
你可以查看 Fluent Emoji 渲染测试,看它在段落、列表、链接、代码块里的表现。
再见 👋
这个插件本身并不复杂,但我很喜欢一点点改进细节的过程,最后的效果我很满意。
我也很喜欢这个解法:视觉上有趣,但不破坏默认行为。
希望能做出更多有趣的东西~