From 47a50acc55151a75ec1f21b8bb3a86b3c40b0c06 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Dec 2017 15:16:13 -0800 Subject: [PATCH] Use IntersectionObserver to turn off renderer when hidden Fixes #880 --- src/renderer/Renderer.ts | 52 +++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index ea373e5e..adfe22c1 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -21,6 +21,8 @@ export class Renderer extends EventEmitter implements IRenderer { private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; + private _isPaused: boolean = false; + private _needsFullRefresh: boolean = false; public colorManager: ColorManager; public dimensions: IRenderDimensions; @@ -52,6 +54,20 @@ export class Renderer extends EventEmitter implements IRenderer { actualCellHeight: null }; this._devicePixelRatio = window.devicePixelRatio; + + // Detect whether IntersectionObserver is detected and enable renderer pause + // and resume based on terminal visibility if so + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), {threshold: 0}); + observer.observe(this._terminal.element); + } + } + + public onIntersectionChange(entry: IntersectionObserverEntry): void { + this._isPaused = entry.intersectionRatio === 0; + if (!this._isPaused && this._needsFullRefresh) { + this._terminal.refresh(0, this._terminal.rows - 1); + } } public onWindowResize(devicePixelRatio: number): void { @@ -72,7 +88,11 @@ export class Renderer extends EventEmitter implements IRenderer { l.reset(this._terminal); }); - this._terminal.refresh(0, this._terminal.rows - 1); + if (this._isPaused) { + this._needsFullRefresh = true; + } else { + this._terminal.refresh(0, this._terminal.rows - 1); + } return this.colorManager.colors; } @@ -136,7 +156,11 @@ export class Renderer extends EventEmitter implements IRenderer { this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions, didCharSizeChange)); // Force a refresh - this._terminal.refresh(0, this._terminal.rows - 1); + if (this._isPaused) { + this._needsFullRefresh = true; + } else { + this._terminal.refresh(0, this._terminal.rows - 1); + } this.emit('resize', { width: this.dimensions.canvasWidth, @@ -149,27 +173,35 @@ export class Renderer extends EventEmitter implements IRenderer { } public onBlur(): void { - this._renderLayers.forEach(l => l.onBlur(this._terminal)); + this._runOperation(l => l.onBlur(this._terminal)); } public onFocus(): void { - this._renderLayers.forEach(l => l.onFocus(this._terminal)); + this._runOperation(l => l.onFocus(this._terminal)); } public onSelectionChanged(start: [number, number], end: [number, number]): void { - this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end)); + this._runOperation(l => l.onSelectionChanged(this._terminal, start, end)); } public onCursorMove(): void { - this._renderLayers.forEach(l => l.onCursorMove(this._terminal)); + this._runOperation(l => l.onCursorMove(this._terminal)); } public onOptionsChanged(): void { - this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); + this._runOperation(l => l.onOptionsChanged(this._terminal)); } public clear(): void { - this._renderLayers.forEach(l => l.reset(this._terminal)); + this._runOperation(l => l.reset(this._terminal)); + } + + private _runOperation(operation: (layer: IRenderLayer) => void): void { + if (this._isPaused) { + this._needsFullRefresh = true; + } else { + this._renderLayers.forEach(l => operation(l)); + } } /** @@ -179,6 +211,10 @@ export class Renderer extends EventEmitter implements IRenderer { * @param {number} end The end row. */ public queueRefresh(start: number, end: number): void { + if (this._isPaused) { + this._needsFullRefresh = true; + return; + } this._refreshRowsQueue.push({ start: start, end: end }); if (!this._refreshAnimationFrame) { this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));