2

Using the Intersection Observer web API to improve performance

 3 years ago
source link: https://www.growingwiththeweb.com/2018/01/intersection-observer.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

For the uninitiated, xterm.js is an open source project that I work on which enables the creation of terminal emulators for the web. VS Code for example uses xterm.js to drive it’s integrated terminal.

xtermjs-full.png

Problem#

Some consumers of xterm.js spawn multiple terminal instances at once. Ever since the recent performance improvements which moved rendering over to use canvas, each terminal still consumes CPU/GPU time to render terminals even when they’re hidden. This is a particular problem if there are background tasks running like long running or watch scripts. Let’s dig a little deeper into solving this in a nice way.

Solution#

In VS Code we know exactly when the terminal is being hidden and displayed so this problem could be solved by simply adding new APIs Terminal.pauseRenderer and Terminal.resumeRenderer. The issue with this approach though is that then every single consumer of xterm.js needs to trigger these new APIs at the right time in order to reap the benefits.

It turns out there’s an easier way using the relatively new Intersection Observer API. Intersection Observer works by observing an element within some viewport and fires a callback when a certain percentage of it is visible and when it drops below that percentage.

var options = {
  root: document.querySelector('#scrollArea'),
  rootMargin: '0px',
  threshold: 0.5
};
var observer = new IntersectionObserver(callback, options);
var target = document.querySelector('#item');
observer.observe(target);

function callback(entries) {
  // Fires when #item goes above or below 50% visible inside #scrollArea
  entries.forEach(entry => {
    console.log('intersection change', entry);
  });
}

The use case for xterm.js is relatively simple as that threshold is 0%; we want to fire when either any part of the terminal is visible or when the terminal is completely hidden from view. We also want to use the browser’s viewport so there’s no need to set root or rootMargin.

// Basic feature detection
if ('IntersectionObserver' in window) {
  // Create the IntersectionObserver and observe the terminal
  const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), {threshold: 0});
  observer.observe(this._terminal.element);
}

The actual change callback will pause or resume the rendering and force a full refresh of the terminal if needed.

public onIntersectionChange(entry: IntersectionObserverEntry): void {
    this._isPaused = entry.intersectionRatio === 0;
    if (!this._isPaused && this._needsFullRefresh) {
      this._terminal.refresh(0, this._terminal.rows - 1);
    }
  }
}

Whenever an operation that could change the terminal viewport occurs while paused, the flag this._needsFullRefresh is set to true so a full refresh can be done when the renderer is resumed.

private _runOperation(operation: (layer: IRenderLayer) => void): void {
  if (this._isPaused) {
    this._needsFullRefresh = true;
  } else {
    this._renderLayers.forEach(l => operation(l));
  }
}

And that’s it! Thanks to Intersection Observer, xterm.js can save some CPU/GPU cycles and battery with minimal effort and is now standard behavior which just works out of the box. If you’re interested in more details here is the pull request where the change was made.

Support#

At the time of writing IntersectionObserver is supported by all major browser excluding Safari. There is also a polyfill available if you need better support.

Share this page#

More posts tagged JavaScript#


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK