3

About

Inspired by this question on SO Meta. Sometimes, when posting or editing posts with one or more Stack Snippets, the editor requires a lot of resources to continuously re-render the post preview. Typing is difficult when the textarea of a post does not respond to keystrokes immediately - the more snippets there are in a post, the less responsive the editor is.

For example, editing this post, which has 3 snippets, may result in something like this:

enter image description here

For higher-end machines, this laggy behavior doesn't always occur. I'm not sure what the conditions are, but it happens most frequently on Chrome when there are multiple tabs open. It might have something to do with one's GPU.

When it does happen, one must either suffer through this lag, or type up text in a separate editor like Notepad and then paste it into the textarea, or construct the snippets only once all the non-snippet text is typed. This script attempts to fix that, allowing for normal responsive typing all the time.

Platform

Designed to fix problems experienced in Chrome, but it may be useful on other browsers too. Should work on any browser that supports userscripts and ES6.

Operation

Technical details: by default, every keystroke in the textarea results in the post preview re-rendering. This script intercepts the key events in the capturing phase and uses stopPropagation() to ensure the event doesn't reach the textarea, which would result in a re-rendering. Instead, after a key event, a re-render function is put into a setTimeout which is cleared after each keystroke - so, it'll only run once the last keystroke was more than ~700ms ago. Moving the mouse will also trigger the re-render immediately. To illustrate, using the same post as before with the script enabled:

enter image description here

Again - to re-render, either move the mouse, or don't press a key for 700ms. (feel free to customize the timing)

Code

// ==UserScript==
// @name        Stack Snippet Lag Fixer
// @description Prevents post previews with stack snippets from continuously re-rendering
// @author      CertainPerformance
// @include     https://stackoverflow.com/questions/*
// @version     1.0
// @grant       none
// ==/UserScript==

// Milliseconds to wait after typing before a re-render:
const msDelay = 700;

const queueRender = (() => {
  // To avoid re-creating render functions every time an event is triggered,
  // use a persistent Map of elements to their render functions:
  const renderFnsMap = new Map();
  let timeout;

  const getRenderFn = (elm) => {
    if (renderFnsMap.has(elm)) {
      return renderFnsMap.get(elm);
    }
    const render = () => {
      elm.dispatchEvent(new Event('input'));
      clearTimeout(timeout);
      document.removeEventListener('mousemove', render);
    };
    renderFnsMap.set(elm, render);
    return render;
  };

  // queueRender function: Call render after msDelay or after a mousemove event,
  // whichever comes first:
  return (elm) => {
    const render = getRenderFn(elm);
    clearTimeout(timeout);
    timeout = setTimeout(render, msDelay);
    document.addEventListener('mousemove', render);
  };
})();

const hasSnippet = textarea => textarea.value.includes('<!-- begin snippet');

const handler = (event) => {
  // .wmd-input: question and answer textboxes, for both posting and editing
  // isTrusted check required so that the dispatched event above isn't caught
  const { target, isTrusted } = event;
  if (target.matches('.wmd-input') && isTrusted && hasSnippet(target)) {
    event.stopPropagation();
    queueRender(target);
  }
};

// All of these events trigger the re-render by default:
const eventNames = ['keypress', 'keydown', 'input'];
eventNames.forEach((eventName) => {
  // Must intercept the event in the capturing phase, before the event reaches the textarea:
  document.addEventListener(eventName, handler, true);
});

You must log in to answer this question.

Browse other questions tagged .