Ir para o conteúdo
OneKitly

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

Daniel OkonkwoProgramador front-end e redator de Tecnologia na OneKitly

Desempenho web · Formatos de ficheiro

Verificado a partir de 5 fontes

Ver perfil
Em resumo

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.

Unidades UTF-16
Um emoji visível, medido de três maneiras — Node 26, Intl.Segmenter com granularity "grapheme"
EmojiComo é construídoUnidades UTF-16Pontos de códigoGrupos de grafemas
😀Um único ponto de código, U+1F600211
❤️Base U+2764 mais seletor de variante U+FE0F221
1️⃣Algarismo 1, seletor U+FE0F, tecla U+20E3331
👍🏽Base U+1F44D mais modificador de tom U+1F3FD421
🇵🇹Dois indicadores regionais, U+1F1F5 U+1F1F9421
👨‍👩‍👧‍👦Quatro pessoas ligadas por três juntores U+200D1171
🏴󠁧󠁢󠁳󠁣󠁴󠁿Bandeira preta mais cinco caracteres de etiqueta mais um terminador1471
Remover emojisRemova todos os emojis e pictogramas do seu texto.Experimentar a ferramenta

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
GuiaFormatar números para seis línguas: separadores, moeda e o regresso ao valor1 234,56 e 1,234.56 são o mesmo número, e confundi-los muda o valor que um leitor lê. Corremos Intl.NumberFormat para as seis localizações do site e imprimimos cada separador — incluindo o invisível que o francês usa — e depois medimos porque parseFloat não consegue desfazer nada disso.ExplicaçãoContar palavras é ambíguo, e cada ferramenta responde de forma diferenteUma contagem de palavras é uma definição, não uma medição. Contámos o mesmo parágrafo de quatro maneiras e obtivemos 25, 28, 33 e 38; depois contámos 50 000 caracteres de prosa vulgar e obtivemos acordo dentro de 4,5 %. A diferença deve-se inteiramente a compostos, algarismos e URL.TutorialFiltrar linhas por um padrão sem linha de comandosIsto é o grep para quem não usa grep, com uma diferença importante: a procura é uma subcadeia literal, pelo que uma expressão regular a sério devolve uma caixa vazia e nenhum erro. Cada afirmação foi verificada executando a ferramenta.ExplicaçãoOnde uma linha pode quebrar: o algoritmo Unicode por trás de cada parágrafo ajustado«Quebrar nos espaços» falha na maioria dos sistemas de escrita. O UAX #14 dá a cada caráter uma classe de quebra de linha; procurámos as nossas no Unicode 17.0.0 e executámos uma implementação conforme sobre espaços inquebráveis, hifenes condicionais, espaços de largura zero, URL, japonês e tailandês.ExplicaçãoRetirar os acentos estraga a pesquisa — até o fazer dos dois ladosRebater os diacríticos é um passo de normalização, e a normalização só funciona quando a mesma função corre sobre o índice e sobre a consulta. NFC contra NFD com os pontos de código à vista, e as letras — ø, ł, ß, œ, ı — que saem do rebatimento intactas.GuiaConverter entre formatos de lista sem perder dados: as regras de aspas que ninguém lêPassar de uma lista com quebras de linha para uma lista com vírgulas é trivial até um item conter uma vírgula. As regras de aspas da RFC 4180, porque um campo CSV pode conter uma quebra de linha, porque as folhas de cálculo europeias usam o ponto e vírgula, e o que um item vazio faz à ida e volta — cada caso executado e impresso.

Ferramentas relacionadas

Fontes

Detetaste um erro neste artigo?