Les emoji sont plus difficiles qu'ils n'en ont l'air : pourquoi « il suffit de les retirer » n'a pas de réponse en une ligne
Publié le 29/09/2025 · 14 min de lecture · Outils texte & langage
Daniel Okonkwo — Développeur front-end et rédacteur Tech chez OneKitly
Performance web · Formats de fichiers
Vérifié à partir de 5 sources
Il n'existe pas d'expression régulière simple pour retirer les emoji, parce qu'il n'existe pas de définition simple de ce qu'est un emoji. Une seule image à l'écran peut être un point de code (😀 vaut U+1F600), un caractère de base plus un sélecteur de variante invisible (❤️ vaut U+2764 U+FE0F), une base plus un modificateur de teinte (👍🏽 vaut U+1F44D U+1F3FD), une chaîne de caractères liés par des jointeurs de largeur nulle (👨👩👧👦 fait sept points de code et onze unités UTF-16), une paire d'indicateurs régionaux (🇵🇹 vaut U+1F1F5 U+1F1F9), ou une séquence d'étiquettes de sept points de code. Les propriétés auxquelles on pense ne s'alignent pas non plus : \p{Emoji} reconnaît les chiffres ASCII 0 à 9 ainsi que # et *, si bien que l'utiliser pour nettoyer une phrase supprime les nombres — nous l'avons lancé et « 2026 revenue up 40% » est revenu en « revenue up % ». \p{Extended_Pictographic} est la bonne propriété pour les images mais ne couvre ni les teintes ni les drapeaux. La règle qui marche consiste à découper le texte en groupes de graphèmes, puis à retirer des groupes entiers. La même règle répare la troncature, où couper à un indice d'unité de code peut scinder une famille de quatre en deux personnes sans rapport, ou laisser un demi-caractère qui devient U+FFFD.
Un emoji visible peut valoir un point de code ou quatorze unités UTF-16. Nous avons lancé trois expressions régulières répandues sur une vraie phrase : chacune a cassé différemment, l'une a supprimé les chiffres. Voici pourquoi, quelle propriété Unicode répond à quelle question, et la règle de groupes de graphèmes qui marche vraiment.
Une image, six constructions différentes
Demande à un programme la longueur d'un emoji et tu obtiens trois réponses selon l'unité. Nous avons mesuré les mêmes huit emoji de trois façons : en unités de code UTF-16, ce que renvoie .length en JavaScript ; en points de code ; et en groupes de graphèmes, ce qu'un lecteur appellerait un caractère. Le compte en graphèmes vaut 1 pour chacun d'eux. Les deux autres vont de 1 à 14.
Le cas simple est un point de code unique : 😀 vaut U+1F600, deux unités UTF-16 car il est hors du plan multilingue de base. Ensuite cela se corse. ❤️ vaut U+2764 suivi de U+FE0F, un sélecteur de variante invisible dont le seul rôle est de dire « dessine le caractère précédent en image, pas en symbole ». 1️⃣ fait trois points de code : le chiffre ASCII 1, le même sélecteur, et U+20E3 COMBINING ENCLOSING KEYCAP. 👍🏽 est un pouce levé suivi de U+1F3FD, un modificateur de teinte qui est lui-même un caractère valide et s'affiche en carré coloré quand il reste seul.
Les cas difficiles sont les séquences. 👨👩👧👦 est un homme, une femme, une fille et un garçon séparés par trois copies de U+200D ZERO WIDTH JOINER : sept points de code, onze unités UTF-16, une image. 👩💻 est une femme plus un ordinateur portable joints de la même façon, et 🏴☠️ un drapeau noir joint à une tête de mort avec un sélecteur de variante à la fin. Les drapeaux fonctionnent encore autrement : 🇵🇹 n'est pas du tout un caractère de drapeau mais la paire d'indicateurs régionaux U+1F1F5 U+1F1F9, soit les lettres P et T dans un alphabet spécial, et 🏴 est un drapeau noir suivi de cinq caractères d'étiquette invisibles épelant un code de subdivision, puis un terminateur — sept points de code, quatorze unités UTF-16.
La propriété qui semble juste et supprime tes nombres
JavaScript, et tout moteur d'expressions régulières qui gère les échappements de propriétés Unicode, propose \p{Emoji}. C'est le choix évident, et c'est le mauvais. Emoji est une propriété de caractère qui signifie « ce caractère peut participer à un emoji », et les chiffres 0 à 9 y participent : ce sont les bases des séquences de touches. # et * aussi, pour la même raison.
La conséquence se démontre facilement. Prends la phrase « 2026 revenue up 40% », qui ne contient aucun emoji, et lance replace(/\p{Emoji}/gu, ""). Elle renvoie « revenue up % ». Tous les chiffres ont disparu ; le signe pour cent, qui n'est pas un composant d'emoji, survit. La même expression transforme « Order #7 shipped ✅ » en « Order shipped ». Lance plutôt \p{Extended_Pictographic} sur la première phrase et elle revient intacte, ce qui est le comportement voulu.
Mais Extended_Pictographic n'est pas non plus une réponse complète, car elle signifie « ce caractère est un pictogramme » et plusieurs parties d'un emoji n'en sont pas. Nous avons testé chaque partie : le modificateur de teinte U+1F3FD n'est pas Extended_Pictographic, et les indicateurs régionaux qui composent un drapeau non plus. Lance Extended_Pictographic seule sur une phrase contenant 🇵🇹 et 👍🏽 : le drapeau survit entier tandis que le pouce disparaît en laissant sa teinte derrière lui, en carré coloré solitaire.
Emoji_Presentation est la propriété qui répond à « cela s'affichera-t-il en image »
Certains caractères s'affichent par défaut en image colorée, d'autres par défaut en texte monochrome. C'est exactement cette distinction que consigne Emoji_Presentation. Nous avons sondé un ensemble de caractères et la coupure est nette : 😀, ⌚, 👍 et 🀄 possèdent Emoji_Presentation et se dessinent donc en image sans rien ajouter. ❤, ☺, ▶, ✔, ©, ® et ™ ne l'ont pas ; laissés seuls, ils se dessinent en glyphes de texte dans la police environnante.
C'est à cela que sert le sélecteur de variante. U+FE0F est la demande d'afficher le caractère précédent en emoji, et c'est lui qui transforme ❤ en ❤️ et ▶ en ▶️. Il est aussi invisible, sans poids dans la représentation du lecteur, et pourtant un vrai caractère dans la chaîne : d'où le U+FE0F orphelin que laisse une regex qui retire le pictogramme mais pas son sélecteur. Nous l'avons mesuré : « done ❤️ » débarrassé des Extended_Pictographic revient en six points de code : d, o, n, e, espace, U+FE0F. Le résultat a l'air propre et ne l'est pas.
Trois regex naïves, trois échecs différents
Nous avons pris une phrase — « Shipping to 🇵🇹 today 👨👩👧👦 — 40% off, thanks 👍🏽 #7 ❤️ », soit 63 unités UTF-16, 55 points de code et 46 groupes de graphèmes — et lancé les trois approches habituelles, plus une quatrième bâtie sur les groupes de graphèmes.
L'approche par plage de points de code, /[\u{1F300}-\u{1FAFF}]/gu, est celle que l'on trouve dans la moitié des réponses en ligne. Elle a retiré les membres de la famille et le pouce, mais le drapeau a survécu car les indicateurs régionaux se trouvent en U+1F1E6–U+1F1FF, sous la plage ; le cœur a survécu car U+2764 est bien en dessous ; et les trois jointeurs de largeur nulle qui tenaient la famille sont restés dans la chaîne, déchets invisibles.
L'approche \p{Emoji} a retiré le drapeau, la famille et le pouce, et aussi le 40 et le 7 de #7, laissant « % off » et un dièse nu. L'approche Extended_Pictographic a gardé le drapeau intact, retiré le pouce mais pas sa teinte, et laissé les jointeurs et le sélecteur de variante. Trois lignes plausibles, trois sortes distinctes de sortie fausse, aucune n'annonçant de problème.
La quatrième approche découpe le texte en groupes de graphèmes avec Intl.Segmenter, puis écarte un groupe entier dès qu'il contient un indicateur régional, un caractère doté d'Emoji_Presentation, ou un pictogramme suivi du sélecteur de variante. Sur la même phrase elle a renvoyé « Shipping to today — 40% off, thanks #7 » — chiffres conservés, drapeau parti d'un bloc, aucun jointeur orphelin, aucun sélecteur égaré. Le seul artefact est le doublement des espaces là où étaient les emoji, qu'un repli des blancs corrige en une deuxième passe.
La troncature, c'est là que ça mord en production
Couper une chaîne à N unités est de loin la façon la plus courante de casser des emoji, parce que c'est la moins chère à écrire et qu'elle marche sur toutes les chaînes de test qu'un développeur anglophone pense à essayer. Prends « Great work 👨👩👧👦 thanks », 29 unités UTF-16. Couper à 12, 15 ou 18 termine la chaîne sur un demi-caractère haut isolé, ce qui n'est pas du texte valide. Passe-le en UTF-8 et il devient les trois octets EF BF BD, le caractère de remplacement : le lecteur voit un losange noir. isWellFormed() renvoie false pour cette chaîne en JavaScript, ce qui est un moyen bon marché d'attraper le bug dans un test.
L'échec le plus subtil est pire, car il produit du texte valide qui veut dire autre chose. Couper la même chaîne à 16 unités donne « Great work 👨👩 » — la famille de quatre est devenue un homme et une femme reliés par un jointeur, ce qui n'est pas une séquence définie : cela s'affiche donc en deux personnes séparées. Rien n'est malformé. Rien ne lève d'erreur. Le message montre maintenant une autre image que celle envoyée.
Itérer par point de code avec l'opérateur de décomposition règle le problème du demi-caractère, pas l'autre : prendre les 14 premiers points de code de la même chaîne donne toujours « Great work 👨👩 ». Seuls les groupes de graphèmes font juste, car ils sont l'unité que l'algorithme de segmentation définit comme un caractère perçu par l'utilisateur. Prendre les 14 premiers groupes de graphèmes donne « Great work 👨👩👧👦 t » — toute la famille conservée d'un bloc, ce qu'un lecteur attendrait d'un aperçu de quatorze caractères.
Compter les emoji pose le même problème que les retirer
Prends la chaîne « 🇵🇹 👨👩👧👦 👍🏽 ❤️ 😀 ». Un humain compte cinq emoji. Nous avons interrogé six méthodes différentes et obtenu six réponses différentes : 27 unités UTF-16, 18 points de code, 9 groupes de graphèmes, 5 groupes de graphèmes non blancs, 7 correspondances de \p{Extended_Pictographic}, 9 de \p{Emoji_Presentation} et 10 de \p{Emoji}. Une seule vaut 5.
Les comptes par propriété sont élevés pour les raisons exactes ci-dessus : la famille apporte quatre pictogrammes, le pouce un et sa teinte un autre sous Emoji_Presentation, et le drapeau deux. Si ton produit applique une règle du type « trois emoji maximum par publication », la règle ne vaut que ce que vaut le compteur derrière, et le compteur doit être celui qui donne raison au lecteur : les groupes de graphèmes contenant un caractère emoji.
Ce que fait plutôt le retrait du non-ASCII, et pourquoi ce n'est pas le même travail
Un raccourci tentant consiste à ne garder que l'ASCII, au motif que tous les emoji sont en dehors. Tous les emoji le sont, et la majeure partie du reste du monde aussi. Nous avons lancé replace(/[^\x00-\x7F]/g, "") sur « Café ☕ — résumé sent 👍 » : le résultat est « Caf rsum sent ». La tasse et le pouce ont disparu, mais aussi le é de Café, les deux accents de résumé et le tiret cadratin. Sur une phrase française, espagnole, portugaise, allemande ou italienne, cela détruit des mots ordinaires, pas de la décoration.
Les deux opérations relèvent d'outils différents pour une bonne raison. Retirer les emoji veut dire « enlève les images et laisse la langue tranquille ». Retirer le non-ASCII veut dire « réduis ceci aux 128 caractères qu'un vieux système sait traiter », ce qui est un travail de translittération à pertes réelles, à choisir en connaissance de cause. Prendre le second quand on voulait le premier est l'une des façons les plus discrètes de casser un produit multilingue.
| Emoji | Comment il est construit | Unités UTF-16 | Points de code | Groupes de graphèmes |
|---|---|---|---|---|
| 😀 | Un point de code unique, U+1F600 | 2 | 1 | 1 |
| ❤️ | Base U+2764 plus sélecteur de variante U+FE0F | 2 | 2 | 1 |
| 1️⃣ | Chiffre 1, sélecteur U+FE0F, touche U+20E3 | 3 | 3 | 1 |
| 👍🏽 | Base U+1F44D plus modificateur de teinte U+1F3FD | 4 | 2 | 1 |
| 🇵🇹 | Deux indicateurs régionaux, U+1F1F5 U+1F1F9 | 4 | 2 | 1 |
| 👨👩👧👦 | Quatre personnes liées par trois jointeurs U+200D | 11 | 7 | 1 |
| 🏴 | Drapeau noir plus cinq caractères d'étiquette plus un terminateur | 14 | 7 | 1 |
Questions fréquentes
- Quelle est la regex la plus courte qui retire correctement les emoji ?
- Il n'y en a pas, et c'est la réponse honnête. Une regex reconnaît des points de code, et un emoji est une suite de points de code dont les frontières sont définies par un algorithme de segmentation, pas par un motif. On peut s'en approcher avec une longue alternance couvrant pictogrammes, suites de jointeurs, paires d'indicateurs régionaux et modificateurs, mais on réimplémente alors mal l'algorithme. Segmente d'abord, filtre les groupes ensuite — trois lignes, et c'est juste.
- Pourquoi le retrait des emoji a-t-il laissé un caractère invisible ?
- Parce que tu as retiré l'image et pas ses compagnons. Les deux suspects habituels sont U+FE0F, le sélecteur de variante qui demande le rendu emoji, et U+200D, le jointeur de largeur nulle qui relie les parties d'une séquence. Aucun n'est un pictogramme, donc un filtre fondé sur les pictogrammes les laisse. Ils sont invisibles à l'écran mais réels dans la chaîne : ils comptent dans les limites de caractères, ils cassent les comparaisons d'égalité, et ils surprendront la prochaine personne qui comparera le texte.
- Pourquoi le même emoji est-il différent sur un autre téléphone ?
- La chaîne porte l'identité, pas le dessin. Chaque éditeur livre sa propre police d'emoji, si bien que U+1F600 est le sourire d'un dessinateur sur une plateforme et celui d'un autre ailleurs. Quand un appareil n'a pas de glyphe pour une séquence, il retombe sur le dessin des parties : une famille qui s'affiche en une image sur ton téléphone peut apparaître en quatre personnes séparées sur un appareil plus ancien, ce qui est le repli correct pour une séquence à jointeurs, pas un bug.
- Comment une limite de caractères doit-elle compter un emoji ?
- Compte des groupes de graphèmes si la limite existe pour le lecteur, et des octets si elle existe pour le stockage. Les deux réponses diffèrent d'un facteur onze sur un emoji de famille : choisis celle qui correspond à la raison de la limite, et dis laquelle à côté du champ. Ce qu'il ne faut jamais faire, c'est appliquer une longueur UTF-16 en silence, car un utilisateur qui tape trois drapeaux atteindra une limite de 30 caractères à douze caractères visibles, sans explication.
- © et ™ sont-ils des emoji ?
- Selon les propriétés, oui et non à la fois — c'est précisément la confusion dont parle cet article. Nous avons sondé les deux : chacun possède Emoji et Extended_Pictographic, mais aucun n'a Emoji_Presentation, si bien que tous deux s'affichent en texte ordinaire sauf si un sélecteur de variante réclame une image. Un filtre bâti sur Extended_Pictographic supprimera donc le symbole de copyright de ton pied de page. Si tu ne veux que les images colorées, teste Emoji_Presentation ou un pictogramme immédiatement suivi de U+FE0F.
- Retirer les emoji change-t-il le sens d'un message ?
- Souvent, ce qui plaide pour ne les retirer qu'aux frontières d'un système, pas au milieu. Les emoji portent le ton, la négation et parfois tout le contenu d'une réponse. Les retirer est justifié quand la destination ne sait pas les afficher — un export en texte brut, une colonne de base de données ancienne, un rapport imprimé, un nom de fichier — et injustifié quand le texte sera encore lu par une personne. Retire à la sortie, garde l'original stocké.
Articles qui pourraient t'intéresser
Tous les guides →Outils similaires
Sources
- Unicode Consortium — UTS #51: Unicode Emoji — emoji properties, ZWJ sequences, modifiers and flags
- Unicode Consortium — UAX #29: Unicode Text Segmentation — grapheme cluster boundaries
- Mozilla — MDN Web Docs — Unicode character class escapes in regular expressions
- Mozilla — MDN Web Docs — Intl.Segmenter
- Ecma International — ECMA-402: ECMAScript Internationalization API Specification
Tu as repéré une erreur dans cet article ?