EmojiEmoji
Từ tiếng Nhật (絵文字) có nghĩa là 'ký tự hình ảnh' — các ký hiệu đồ họa nhỏ dùng trong giao tiếp kỹ thuật số để diễn đạt ý tưởng, cảm xúc và sự vật. Versioning and Support Detection
UnicodeUnicode
Tiêu chuẩn mã hóa ký tự phổ quát gán một số duy nhất cho mỗi ký tự trong tất cả hệ thống chữ viết và bộ ký hiệu, bao gồm cả emoji. releases a new emoji set roughly every year. Emoji 15.0 introduced 31 new characters; Emoji 16.0 (released in late 2024) added more. Platforms (iOS, Android, Windows) update their emoji fonts with operating system releases, meaning users on older OS versions cannot see newer emoji.
When a platform does not support an emoji, it renders as a tofu box (☐) or as the text description in brackets. Understanding how to detect and handle this gracefully keeps your UI from looking broken.
How Emoji Versions Work
Emoji versions are distinct from Unicode versions, though they track together. Key milestones:
| Emoji VersionEmoji Version Phiên bản phát hành mà trong đó một emoji được giới thiệu lần đầu, theo chu kỳ phát hành hàng năm kể từ Emoji 4.0 (2016). |
Unicode Version | Year | Notable additions |
|---|---|---|---|
| 1.0 | 8.0 | 2015 | 722 baseline emoji |
| 3.0 | 9.0 | 2016 | 🤣 🤞 🦊 🥑 |
| 5.0 | 10.0 | 2017 | 🤩 🧠 🦷 🥗 |
| 11.0 | 11.0 | 2018 | 🥰 🦸 🧩 🥳 |
| 12.0 | 12.0 | 2019 | 🧑🦯 🦦 🧆 🥻 |
| 13.0 | 13.0 | 2020 | 🥲 🫀 🦬 🪲 |
| 14.0 | 14.0 | 2021 | 🫣 🫶 🪸 🩻 |
| 15.0 | 15.0 | 2022 | 🫨 🪼 🩵 🫷 |
| 15.1 | 15.1 | 2023 | 🙂↕️ 🙂↔️ 🍋🟩 👁️🗨️ |
| 16.0 | 16.0 | 2024 | 🫎 🐦🔥 |
Each emoji in emoji-test.txtemoji-test.txt carries a version annotation in a comment:
Tệp Unicode chính thức liệt kê tất cả chuỗi emoji cùng trạng thái đủ điều kiện, điểm mã và tên ngắn CLDR của chúng.
1F600 ; fully-qualified # 😀 E1.0 grinning face
1FAE8 ; fully-qualified # 🫨 E15.0 shaking face
Platform Support Lag
After Unicode releases an emoji version, platforms deploy support through OS updates:
| Emoji Version | iOS support | Android support | Windows support |
|---|---|---|---|
| 15.0 | iOS 16.4 | Android 13 | Windows 11 22H2 |
| 15.1 | iOS 17.4 | Android 14 | Windows 11 23H2 |
| 16.0 | iOS 18.2 | Android 15 | Windows 11 24H2 |
This means users on iOS 16 cannot display Emoji 15.0 characters — they see a tofu box instead.
Detecting Emoji Support in the Browser
Canvas Pixel Comparison Method
The most reliable client-side detection renders the emoji on a canvas and checks whether the pixels differ from a known unsupported character:
const emojiSupportCache = new Map();
function supportsEmoji(emoji) {
if (emojiSupportCache.has(emoji)) {
return emojiSupportCache.get(emoji);
}
const canvas = document.createElement('canvas');
canvas.width = 2;
canvas.height = 2;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.font = '14px Arial';
// Baseline: a character definitely not supported (use a PUA code point)
ctx.fillText('\uDB40\uDC00', 0, 14); // Tag character fallback
const baseline = ctx.getImageData(0, 0, 1, 1).data.slice(0, 3);
ctx.clearRect(0, 0, 2, 2);
ctx.fillText(emoji, 0, 14);
const tested = ctx.getImageData(0, 0, 1, 1).data.slice(0, 3);
// If color data differs, the emoji rendered as a glyph
const supported = !baseline.every((v, i) => v === tested[i]);
emojiSupportCache.set(emoji, supported);
return supported;
}
// Check specific emoji versions
const checks = {
emoji12: "🥲", // E12.0 — 2020
emoji13: "🫀", // E13.0 — 2021
emoji14: "🫣", // E14.0 — 2022
emoji15: "🫨", // E15.0 — 2022
emoji16: "", // E16.0 — 2024
};
for (const [version, char] of Object.entries(checks)) {
console.log(`${version} (${char}): ${supportsEmoji(char) ? '✅ supported' : '❌ not supported'}`);
}
Detecting ZWJZero Width Joiner (ZWJ)
Ký tự Unicode vô hình (U+200D) dùng để ghép nhiều emoji thành một emoji tổng hợp, chẳng hạn kết hợp người và vật thể thành emoji nghề nghiệp. Sequence Support
ZWJ sequences can partially render even when the combined glyph is not supported — the individual components show instead. To detect whether a ZWJ sequence renders as one glyph vsVariation Selector (VS)
Các ký tự Unicode (VS-15 U+FE0E và VS-16 U+FE0F) xác định xem một ký tự được hiển thị dưới dạng văn bản (đơn sắc) hay emoji (có màu).. multiple:
function supportsZWJSequence(zwjEmoji) {
const canvas = document.createElement('canvas');
canvas.width = 80;
canvas.height = 20;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.font = '14px Arial';
// Measure the ZWJ sequence
const zwjWidth = ctx.measureText(zwjEmoji).width;
// Measure the components separately (without ZWJ)
const parts = [...zwjEmoji].filter(c => c !== '\u200D');
const partsWidth = ctx.measureText(parts.join('')).width;
// If widths are similar, ZWJ sequence is not supported (renders as parts)
// If ZWJ is narrower, it's a single glyph
return zwjWidth < partsWidth * 0.9;
}
console.log(supportsZWJSequence("👨💻")); // true on modern platforms
console.log(supportsZWJSequence("🧑🦲")); // may vary by platform/OS version
Detecting Emoji Support in Python
Server-side detection is less reliable since you don't have access to the client's font stack. The best approach is to use the client's User-Agent to infer emoji support:
from dataclasses import dataclass
import re
@dataclass
class EmojiVersionInfo:
min_emoji_version: float
os_name: str
def detect_emoji_version(user_agent: str) -> EmojiVersionInfo:
"""Approximate emoji version support from User-Agent string."""
# iOS version → emoji version mapping
if "iPhone OS" in user_agent or "iPad; CPU OS" in user_agent:
match = re.search(r'OS (\d+)_', user_agent)
if match:
ios_ver = int(match.group(1))
version_map = {18: 16.0, 17: 15.1, 16: 15.0, 15: 14.0, 14: 13.0}
for ios, emoji in version_map.items():
if ios_ver >= ios:
return EmojiVersionInfo(emoji, "iOS")
return EmojiVersionInfo(11.0, "iOS")
# Android version → emoji version (via Noto updates)
if "Android" in user_agent:
match = re.search(r'Android (\d+)', user_agent)
if match:
android_ver = int(match.group(1))
version_map = {15: 16.0, 14: 15.1, 13: 15.0, 12: 13.1, 11: 13.0}
for android, emoji in version_map.items():
if android_ver >= android:
return EmojiVersionInfo(emoji, "Android")
return EmojiVersionInfo(11.0, "Android")
# Windows 11 generally has recent emoji support
if "Windows NT 10.0" in user_agent:
return EmojiVersionInfo(15.0, "Windows")
return EmojiVersionInfo(11.0, "Unknown")
Graceful Degradation Strategies
1. Image Fallback for Specific New Emoji
If you use a specific new emoji that may not be supported, provide an image fallback:
<!-- Fallback: emoji → image for unsupported platforms -->
<picture>
<source media="(min-resolution: 1dpi)" srcset="/emoji/shaking-face.png">
<!-- Modern platforms show this text, which triggers the image source on older -->
<img src="/emoji/shaking-face.png" alt="Shaking face" width="20" height="20">
</picture>
Or use JavaScript to replace unsupported emoji dynamically:
const NEW_EMOJI = ["🫨", "🪼", "🩵", "🫷", "🫸"]; // E15.0+
document.addEventListener('DOMContentLoaded', () => {
NEW_EMOJI.forEach(emoji => {
if (!supportsEmoji(emoji)) {
// Replace with TwemojiTwemoji
Bộ emoji mã nguồn mở ban đầu được Twitter tạo ra, cung cấp các tài nguyên emoji SVG và PNG có thể dùng trong bất kỳ dự án nào. image
const imgUrl = getTwemojiUrl(emoji);
document.querySelectorAll('*').forEach(el => {
if (el.childNodes.length === 1 && el.childNodes[0].nodeType === 3) {
el.innerHTML = el.innerHTML.replace(
emoji,
`<img src="${imgUrl}" alt="${emoji}" style="height:1em;vertical-align:-0.1em;">`
);
}
});
}
});
});
2. Twemoji for Universal Rendering
Twemoji (from Twitter/X) renders all emoji as SVG/PNG images from a CDN, ensuring consistent display across all platforms regardless of OS emoji version:
import twemoji from 'twemoji';
// Replace emoji with images sitewide
twemoji.parse(document.body, {
folder: 'svg',
ext: '.svg',
base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/',
className: 'twemoji',
});
.twemoji {
height: 1em;
width: 1em;
margin: 0 0.05em 0 0.1em;
vertical-align: -0.1em;
}
3. Emoji Version Tag in Your Data
When storing user-generated emoji content, record the minimum emoji version required:
import regex
# Map emoji to their version (from emoji-data.txt parsing)
EMOJI_VERSION_MAP: dict[str, float] = {
"🫨": 15.0,
"🪼": 15.0,
"🥲": 13.0,
"🤣": 3.0,
"😀": 1.0,
# ... full dataset
}
def required_emoji_version(text: str) -> float:
"""Return the minimum emoji version needed to display this text."""
found = regex.findall(r'\X', text)
max_version = 0.0
for grapheme in found:
# Use the first code point of the cluster for lookup
version = EMOJI_VERSION_MAP.get(grapheme, 0.0)
max_version = max(max_version, version)
return max_version
Explore More on EmojiFYI
- Compare emoji rendering across platforms: Compare Tool
- Inspect emoji version data: Sequence Analyzer
- Unicode emoji version history: Glossary
- Query emoji version data via API: API Reference