Thomas Zilliox
Intégrateur CSS Freelance à Lyon

Une expression régulière pour sélectionner tous les emojis

Je voulais créer une expression régulière qui me permette de sélectionner tous les emojis d'un texte. Ça n'a pas été facile, mais j'ai appris plein de choses en chemin, alors j'ai décidé de l'écrire ici !

Pourquoi ?

Dans un précédent article, j'avais montré pourquoi je change la police d'écriture juste pour les emojis dans mes pages web. Dans la pratique, cela revient à entourer tous les emojis présents dans mes articles par une balise <span class="u-emoji" />. J'aurais voulu automatiser ce travail et comme ça, être sûr de ne jamais oublier de le faire.

Ma première étape a été de me demander, « c'est quoi un emoji ? » Ça paraît très scolaire… Mais en fait ça n'a pas été inutile !

Les emojis simples

Des emojis, il y en un peu partout dans les tables unicode. Ils ne sont pas du tout les uns à côté des autres. 😭

Un extrait des tables unicode contenant des emojis

J'ai identifié 12 tables unicodes qui contiennent officiellement des caractères considérés comme des emojis :

 * Ponctuation générale (U+2000 - U+206F)
 * Symboles lettrés (U+2100 - U+214F)
 * Flèches (U+2190 - U+21FF)
 * Signes techniques divers (U+2300 - U+23FF)
 * Alphanumériques entourés (U+2460 - U+24FF)
 * Formes géométriques (U+25A0 - U+25FF)
 * Symboles divers (U+2600 - U+26FF)
 * Casseau (U+2700 - U+27BF)
 * Flèches – supplément – B (U+2900 - U+297F)
 * Symboles et flèches divers (U+2900 - U+297F)
 * Symboles et ponctuation CJC (U+3000 - U+303F)
 * Lettres et mois CJC entourés (U+3200 - U+32FF)

Pour ma première solution, j'ai essayé une expression régulière très large qui sélectionne tous les symboles. Les sélectionner un à un aurait été très long et assez peu maintenable. Malheureusement, cette première solution va inclure de nombreux symboles, ponctuations, et lettres étrangères. Donc à ne pas utiliser dans toutes les situations.


const emojiRegex = /[\u2000-\u32FF]/g;
console.log('basic emoji ☠'.match(emojiRegex));

Mais c'est pas fini…

Les emojis UTF-16

Tous les emojis ne rentrent pas dans les tables UTF-8. Pour écrire un caractère UTF-16 dans nos pages web, on utilise deux caractères. Un caractère de la « demi-zone haute d’indirection » (entre U+D800 à U+DBFF) puis un caractère de la « demi-zone basse d’indirection » (entre U+DC00 à U+DFFF).

Ainsi un drapeau noir 🏴 correspond au code UTF-16 « U+1F3F4 ». Dans une page web en UTF-8, cela s'écrit comme une paire de seizets : « U+D83C U+DFF4 ».


// UTF-16
console.log('🏴'.codePointAt().toString(16));
// One character is not always one character
console.log('🏴'.length);
// Which ones?
console.log(debugUtf8('🏴'));

Voici donc une expression régulière qui va sélectionner les emojis simple, ainsi que les emojis formés d'une paire de seizets :


const emojiRegex = /([\u2000-\u32FF]|[\ud83c-\ud83e][\udc00-\udfff])/g;
console.log('basic emoji ☠'.match(emojiRegex));
console.log('utf-16 emoji 🏴'.match(emojiRegex));

Le futur

J'en avais déjà parlé lors de mon dernier article sur les regex, les classes de caractères de propriétés permettent de sélectionner les caractères unicode en fonction de leur catégorie. C'est assez récent en JavaScript, depuis ES2018. Cela veut dire que seuls les navigateurs récents les supportent !

On peut donc sélectionner, en théorie, les emojis avec la classe de caractères \p{Emoji}.


const emojiRegex = /\p{Emoji}/gu;
console.log('basic emoji ☠'.match(emojiRegex));
console.log('utf-16 emoji 🏴'.match(emojiRegex));

Sauf que, surprise, cette syntaxe sélectionne aussi tous les nombre ! Ce n'est pas un bug d'implémentation, c'est écrit dans la spécification d'unicode. 😭

J'ai trouvé une solution en utilisant plutôt la catégorie « Extended Pictographic » qui s'écrit \p{ExtPict}.


const emojiRegex = /\p{ExtPict}/gu;
console.log('basic emoji ☠'.match(emojiRegex));
console.log('utf-16 emoji 🏴'.match(emojiRegex));

Les séquences d'emojis

Pour toutes les variantes des emojis, il existe une manière de composer plusieurs emojis pour en créer de nouveaux. Pour cela, on utilise le caractère « Zero Width Joiner U+200D ». On appelle alors ces nouveaux caractères des séquences ZWJ d'emojis, ou « Emoji ZWJ Sequence » en anglais.

Si on reprend l'exemple du drapeau noir, on peut le composer avec une tête de mort pour obtenir un drapeau pirate. 🏴‍☠


// Two emojis
console.log(debugUtf8('🏴‍☠'));
// Or more
console.log(debugUtf8('👩‍👩‍👦'));

Il existe un autre type de séquence pour les variations de couleurs de peau. Si on fait suivre un emoji par une teinte de peau, celui-ci s'adapte automatiquement. Ces emojis particuliers sont appelés des modificateurs d'emojis et peuvent être sélectionné avec la classe de caractère \p{EMod}.


// Default emoji
console.log(debugUtf8('👋'));
// Dark skin tone
console.log(debugUtf8('👋🏿'));
// Light skin tone
console.log(debugUtf8('👋🏼'));

Il faut donc aussi compléter nos expressions régulières pour prendre en compte ces deux cas.

const emojiRegex = /([\u2000-\u32FF]|[\ud83c-\ud83e][\udc00-\udfff])(\u200d?([\u2000-\u32FF]|[\ud83c-\ud83e][\udc00-\udfff]))*/g;
// or
const emojiRegexUnicode = /\p{ExtPict}(\u200d\p{ExtPict}|\p{EMod})*/gu;

const emojiRegex =  /\p{ExtPict}(\u200d\p{ExtPict}|\p{EMod})*/gu;
console.log('basic emoji ☠'.match(emojiRegex));
console.log('utf-16 emoji 🏴'.match(emojiRegex));
console.log('2-in-1 emoji 🏴‍☠'.match(emojiRegex));
console.log('3-in-1 emoji 👩‍👩‍👦'.match(emojiRegex));
console.log('skin tone emoji 👋🏿'.match(emojiRegex));

En résumé

Voici comment je fais pour rajouter automatiquement une balise autour des emojis présents dans mes articles :

function niceEmoji(text) {
  const emojiRegex = /(\p{ExtPict}(\u200d\p{ExtPict}|\p{EMod})*)/gu;
  return text.replace(emojiRegex, '<span class="u-emoji">$1</span>');
}

Et ça fonctionne ! J'ai retenu deux leçons avec cette histoire :

  1. Utiliser les expressions regulières modernes quand je le peux. Elles ont l'avantage d'être plus lisibles et de sélectionner plus précisément.
  2. Tester mes expressions régulières avec un jeu de données varié. Sans ça, je me serais peut-être satisfait de ma première solution.

Happy 💻, Thomas.

That's my face!

Thomas ZILLIOX

L'homme qui murmurait à l'oreille des chevrons.

Je développe, j'intègre, je forme ou je conseille sur les CSS. Besoin d'améliorer la maintenabilité ou les performances de vos projets ?