目录
- 注意
- 使用
- Vue / React 项目
- 验证
- Twemoji 的作用:
Twemoji 会把你网页/应用中的 Emoji 字符(如 😄)自动替换为 Twitter 风格的图片(SVG/PNG);
它不依赖系统字体,因此在 Android、Windows、macOS、iOS 等系统上都能显示统一的视觉效果;但显示出来的 不是 Apple emoji的风格,而是 Twitter 的风格。
场景 | 是否能解决 |
---|---|
安卓显示苹果风格 Emoji | 无法使用 Apple 的图形❌ |
安卓系统不支持新 Emoji,无法显示 | 用图像替代,兼容所有新旧设备 |
各系统 Emoji 风格不统一 | Twemoji 保证所有平台一致 |
不依赖用户系统字体 | ✅ |
可自定义大小、样式、加载策略 | ✅ |
注意
npm 包用的也是maxcdn的地址,但是maxcdn已经崩溃了。
之后所有的twemoji.parse都应该修改base
import twemoji from 'twemoji';
export default {
install(app: any) {
app.directive('twemoji', {
mounted(el: HTMLElement) {
twemoji.parse(el, {base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/'});
},
updated(el: HTMLElement) {
twemoji.parse(el,{base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/'});
}
});
}
};
//这样就可以正常加载了
国内源https://cdn.jsdmirror.com/gh/twitter/twemoji@14.0.2/assets/
cdn.jsdmirror.com
- emoji显示大小调整
Twemoji 默认插入的<img>
标签没有限制尺寸,导致显示时占用过多空间。
/* 全局限制 twemoji img 大小 */
img.emoji, img.emoji-svg {
display:inline-block;
width: 1em;
height: 1em;
vertical-align: -0.1em; /* 调整与文字基线对齐 */
}
使用
- CDN引入
<script src="https://twemoji.maxcdn.com/v/latest/twemoji.min.js"></script>
- 在页面加载后执行解析
<script>
document.addEventListener('DOMContentLoaded', function () {
twemoji.parse(document.body, {
folder: 'svg', // svg 或 72x72(PNG 图片)
ext: '.svg' // 或 '.png'
});
});
</script>
Vue / React 项目
- 安装
npm install twemoji
- 全局应用 Twemoji(也就是全站的 Emoji 都自动替换为 Twemoji 风格)方案:
框架 | 推荐方式 | 说明 |
---|---|---|
Vue 3 | 创建自定义指令 / 全局组件 | 自动处理所有含 Emoji 的元素 |
React | 使用全局解析函数(如 HOC 或 Layout hook) | 在渲染后全局替换 DOM 中 Emoji |
//vue 全局应用
//(用于 layout组件 或 app.vue)
<script setup>
import { onMounted } from 'vue';
import twemoji from 'twemoji';
onMounted(() => {
twemoji.parse(document.body, {
folder: 'svg',
ext: '.svg'
});
});
</script>
//react全局应用
//方法1 在 Layout / App.jsx 中统一解析
import React, { useEffect } from 'react';
import twemoji from 'twemoji';
function App() {
useEffect(() => {
twemoji.parse(document.body, {
folder: 'svg',
ext: '.svg'
});
}, []);
//只会在组件首次渲染(挂载)到 DOM 之后执行一次,
return (
<div>
<h1>Hello React 😄❤️🎉</h1>
{/* 其他页面组件 */}
</div>
);
}
export default App;
//方法2 创建高阶组件(HOC)包裹需要解析的组件
//接收组件 返回包装好的新组件
import twemoji from 'twemoji';
import { useEffect, useRef } from 'react';
export function withTwemoji(Component) {
return function Wrapped(props) {
const ref = useRef();
useEffect(() => {
if (ref.current) {
//ref.current 指的就是 useRef 返回的 ref 对象所实际引用的值
twemoji.parse(ref.current, {
folder: 'svg',
ext: '.svg'
});
}
}, []);
return (
<div ref={ref}>
<Component {...props} />
</div>
);
};
}
//需要使用的组件直接使用:
const TwemojiPage = withTwemoji(MyPageComponent);
twemoji.parse() 的作用是:它会扫描传入的 DOM 元素(在这里是 ref.current 所引用的元素)内部的文本内容,找到其中的 Unicode Emoji 字符,然后将这些字符替换成 <img>
标签,而<img>
标签的 src 属性指向 Twemoji 的 SVG 图片。ref.current 这个变量本身(它存储的内存地址)没有改变,它依然指向同一个 div 元素。但这个 div 元素内部的子节点和内容被 twemoji.parse 方法修改了。
- 但vue全局应用的方法可能出现问题:没有生效
解析时 DOM 内容还没真正渲染完,App.vue 的 onMounted() 仅保证 Vue 根组件挂载,但此时子组件内容可能尚未完全渲染进 DOM;
解决方法有两种
- 用 Layout 页面来做解析(推荐用于中大型项目)
不要在 App.vue 直接解析,而是把解析放到页面 Layout 或页面组件(如 MainLayout.vue 或 HomePage.vue)中的 onMounted() 中,这样确保 Emoji 渲染后再替换
推荐在主页面区域加个 id=“main-content” 来明确定位,不要直接操作整个 document.body。
保证这个区域包裹需要解析的区域
import { onMounted, ref } from 'vue';
import twemoji from 'twemoji';
onMounted(() => {
const el = document.getElementById('main-content'); // 指定 DOM 区域
if (el) {
twemoji.parse(el, { folder: 'svg', ext: '.svg' });
}
});
//最好是使用nextTick 保证获取到的是最新内容
onMounted(() => {
nextTick(() => {
//等待 Vue 完成本轮 DOM 更新后再执行代码,确保你访问到的是已经渲染进 DOM 的最终结果。
if (contentRef.value) {
twemoji.parse(contentRef.value, {
folder: 'svg',
ext: '.svg'
});
}
});
//如果带emoji的内容是通过异步加载或父组件传入,也可能不会生效
//Vue 渲染晚于 onMounted,那 Twemoji 在执行时内容还没出现在 DOM。
//解决方法:用 watch 监听变化后再解析
import { watch } from 'vue';
watch(content, () => {
nextTick(() => {
twemoji.parse(emojiBox.value, { folder: 'svg', ext: '.svg' });
});
});
//如果内容使用了 v-html,Vue 不会触发 DOM 更新钩子,
// 则twemoji.parse 不会自动处理
- 封装成全局自定义指令(最推荐)
使用 v-twemoji 指令来保证解析的是已经渲染完的 DOM 元素
// plugins/twemoji.js
import twemoji from 'twemoji';
export default {
install(app) {
app.directive('twemoji', {
mounted(el) {
twemoji.parse(el, { folder: 'svg', ext: '.svg' });
},
updated(el) {
twemoji.parse(el, { folder: 'svg', ext: '.svg' });
}
});
}
};
//在 App.vue、Layout.vue 或任意组件这样使用
<template>
<div v-twemoji>
Hello 😊🎉
</div>
</template>
- 部分应用
//部分应用
//vue
<template>
<div ref="emojiContainer">{{ message }}</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import twemoji from 'twemoji';
const emojiContainer = ref(null);
const message = '你好 Vue 😊❤️🎉';
onMounted(() => {
twemoji.parse(emojiContainer.value, {
folder: 'svg',
ext: '.svg'
});
});
</script>
//react
import React, { useEffect, useRef } from 'react';
import twemoji from 'twemoji';
const EmojiText = ({ text }) => {
const ref = useRef();
useEffect(() => {
twemoji.parse(ref.current, {
folder: 'svg',
ext: '.svg'
});
}, []);
return <div ref={ref}>{text}</div>;
};
export default function App() {
return <EmojiText text="Hello 😄🎉 from React!" />;
}
验证
使用浏览器开发者工具,检查是否 emoji 被替换为<img>
,src来自twemoji…cdn…地址