October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk6 min

How to Build a Real-Time Text Analyzer With Vanilla JavaScript

Build a browser-based text analyzer that updates as you type, with word and character counts that handle emoji, combining marks, and languages without spaces.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A real-time text analyzer needs one textarea, one input listener, and a few counting functions. The hard part is deciding what “character” and “word” mean, because JavaScript’s built-in string length does not match what a reader sees once emoji or non-Latin scripts are involved. This guide builds the analyzer with no libraries, explains which count to show and why, and shows how to measure responsiveness instead of assuming it. The result is a tool that updates as you type, with counts that are correct for the text you give it.

Decide what “character” means before you count

JavaScript strings are sequences of UTF-16 code units, so text.length counts code units. That number is useful for some purposes, but it is not the number of characters a person sees. Iterating with spread syntax, [...text], counts Unicode code points, which is closer but still splits some visible characters into several parts. Grapheme clusters are the unit that matches what a user perceives as one character, and Intl.Segmenter can produce them.

As an Amazon Associate I earn from qualifying purchases.

The example below uses a family emoji, which is a sequence of three person emoji joined by zero-width joiners, and a thumbs-up with a skin-tone modifier.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Method What it counts Family emoji 👨‍👩‍👧 Thumbs-up with skin tone 👍🏽 Use it for
text.length UTF-16 code units 8 4 Limits tied to JavaScript string storage
[...text].length Unicode code points 5 2 Counting Unicode scalar values
Intl.Segmenter with granularity: "grapheme" User-perceived characters 1 1 A “characters” count shown to people

For a counter labeled “Characters,” use grapheme segments. If you also want to show the raw length, label it explicitly as UTF-16 length so readers do not confuse the two numbers.

Choose a word-counting method

The simplest word count splits on whitespace. That works for English-style text where spaces separate words, and it gives wrong answers for languages whose words are not separated by spaces, such as Chinese, Japanese, and Thai. It also miscounts punctuation attached to words, and it counts fragments such as an isolated dash as words.

A locale-aware approach uses Intl.Segmenter with granularity: "word". Each segment carries an isWordLike flag, and counting only the segments where that flag is true excludes spaces and punctuation. MDN describes Intl.Segmenter as enabling locale-sensitive text segmentation, which is the reason to pass the page’s language rather than a hard-coded value. (Intl.Segmenter on MDN)

Check browser support before writing the code

MDN labels Intl.Segmenter as Baseline 2024 and says it has been available across the latest devices and browser versions since April 2024, while warning that it may not work on older devices or browsers. Decide your target browsers first. If you need older support, keep a fallback path that uses whitespace splitting and code-point counting, and label the results as approximate in that mode.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Check for support at startup: typeof Intl !== "undefined" && typeof Intl.Segmenter === "function".
  • If the check fails, fall back to text.trim().split(/s+/) for words and [...text].length for characters.
  • Show a short note when the fallback is active so readers know the counts are less precise.

Full reference details are in MDN’s internationalization guide. (MDN internationalization guide)

Build the page

Start with a labeled textarea and a results area. The labels should name each count precisely, because the counting rules are part of what the tool is telling the reader.

<label for="source">Your text</label>
<textarea id="source" rows="12"></textarea>

<dl>
  <dt>Words</dt><dd id="words">0</dd>
  <dt>Characters (user-perceived)</dt><dd id="chars">0</dd>
  <dt>UTF-16 length</dt><dd id="utf16">0</dd>
</dl>

Set the lang attribute on the html element so the segmenters can use the correct locale. Without it, the code falls back to English rules.

Count words and characters with Intl.Segmenter

Create each segmenter once, not on every keystroke. Constructing segmenters is more work than calling them, and reusing them keeps the update path lean.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const input = document.getElementById("source");
const wordsOut = document.getElementById("words");
const charsOut = document.getElementById("chars");
const utf16Out = document.getElementById("utf16");

const locale = document.documentElement.lang || "en";
const wordSegmenter = new Intl.Segmenter(locale, { granularity: "word" });
const graphemeSegmenter = new Intl.Segmenter(locale, { granularity: "grapheme" });

