DEV Community

Cover image for Building an RSVP speed-reading Chrome extension: a red letter, pauses and very long words
Сергей С
Сергей С

Posted on

Building an RSVP speed-reading Chrome extension: a red letter, pauses and very long words

I read a lot on screen and mostly skim. So I built a small Chrome extension, ListoRead, that shows any text one word at a time in a single spot (RSVP, rapid serial visual presentation). The idea is about fifty years old and looks trivial: take a word, show it, wait, next. In practice almost all the work is in details you only notice when you read with it yourself. Here they are, with code. Plain JavaScript, no libraries, Manifest V3.

Where the eye should land

Centering each word feels jittery: short and long words start in different places and your eye keeps hunting. The fix is the optimal recognition point (ORP), a letter slightly left of the middle. ListoRead colors it red and pins it to the same spot for every word.

/** ORP index (0-based) for a word with `length` letters. */
function getOrpIndex(length) {
  if (length <= 1) return 0;
  if (length <= 5) return 1;
  if (length <= 9) return 2;
  if (length <= 13) return 3;
  return 4;
}
🔥 ข่าวด่วน: acdeaa/building an rsvp speed reading chrome extension a red letter pauses and very long words j - Kapook Update 2025 🔥 ข่าวด่วน: acdeaa/building an rsvp speed reading chrome extension a red letter pauses and very long words j - Kapook Update 2025

Two gotchas:

  • Punctuation doesn't count. In "Hello," the quotes and comma must not shift the red letter, so the index is computed on letters only and edge punctuation goes to a prefix/suffix.
  • Letters are graphemes, not code units. Emoji and combining accents break str[i]. Words are split with Intl.Segmenter, otherwise you sometimes color half a character.

Rhythm matters more than speed

A flat interval feels like a metronome. Each word gets a multiplier on the base interval:

const COMMA_FACTOR = 1.5;      // , ; : — –
const SENTENCE_FACTOR = 2.2;   // . ! ? …
const PARAGRAPH_FACTOR = 2.8;  // line break after the word
const SHORT_WORD_FACTOR = 0.9; // 1–3 letters
const LONG_WORD_FROM = 8;      // letters; from here each letter adds LONG_WORD_STEP
const LONG_WORD_STEP = 0.08;
const LONG_WORD_MAX = 1.6;
🔥 ข่าวด่วน: acdeaa/building an rsvp speed reading chrome extension a red letter pauses and very long words j - Kapook Update 2025 🔥 ข่าวด่วน: acdeaa/building an rsvp speed reading chrome extension a red letter pauses and very long words j - Kapook Update 2025

The final delay is the length factor times the punctuation factor. One more small thing: after Play, speed ramps from 60% to 100% over the first 8 words, so the first word doesn't fly past before your eyes focus.

Very long words

English rarely has this problem; German and Russian have it all the time. Words of 14+ letters are split with a hyphen. Existing hyphens are used first; otherwise the cut is chosen near the middle, preferring vowel|consonant, then consonant|consonant, then vowel|vowel, with parts at least 4 and 3 letters long. URLs, emails, paths and long numbers are never split.

Finding the article when nothing is selected

The main flow is: open an article, press Alt+R. No Readability, just a short heuristic:

  1. Take visible <p> elements longer than 40 characters.
  2. Skip anything inside nav, aside, footer, form, comments, sidebars, share and related blocks.
  3. Add each paragraph's length to its parent in full and to its grandparent at half.
  4. The highest-scoring element wins; collect headings, paragraphs, lists and quotes from it in order.
  5. Under 200 characters means "no article here", and the extension says so.

It works well on news sites, blogs and docs, and fails on pages split into dozens of tiny containers. There you select the text by hand.

Getting back to where you stopped

RSVP's honest weakness is that you can't glance back. So pause shows the whole sentence and you can click any word to jump to it. When you close the reader, the page scrolls to the word where you stopped and highlights it. The reader text and the page text differ (hidden nodes, markup, removed junk), so the match is searched within ~80 words of the proportional position, preferring candidates whose neighbours also match.

The highlight uses the CSS Custom Highlight API: a Range registered in CSS.highlights. No elements are added to the page, nothing breaks the site's layout, and it disappears after 15 seconds.

Isolation and permissions

The reader renders in a closed Shadow DOM. Permissions are activeTab, scripting, contextMenus and storage, with no host_permissions: the extension touches a page only when you start reading. There is no server; settings live in chrome.storage.local.

Try it

ListoRead is free, has 11 UI languages and works in Chrome, Edge, Brave and other Chromium browsers.

I'd love feedback on the article-detection heuristic and the word-splitting rules. If you know sites where detection breaks, tell me in the comments.

This post was drafted with AI assistance and reviewed by the author.

Top comments (0)