Contagem de palavras e caracteres: emoji, CJK e limites de envio

Adicione um contador próprio da aplicação sem substituir o documento a cada tecla. Este guia inclui um módulo testado e um exemplo completo que valida um limite de 200 caracteres, mantendo intactos os rascunhos que ultrapassarem o limite.
Defina o que significa um caractere
O comprimento de uma string JavaScript conta unidades UTF-16. Um emoji de família ou uma letra com acento combinado pode conter várias unidades e parecer um caractere. Este exemplo conta grafemas com Intl.Segmenter, inclui espaços e quebras de linha e normaliza CRLF ou CR para LF.
Palavras são os segmentos identificados como palavras para o idioma explícito do documento. Isso não promete igualdade com Microsoft Word, a estatística interna do editor, cobrança de SMS ou regras de redes sociais. Separe essas políticas e explique aos autores qual unidade seu limite usa.
Baixe o módulo de contagem
Baixe o módulo de métricas de texto. Ele recebe texto simples, não HTML. Leia o documento com getPlainText() em vez de contar o HTML de getHTMLCode(). Tags não são palavras; limites de parágrafo e marcadores gerados pela extração precisam de testes explícitos.
O módulo exige suporte à segmentação no navegador ou servidor. Se não houver suporte, informa um erro em vez de mudar silenciosamente para divisão por espaços, inadequada para chinês e japonês.
Conecte o contador à verificação de envio
Disponibilize os arquivos do editor e o módulo pela sua aplicação. O exemplo apenas verifica as condições do formulário; não envia nem salva conteúdo. A contagem é agrupada durante a digitação e refeita imediatamente no envio. Uma rejeição mantém o rascunho intacto.
<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css">
<script src="/richtexteditor/rte.js"></script>
<script src="/richtexteditor/plugins/all_plugins.js"></script>
<style>body{font:16px/1.6 system-ui,sans-serif;max-width:960px;margin:24px auto;padding:0 16px}button{min-height:44px;padding:8px 16px}</style>
<h1>Word and character counter</h1>
<form id="message-form">
<div style="max-width:100%;overflow-x:auto"><div id="editor"></div></div>
<p id="counter"></p>
<p>Limit: 200 graphemes, including spaces and line breaks. This demo does not save.</p>
<button type="submit">Check submission</button>
<p id="result" role="status" aria-live="polite"></p>
</form>
<script type="module">
import { countText } from "/js/text-metrics.mjs";
const editor = new RichTextEditor("#editor", { toolbar: "full", height: "320px", showStatistics: false });
editor.setHTMLCode("<p>Write a short note.</p>");
const LOCALE = "en"; // Choose the document language, not necessarily the UI language.
const LIMIT = 200;
const counter = document.getElementById("counter");
const result = document.getElementById("result");
function updateCount() {
try {
const metrics = countText(editor.getPlainText(), LOCALE);
counter.textContent = metrics.words + " words · " + metrics.characters +
" / " + LIMIT + " characters (graphemes)";
return metrics;
} catch {
counter.textContent = "Counter unavailable. Check locale and Intl.Segmenter support.";
return null;
}
}
let timer, checkedText = null;
editor.attachEvent("change", () => {
if (editor.getPlainText() !== checkedText) result.textContent = "";
clearTimeout(timer);
timer = setTimeout(updateCount, 200);
});
document.getElementById("message-form").addEventListener("submit", event => {
event.preventDefault();
clearTimeout(timer);
const metrics = updateCount(); // Do not trust a delayed displayed count.
checkedText = editor.getPlainText();
if (!metrics) { result.textContent = "Cannot validate this count."; return; }
if (metrics.characters > LIMIT) {
result.textContent = "Too long. Shorten the draft; your content is still here.";
editor.focus();
return;
}
result.textContent = "Within limit. Ready for your server-side validation.";
// Add your authenticated save here. This demo sends no request.
});
updateCount();
// Exposed only so you can inspect this standalone demo in developer tools:
window.counterDemo = { editor, updateCount };
</script>
Aplique a mesma política no servidor
Repita a validação no servidor usando o documento recebido e uma política de extração acordada. Não confie na contagem enviada pelo cliente nem em outro campo de texto simples. Verifique separadamente autorização, sanitização HTML e tamanho da requisição em bytes: 200 grafemas não garantem um HTML pequeno.
Você pode reutilizar o módulo em uma aplicação Node.js moderna após extrair texto simples. Alinhe normalização e idioma e use testes compartilhados: dados Unicode e de segmentação podem variar entre versões do navegador e servidor. Para limites contratuais, escolha e versione uma política oficial do servidor.
Teste idiomas e métodos de entrada
- Verifique texto vazio, espaços, quebras de parágrafo e CRLF.
- Experimente um emoji de família, uma bandeira e um acento combinado. Não os corte para respeitar o limite.
- Use documentos chineses e japoneses reais, sem presumir que espaços separam palavras.
- Cole conteúdo acima do limite e envie imediatamente, antes de o contador atualizar. A verificação deve rejeitar e preservar o rascunho.
- Apague texto suficiente e envie novamente. Teste também composição IME e desfazer na sua integração.
O contador não aplica regras de conteúdo obrigatório nem decide se um documento só com imagens é válido. Reescrever o HTML ou cancelar cada tecla pode prejudicar seleção, histórico e entrada IME.
Continue com os guias relacionados
Veja extração de texto simples, validação de formulários, salvamento automático no servidor e políticas de contagem de palavras.