function countWords(text) {
  let count = 0;
  for (const segment of wordSegmenter.segment(text)) {
    if (segment.isWordLike) count++;
  }
  return count;
}

function countCharacters(text) {
  let count = 0;
  for (const _ of graphemeSegmenter.segment(text)) count++;
  return count;
}

function analyze(text) {
  wordsOut.textContent = countWords(text);
  charsOut.textContent = countCharacters(text);
  utf16Out.textContent = text.length;
}

Counting with a loop avoids building an array of segments just to read its length, which is a small but avoidable allocation on every update.

Connect the input event

The input event fires when the user changes the textarea’s content, so it is the right hook for typing. It does not fire when your script changes .value, so any code that sets the value programmatically must call the analysis function itself. (MDN input event reference)

input.addEventListener("input", () => analyze(input.value));

// Example: a "clear" button or a loaded sample
function loadSample(text) {
  input.value = text;
  analyze(input.value);
}

analyze(input.value);

Keep the interface responsive

For a small text box, calling analyze directly on every event is usually fine. When text grows or the page does more work per update, the risk is repeated DOM writes and layout work for every keystroke. A single pending animation frame avoids that: each event stores the latest text, and one callback runs before the next repaint.

let pendingText = null;
let frameId = 0;

function scheduleAnalysis(text) {
  pendingText = text;
  if (frameId) return;
  frameId = requestAnimationFrame(() => {
    frameId = 0;
    analyze(pendingText);
  });
}

input.addEventListener("input", () => scheduleAnalysis(input.value));

MDN describes requestAnimationFrame as scheduling a callback before the next repaint, generally at the display’s refresh rate. (requestAnimationFrame on MDN)

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What this change does and does not do

  • It reduces DOM updates to at most one per frame, no matter how many input events fire in that frame.
  • It does not make the counting itself cheaper. If the analysis is expensive, the work still runs once per frame.
  • It is not a timer. Browsers generally pause requestAnimationFrame in background tabs, so do not use it for work that must keep running while the page is hidden.

Ways to judge whether the update feels instant

MDN’s general performance guidance gives 50 ms as the budget for idle work, 16.7 ms for animation frames, and 50 to 200 ms as the range for responding to user input. The publication date of that guidance is not stated on the page. These are general targets, not measurements of this analyzer, and they do not guarantee how any particular person perceives a given update. (MDN Web performance guidance)

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Decide whether analysis belongs in a Web Worker

Move analysis to a Web Worker only when measurement shows that the main thread is blocked for long enough to affect typing or scrolling. Workers run scripts on a separate thread and communicate through messages, which adds copying and message overhead. MDN’s Web Workers guide covers the mechanics. (Using Web Workers on MDN) The sources used for this article do not establish an input-size threshold where a worker becomes worthwhile, so the decision should come from profiling your own text sizes on your target devices.

Measure before you claim speed

Timing a single call with performance.now() is enough to get a first number. Run it on a realistic input size and report the device, browser, input length, and whether you ran it once or several times.

const sample = "Your test paragraph goes here. ".repeat(2000);

const start = performance.now();
analyze(sample);
const elapsed = performance.now() - start;

console.log(`Analysis of ${sample.length} UTF-16 units took ${elapsed.toFixed(2)} ms`);

A single run is noisy. Repeat the measurement several times, discard the first run because of warm-up, and compare the median. Avoid calling the tool “lightning-fast” unless your own numbers, taken this way, support that wording.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Test the edge cases

Check each of these inputs before you ship. Each one exposes a different counting or update problem.

  • Empty input and whitespace-only input, which should show zero words and zero characters.
  • Short text and large text, such as several thousand paragraphs pasted at once.
  • Mixed scripts in one paragraph, for example English with Japanese or Thai.
  • Punctuation and dashes attached to words, such as “well-known” and “don’t”.
  • Combining marks, such as “é” written as “e” followed by a combining acute accent.
  • Emoji sequences with skin tones and zero-width joiners, such as the family and thumbs-up examples above.
  • A scripted value change through loadSample(), confirming the counts update without a user event.
  • Rapid typing and pasting, confirming the frame-based update shows the latest text.

If a case fails, compare the three counts side by side. The differences between UTF-16 length, code points, and graphemes usually explain the result.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.