From da216aa581adb7088dc832462c4b015d4810a0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Tue, 9 Feb 2016 09:59:19 +0100 Subject: [PATCH] Improve handling of view updates and detection of invisible documents. Switch to the new HTML5 Page Visibility API. --- src/core/PaperScript.js | 4 ++++ src/dom/DomEvent.js | 53 +++++++++++++---------------------------- src/item/Project.js | 13 +++++----- src/view/CanvasView.js | 10 ++++---- src/view/View.js | 22 +++++++++++------ 5 files changed, 48 insertions(+), 54 deletions(-) diff --git a/src/core/PaperScript.js b/src/core/PaperScript.js index 3b4bfcf9..7950a647 100644 --- a/src/core/PaperScript.js +++ b/src/core/PaperScript.js @@ -482,6 +482,10 @@ Base.exports.PaperScript = (function() { }); if (res.onFrame) view.setOnFrame(res.onFrame); + // Automatically request an update at the end. This is only needed + // if the script does not actually produce anything yet, and the + // used canvas contains previous content. + view.requestUpdate(); } return compiled; } diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js index 8411fba0..568dc130 100644 --- a/src/dom/DomEvent.js +++ b/src/dom/DomEvent.js @@ -72,49 +72,28 @@ DomEvent.requestAnimationFrame = new function() { var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'), requested = false, callbacks = [], - focused = true, timer; - DomEvent.add(window, { - focus: function() { - focused = true; - }, - blur: function() { - focused = false; - } - }); - function handleCallbacks() { - // Checks all installed callbacks for element visibility and - // execute if needed. - for (var i = callbacks.length - 1; i >= 0; i--) { - var entry = callbacks[i], - func = entry[0], - el = entry[1]; - if (!el || (PaperScope.getAttribute(el, 'keepalive') == 'true' - || focused) && DomElement.isInView(el)) { - // Only remove from the list once the callback was called. This - // could take a long time based on visibility. But this way we - // are sure to keep the animation loop running. - callbacks.splice(i, 1); - func(); - } - } - if (nativeRequest) { - if (callbacks.length) { - // If we haven't processed all callbacks yet, we need to keep - // the loop running, as otherwise it would die off. - nativeRequest(handleCallbacks); - } else { - requested = false; - } - } + // Make a local references to the current callbacks array and set + // callbacks to a new empty array, so it can collect the functions for + // the new requests. + var functions = callbacks; + callbacks = []; + // Call the collected callback functions. + for (var i = 0, l = functions.length; i < l; i++) + functions[i](); + // Now see if the above calls have collected new callbacks. Keep + // requesting new frames as long as we have callbacks. + requested = nativeRequest && callbacks.length; + if (requested) + nativeRequest(handleCallbacks); } - return function(callback, element) { + return function(callback) { // Add to the list of callbacks to be called in the next animation // frame. - callbacks.push([callback, element]); + callbacks.push(callback); if (nativeRequest) { // Handle animation natively. We only need to request the frame // once for all collected callbacks. @@ -125,7 +104,7 @@ DomEvent.requestAnimationFrame = new function() { } else if (!timer) { // Install interval timer that checks all callbacks. This // results in faster animations than repeatedly installing - // timout timers. + // timeout timers. timer = setInterval(handleCallbacks, 1000 / 60); } }; diff --git a/src/item/Project.js b/src/item/Project.js index cfd0b75a..f2bd7bea 100644 --- a/src/item/Project.js +++ b/src/item/Project.js @@ -87,13 +87,14 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ */ _changed: function(flags, item) { if (flags & /*#=*/ChangeFlag.APPEARANCE) { - // Never draw changes right away. Simply mark the project as "dirty" - // and request a view update through window.requestAnimationFrame() - // (via view.requestUpdate()), which handles the smooth updates. - this._needsUpdate = true; var view = this._view; - if (view && !view._requested && view._autoUpdate) - view.requestUpdate(); + if (view) { + // Never draw changes right away. Simply mark view as "dirty" + // and request an update through view.requestUpdate(). + view._needsUpdate = true; + if (!view._requested && view._autoUpdate) + view.requestUpdate(); + } } // Have project keep track of changed items so they can be iterated. // This can be used for example to update the SVG tree. Needs to be diff --git a/src/view/CanvasView.js b/src/view/CanvasView.js index c7572656..ad98603f 100644 --- a/src/view/CanvasView.js +++ b/src/view/CanvasView.js @@ -56,6 +56,8 @@ var CanvasView = View.extend(/** @lends CanvasView# */{ this._pixelRatio = deviceRatio / backingStoreRatio; } View.call(this, project, canvas); + // We can't be sure the canvas is clear + this._needsUpdate = true; }, remove: function remove() { @@ -128,14 +130,14 @@ var CanvasView = View.extend(/** @lends CanvasView# */{ * @return {Boolean} {@true if the view was updated} */ update: function() { - var project = this._project; - if (!project || !project._needsUpdate) + if (!this._needsUpdate) return false; - var ctx = this._context, + var project = this._project, + ctx = this._context, size = this._viewSize; ctx.clearRect(0, 0, size.width + 1, size.height + 1); project.draw(ctx, this._matrix, this._pixelRatio); - project._needsUpdate = false; + this._needsUpdate = false; return true; } }); diff --git a/src/view/View.js b/src/view/View.js index 86a892f7..44919113 100644 --- a/src/view/View.js +++ b/src/view/View.js @@ -119,6 +119,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ this._itemEvents = { native: {}, virtual: {} }; // Do not set _autoUpdate on Node.js by default: this._autoUpdate = !paper.agent.node; + this._needsUpdate = false; }, /** @@ -198,8 +199,6 @@ var View = Base.extend(Emitter, /** @lends View# */{ * event hanlders for interaction, animation and load events, this method is * invoked for you automatically at the end. * - * @name View#update - * @function * @return {Boolean} {@true if the view was updated} */ update: function() { @@ -220,8 +219,6 @@ var View = Base.extend(Emitter, /** @lends View# */{ * requestAnimationFrame() mechanism for smooth animation. Note that when * using built-in event handlers for interaction, animation and load events, * updates are automatically invoked for you automatically at the end. - * - * @function */ requestUpdate: function() { if (!this._requested) { @@ -234,13 +231,24 @@ var View = Base.extend(Emitter, /** @lends View# */{ if (that._animate) { // Request next update before handling the current frame that.requestUpdate(); - that._handleFrame(); + var element = that._element; + // Only keep animating if we're allowed to, based on whether + // the document is visible and the setting of keepalive. We + // keep requesting frame regardless though, so the animation + // picks up again as soon as the view is visible. + if ((!DomElement.getPrefixed(document, 'hidden') + || PaperScope.getAttribute(element, 'keepalive') + === 'true') && DomElement.isInView(element)) { + that._handleFrame(); + } } // Even if we're not animating, update the view now since this - // might have been a request for a single redraw after a change + // might have been a request for a single redraw after a change. + // NOTE: If nothing has changed (e.g. _handleFrame() wasn't + // called above), then this does not actually do anything. if (that._autoUpdate) that.update(); - }, this._element); + }); this._requested = true; } },