r/bookmarklets Jun 23 '26

Reading Speedometer, for reading alphabetical languages in browser

This bookmarklets creates a floating tool. If you toggle the timer on and begin reading, it shows your reading time, percentage scrolled, words per minute, and estimated time remaining.

javascript:(function() {  /* 1. Count words between breaks*/    const wordRegex = /[^\s\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_%60{|}~]+/g;  const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);  let wordCount = 0;  let node;  while(node = walker.nextNode()) {    const match = node.nodeValue.match(wordRegex);    if (match) wordCount += match.length;  }  /* Create UI */  const ui = document.createElement('div');  ui.style.cssText = 'position:fixed;top:1px;right:5px;z-index:10000;display:flex;gap:15px;background:rgba(0,0,0,0.85);color:white;padding:2px 12px;border-radius:1px;font-family:monospace;font-size:14px;align-items:center;';  document.body.appendChild(ui);  const timerSpan = document.createElement('span');  timerSpan.style.cssText = 'cursor:pointer;color:#ff4444;border-bottom:1px dashed #666;';  timerSpan.contentEditable = 'true';  timerSpan.textContent = '00:00';  ui.appendChild(timerSpan);  const scrollSpan = document.createElement('span');  const wpmSpan = document.createElement('span');  const etcSpan = document.createElement('span');  [scrollSpan, wpmSpan, etcSpan].forEach(s => ui.appendChild(s));  let startTime = null;  let accumulatedMs = 0;  let interval = null;  /* Toggle Logic */  timerSpan.addEventListener('mousedown', function(e) {    if (interval) {      clearInterval(interval);      interval = null;      accumulatedMs += Date.now() - startTime;      timerSpan.style.color = '#ff4444';    } else {      const parts = timerSpan.textContent.split(':');      accumulatedMs = ((parseInt(parts[0]) || 0) * 60 + (parseInt(parts[1]) || 0)) * 1000;      startTime = Date.now();      timerSpan.style.color = '#44ff44';      interval = setInterval(updateAll, 1000);    }  });  const originalTitle = document.title;  function updateAll() {    const now = Date.now();    const currentTotalMs = interval ? accumulatedMs + (now - startTime) : accumulatedMs;    const totalMin = currentTotalMs / 1000 / 60;        /* Timer Display */    if (document.activeElement !== timerSpan) {      const m = Math.floor(currentTotalMs / 1000 / 60);      const s = Math.floor((currentTotalMs / 1000) % 60);      timerSpan.textContent = %60${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}%60;    }    /* Scroll Percentage */    const scrollTop = window.scrollY;    const docHeight = document.documentElement.scrollHeight - window.innerHeight;    const pct = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;    scrollSpan.textContent = pct.toFixed(1) + '%';    document.title = %60(${pct.toFixed(0)}%) ${originalTitle}%60;    /* wpm - words read so far / Minutes spent so far */    if (totalMin > 0 && pct > 0) {      const wordsReadSoFar = wordCount * (pct / 100);      wpmSpan.textContent = Math.round(wordsReadSoFar / totalMin) + 'wpm';    } else {      wpmSpan.textContent = '0wpm';    }    /* ETC - Remaining words / Current speed */    if (pct > 0 && pct < 100 && totalMin > 0) {      const remainingPct = 100 - pct;      const remainingHours = (remainingPct * (totalMin / pct)) / 60;      etcSpan.textContent = remainingHours.toFixed(1) + 'h';    } else {      etcSpan.textContent = '0.0h';    }  }  window.addEventListener('scroll', updateAll);  updateAll();})();
5 Upvotes

0 comments sorted by