Os emoji são mais difíceis do que parecem: porque «basta retirá-los» não tem resposta numa linha
Publicado a 29/09/2025 · 13 min de leitura · Ferramentas de texto e idioma
Daniel Okonkwo — Programador front-end e redator de Tecnologia na OneKitly
Desempenho web · Formatos de ficheiro
Verificado a partir de 5 fontes
Não existe uma expressão regular simples para retirar emoji, porque não existe uma definição simples do que é um emoji. Uma só imagem no ecrã pode ser um ponto de código (😀 é U+1F600), um carácter base mais um seletor de variante invisível (❤️ é U+2764 U+FE0F), uma base mais um modificador de tom de pele (👍🏽 é U+1F44D U+1F3FD), uma cadeia de caracteres ligados por juntores de largura nula (👨👩👧👦 são sete pontos de código e onze unidades UTF-16), um par de indicadores regionais (🇵🇹 é U+1F1F5 U+1F1F9), ou uma sequência de etiquetas de sete pontos de código. As propriedades a que se recorre também não encaixam: \p{Emoji} reconhece os algarismos ASCII de 0 a 9 e ainda # e *, pelo que usá-la para limpar uma frase apaga os números — corremo-la e «2026 revenue up 40%» voltou como « revenue up %». \p{Extended_Pictographic} é a propriedade certa para as imagens mas não abrange tons de pele nem bandeiras. A regra que funciona é segmentar o texto em grupos de grafemas e retirar grupos inteiros. A mesma regra corrige o truncamento, onde cortar num índice de unidade de código pode partir uma família de quatro em duas pessoas sem relação ou deixar meio carácter que acaba em U+FFFD.
Um emoji visível pode valer um ponto de código ou catorze unidades UTF-16. Corremos três expressões regulares populares sobre uma frase real e cada uma falhou de maneira diferente; uma apagou os algarismos. Eis porquê, que propriedade Unicode responde a que pergunta, e a regra de grupos de grafemas que funciona mesmo.
Uma imagem, seis construções diferentes
Pergunte a um programa qual o comprimento de um emoji e obtém três respostas conforme a unidade. Medimos os mesmos oito emoji de três maneiras: em unidades de código UTF-16, que é o que .length devolve em JavaScript; em pontos de código; e em grupos de grafemas, que é o que um leitor chamaria um carácter. A contagem em grafemas é 1 para todos eles. As outras duas vão de 1 a 14.
O caso simples é um único ponto de código: 😀 é U+1F600, duas unidades UTF-16 porque está fora do plano multilingue básico. Depois complica-se. ❤️ é U+2764 seguido de U+FE0F, um seletor de variante invisível cuja única função é dizer «desenha o carácter anterior como imagem, não como símbolo». 1️⃣ são três pontos de código: o algarismo ASCII 1, o mesmo seletor e U+20E3 COMBINING ENCLOSING KEYCAP. 👍🏽 é um polegar para cima seguido de U+1F3FD, um modificador de tom que é ele próprio um carácter válido e aparece como um quadrado colorido quando fica sozinho.
Os casos difíceis são as sequências. 👨👩👧👦 são um homem, uma mulher, uma menina e um menino separados por três cópias de U+200D ZERO WIDTH JOINER: sete pontos de código, onze unidades UTF-16, uma imagem. 👩💻 é uma mulher mais um portátil ligados do mesmo modo, e 🏴☠️ é uma bandeira preta ligada a uma caveira com um seletor de variante no fim. As bandeiras funcionam de outra forma ainda: 🇵🇹 não é sequer um carácter de bandeira mas o par de indicadores regionais U+1F1F5 U+1F1F9, que são as letras P e T num alfabeto especial, e 🏴 é uma bandeira preta seguida de cinco caracteres de etiqueta invisíveis que soletram um código de subdivisão, mais um terminador — sete pontos de código, catorze unidades UTF-16.
A propriedade que parece certa e apaga os seus números
O JavaScript, e todo o motor de expressões regulares com escapes de propriedades Unicode, oferece \p{Emoji}. É a escolha evidente e é a errada. Emoji é uma propriedade de carácter que significa «este carácter pode participar num emoji», e os algarismos de 0 a 9 participam: são as bases das sequências de tecla. Também # e *, pela mesma razão.
A consequência é fácil de demonstrar. Pegue na frase «2026 revenue up 40%», que não contém emoji nenhum, e corra replace(/\p{Emoji}/gu, ""). Devolve « revenue up %». Todos os algarismos desapareceram; o sinal de por cento, que não é componente de emoji, sobrevive. A mesma expressão transforma «Order #7 shipped ✅» em «Order shipped ». Corra antes \p{Extended_Pictographic} sobre a primeira frase e ela volta intacta, que é o comportamento pretendido.
Mas Extended_Pictographic também não é uma resposta completa, porque significa «este carácter é um pictograma» e várias partes de um emoji não o são. Testámos cada parte: o modificador de tom U+1F3FD não é Extended_Pictographic, nem os indicadores regionais que formam uma bandeira. Corra Extended_Pictographic sozinha sobre uma frase com 🇵🇹 e 👍🏽 e a bandeira sobrevive inteira enquanto o polegar desaparece e deixa o seu tom para trás, como um quadrado colorido solitário.
Emoji_Presentation é a propriedade que responde a «isto vai aparecer como imagem?»
Alguns caracteres aparecem por omissão como imagem a cores e outros por omissão como texto monocromático. Essa distinção é exatamente o que Emoji_Presentation regista. Sondámos um conjunto de caracteres e o corte é limpo: 😀, ⌚, 👍 e 🀄 têm Emoji_Presentation, portanto desenham-se como imagem sem mais nada. ❤, ☺, ▶, ✔, ©, ® e ™ não têm; sozinhos desenham-se como glifos de texto na fonte circundante.
É para isso que existe o seletor de variante. U+FE0F é o pedido de desenhar o carácter anterior como emoji, e é ele que transforma ❤ em ❤️ e ▶ em ▶️. É também invisível, sem peso na representação mental do leitor, e um carácter real na cadeia: daí o U+FE0F órfão que deixa uma regex que retira o pictograma mas não o seu seletor. Medimo-lo: «done ❤️» sem os Extended_Pictographic volta como seis pontos de código: d, o, n, e, espaço, U+FE0F. O resultado parece limpo e não está.
Três regex ingénuas, três falhas diferentes
Pegámos numa frase — «Shipping to 🇵🇹 today 👨👩👧👦 — 40% off, thanks 👍🏽 #7 ❤️», que são 63 unidades UTF-16, 55 pontos de código e 46 grupos de grafemas — e corremos as três abordagens habituais, mais uma quarta construída sobre grupos de grafemas.
A abordagem por intervalo de pontos de código, /[\u{1F300}-\u{1FAFF}]/gu, é a que aparece em metade das respostas na internet. Retirou os membros da família e o polegar, mas a bandeira sobreviveu porque os indicadores regionais ficam em U+1F1E6–U+1F1FF, abaixo do intervalo; o coração sobreviveu porque U+2764 está muito abaixo; e os três juntores de largura nula que seguravam a família ficaram na cadeia como lixo invisível.
A abordagem \p{Emoji} retirou a bandeira, a família e o polegar, e também o 40 e o 7 de #7, deixando «% off» e um cardinal nu. A abordagem Extended_Pictographic manteve a bandeira intacta, retirou o polegar mas não o seu tom, e deixou os juntores e o seletor de variante. Três linhas plausíveis, três tipos distintos de saída errada, nenhuma a anunciar um problema.
A quarta abordagem segmenta o texto em grupos de grafemas com Intl.Segmenter e depois descarta um grupo inteiro assim que ele contém um indicador regional, um carácter com Emoji_Presentation, ou um pictograma seguido do seletor de variante. Sobre a mesma frase devolveu «Shipping to today — 40% off, thanks #7 »: algarismos conservados, bandeira removida como unidade, nenhum juntor órfão, nenhum seletor solto. O único artefacto são os espaços duplos onde estavam os emoji, que um colapso de espaços resolve numa segunda passagem.
O truncamento é onde isto morde em produção
Cortar uma cadeia em N unidades é de longe a forma mais comum de partir emoji, porque é a mais barata de escrever e funciona em todas as cadeias de teste que um programador anglófono se lembra de experimentar. Pegue em «Great work 👨👩👧👦 thanks», 29 unidades UTF-16. Cortar em 12, 15 ou 18 deixa a cadeia a terminar em meio carácter alto solto, o que não é texto válido. Passe-o a UTF-8 e torna-se nos três bytes EF BF BD, o carácter de substituição: o leitor vê um losango preto. isWellFormed() devolve false para essa cadeia em JavaScript, o que é uma maneira barata de apanhar o erro num teste.
A falha mais subtil é pior, porque produz texto válido que significa outra coisa. Cortar a mesma cadeia em 16 unidades dá «Great work 👨👩»: a família de quatro tornou-se um homem e uma mulher com um juntor entre eles, o que não é uma sequência definida, e por isso aparece como duas pessoas separadas. Nada está malformado. Nada lança erro. A mensagem mostra agora uma imagem diferente da que foi enviada.
Iterar por ponto de código com o operador de propagação resolve o problema do meio carácter e não o outro: tomar os 14 primeiros pontos de código da mesma cadeia continua a dar «Great work 👨👩». Só os grupos de grafemas acertam, porque são a unidade que o algoritmo de segmentação define como um carácter percebido pelo utilizador. Tomar os 14 primeiros grupos de grafemas dá «Great work 👨👩👧👦 t»: a família inteira conservada como uma unidade, que é o que um leitor esperaria de uma pré-visualização de catorze caracteres.
Contar emoji põe o mesmo problema que retirá-los
Pegue na cadeia «🇵🇹 👨👩👧👦 👍🏽 ❤️ 😀». Um humano conta cinco emoji. Consultámos seis métodos diferentes e obtivemos seis respostas diferentes: 27 unidades UTF-16, 18 pontos de código, 9 grupos de grafemas, 5 grupos de grafemas não brancos, 7 correspondências de \p{Extended_Pictographic}, 9 de \p{Emoji_Presentation} e 10 de \p{Emoji}. Só uma vale 5.
As contagens por propriedade são altas pelas razões exatas acima: a família contribui com quatro pictogramas, o polegar com um e o seu tom com outro sob Emoji_Presentation, e a bandeira com dois. Se o seu produto aplica uma regra do tipo «no máximo três emoji por publicação», a regra só vale o que vale o contador por trás, e o contador tem de ser o que dá razão ao leitor: os grupos de grafemas que contêm um carácter emoji.
O que faz antes a remoção do não-ASCII, e porque não é o mesmo trabalho
Um atalho tentador é ficar só com ASCII, com o argumento de que todos os emoji estão fora. Todos estão, e quase todo o resto do mundo também. Corremos replace(/[^\x00-\x7F]/g, "") sobre «Café ☕ — résumé sent 👍» e devolveu «Caf rsum sent »: a chávena e o polegar desapareceram, e também o é de Café, os dois acentos de résumé e o travessão. Numa frase francesa, espanhola, portuguesa, alemã ou italiana isto destrói palavras vulgares, não enfeites.
As duas operações pertencem a ferramentas diferentes por uma razão. Retirar emoji significa «tira as imagens e deixa a língua em paz». Retirar o não-ASCII significa «reduz isto aos 128 caracteres que um sistema antigo sabe tratar», que é um trabalho de transliteração com perdas reais, a escolher de propósito. Recorrer ao segundo quando queria o primeiro é uma das formas mais silenciosas de partir um produto multilingue.
| Emoji | Como é construído | Unidades UTF-16 | Pontos de código | Grupos de grafemas |
|---|---|---|---|---|
| 😀 | Um único ponto de código, U+1F600 | 2 | 1 | 1 |
| ❤️ | Base U+2764 mais seletor de variante U+FE0F | 2 | 2 | 1 |
| 1️⃣ | Algarismo 1, seletor U+FE0F, tecla U+20E3 | 3 | 3 | 1 |
| 👍🏽 | Base U+1F44D mais modificador de tom U+1F3FD | 4 | 2 | 1 |
| 🇵🇹 | Dois indicadores regionais, U+1F1F5 U+1F1F9 | 4 | 2 | 1 |
| 👨👩👧👦 | Quatro pessoas ligadas por três juntores U+200D | 11 | 7 | 1 |
| 🏴 | Bandeira preta mais cinco caracteres de etiqueta mais um terminador | 14 | 7 | 1 |
Perguntas frequentes
- Qual é a regex mais curta que retira emoji corretamente?
- Não existe, e essa é a resposta honesta. Uma regex reconhece pontos de código, e um emoji é uma sequência de pontos de código cujas fronteiras são definidas por um algoritmo de segmentação, não por um padrão. Dá para chegar perto com uma alternância longa que cubra pictogramas, séries de juntores, pares de indicadores regionais e modificadores, mas então está a reimplementar mal o algoritmo. Segmente primeiro, filtre grupos depois: são três linhas e está certo.
- Porque é que retirar emoji deixou um carácter invisível?
- Porque retirou a imagem e não os seus acompanhantes. Os dois suspeitos habituais são U+FE0F, o seletor de variante que pede a apresentação como emoji, e U+200D, o juntor de largura nula que liga as partes de uma sequência. Nenhum é pictograma, portanto um filtro baseado em pictogramas deixa-os. São invisíveis no ecrã mas reais na cadeia: contam para os limites de caracteres, partem as comparações de igualdade e vão surpreender quem comparar o texto a seguir.
- Porque é que o mesmo emoji fica diferente noutro telemóvel?
- A cadeia transporta a identidade, não o desenho. Cada fabricante distribui a sua própria fonte de emoji, portanto U+1F600 é o sorriso de um desenhador numa plataforma e o de outro noutra. Quando um aparelho não tem glifo para uma sequência, recorre a desenhar as partes: uma família que aparece como uma imagem no seu telemóvel pode surgir como quatro pessoas separadas num aparelho mais antigo, que é o recuo correto para uma sequência com juntores, não um erro.
- Como deve um limite de caracteres contar um emoji?
- Conte grupos de grafemas se o limite existe para o leitor, e conte bytes se existe para o armazenamento. As duas respostas diferem por um fator de onze num emoji de família, portanto escolha a que corresponde à razão do limite e diga qual é ao lado do campo. O que nunca deve fazer é aplicar um comprimento UTF-16 em silêncio, porque quem escrever três bandeiras bate num limite de 30 caracteres aos doze caracteres visíveis, sem explicação.
- © e ™ são emoji?
- Por propriedades, sim e não ao mesmo tempo, que é precisamente a confusão de que trata este artigo. Sondámos ambos: cada um tem Emoji e Extended_Pictographic, mas nenhum tem Emoji_Presentation, pelo que ambos aparecem como texto normal a não ser que um seletor de variante peça uma imagem. Isso significa que um filtro baseado em Extended_Pictographic apaga o símbolo de copyright do seu rodapé. Se só quer as imagens a cores, teste Emoji_Presentation ou um pictograma imediatamente seguido de U+FE0F.
- Retirar os emoji muda o significado de uma mensagem?
- Muitas vezes, o que é um argumento para os retirar nas fronteiras de um sistema e não no meio. Os emoji transportam tom, negação e por vezes todo o conteúdo de uma resposta. Retirá-los é adequado quando o destino não os consegue apresentar — uma exportação em texto simples, uma coluna de base de dados antiga, um relatório impresso, um nome de ficheiro — e inadequado quando o texto ainda vai ser lido por uma pessoa. Retire à saída e guarde o original.
Artigos que podem interessar-lhe
Todos os guias →Ferramentas relacionadas
Fontes
- 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
Detetaste um erro neste artigo?