Improve handling of view updates and detection of invisible documents.

Switch to the new HTML5 Page Visibility API.
This commit is contained in:
Jürg Lehni 2016-02-09 09:59:19 +01:00
parent 80e6246016
commit da216aa581
5 changed files with 48 additions and 54 deletions

View file

@ -482,6 +482,10 @@ Base.exports.PaperScript = (function() {
}); });
if (res.onFrame) if (res.onFrame)
view.setOnFrame(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; return compiled;
} }

View file

@ -72,49 +72,28 @@ DomEvent.requestAnimationFrame = new function() {
var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'), var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'),
requested = false, requested = false,
callbacks = [], callbacks = [],
focused = true,
timer; timer;
DomEvent.add(window, {
focus: function() {
focused = true;
},
blur: function() {
focused = false;
}
});
function handleCallbacks() { function handleCallbacks() {
// Checks all installed callbacks for element visibility and // Make a local references to the current callbacks array and set
// execute if needed. // callbacks to a new empty array, so it can collect the functions for
for (var i = callbacks.length - 1; i >= 0; i--) { // the new requests.
var entry = callbacks[i], var functions = callbacks;
func = entry[0], callbacks = [];
el = entry[1]; // Call the collected callback functions.
if (!el || (PaperScope.getAttribute(el, 'keepalive') == 'true' for (var i = 0, l = functions.length; i < l; i++)
|| focused) && DomElement.isInView(el)) { functions[i]();
// Only remove from the list once the callback was called. This // Now see if the above calls have collected new callbacks. Keep
// could take a long time based on visibility. But this way we // requesting new frames as long as we have callbacks.
// are sure to keep the animation loop running. requested = nativeRequest && callbacks.length;
callbacks.splice(i, 1); if (requested)
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); nativeRequest(handleCallbacks);
} else {
requested = false;
}
}
} }
return function(callback, element) { return function(callback) {
// Add to the list of callbacks to be called in the next animation // Add to the list of callbacks to be called in the next animation
// frame. // frame.
callbacks.push([callback, element]); callbacks.push(callback);
if (nativeRequest) { if (nativeRequest) {
// Handle animation natively. We only need to request the frame // Handle animation natively. We only need to request the frame
// once for all collected callbacks. // once for all collected callbacks.
@ -125,7 +104,7 @@ DomEvent.requestAnimationFrame = new function() {
} else if (!timer) { } else if (!timer) {
// Install interval timer that checks all callbacks. This // Install interval timer that checks all callbacks. This
// results in faster animations than repeatedly installing // results in faster animations than repeatedly installing
// timout timers. // timeout timers.
timer = setInterval(handleCallbacks, 1000 / 60); timer = setInterval(handleCallbacks, 1000 / 60);
} }
}; };

View file

@ -87,14 +87,15 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
*/ */
_changed: function(flags, item) { _changed: function(flags, item) {
if (flags & /*#=*/ChangeFlag.APPEARANCE) { 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; var view = this._view;
if (view && !view._requested && view._autoUpdate) 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(); view.requestUpdate();
} }
}
// Have project keep track of changed items so they can be iterated. // 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 // This can be used for example to update the SVG tree. Needs to be
// activated in Project // activated in Project

View file

@ -56,6 +56,8 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
this._pixelRatio = deviceRatio / backingStoreRatio; this._pixelRatio = deviceRatio / backingStoreRatio;
} }
View.call(this, project, canvas); View.call(this, project, canvas);
// We can't be sure the canvas is clear
this._needsUpdate = true;
}, },
remove: function remove() { remove: function remove() {
@ -128,14 +130,14 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
* @return {Boolean} {@true if the view was updated} * @return {Boolean} {@true if the view was updated}
*/ */
update: function() { update: function() {
var project = this._project; if (!this._needsUpdate)
if (!project || !project._needsUpdate)
return false; return false;
var ctx = this._context, var project = this._project,
ctx = this._context,
size = this._viewSize; size = this._viewSize;
ctx.clearRect(0, 0, size.width + 1, size.height + 1); ctx.clearRect(0, 0, size.width + 1, size.height + 1);
project.draw(ctx, this._matrix, this._pixelRatio); project.draw(ctx, this._matrix, this._pixelRatio);
project._needsUpdate = false; this._needsUpdate = false;
return true; return true;
} }
}); });

View file

@ -119,6 +119,7 @@ var View = Base.extend(Emitter, /** @lends View# */{
this._itemEvents = { native: {}, virtual: {} }; this._itemEvents = { native: {}, virtual: {} };
// Do not set _autoUpdate on Node.js by default: // Do not set _autoUpdate on Node.js by default:
this._autoUpdate = !paper.agent.node; 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 * event hanlders for interaction, animation and load events, this method is
* invoked for you automatically at the end. * invoked for you automatically at the end.
* *
* @name View#update
* @function
* @return {Boolean} {@true if the view was updated} * @return {Boolean} {@true if the view was updated}
*/ */
update: function() { update: function() {
@ -220,8 +219,6 @@ var View = Base.extend(Emitter, /** @lends View# */{
* requestAnimationFrame() mechanism for smooth animation. Note that when * requestAnimationFrame() mechanism for smooth animation. Note that when
* using built-in event handlers for interaction, animation and load events, * using built-in event handlers for interaction, animation and load events,
* updates are automatically invoked for you automatically at the end. * updates are automatically invoked for you automatically at the end.
*
* @function
*/ */
requestUpdate: function() { requestUpdate: function() {
if (!this._requested) { if (!this._requested) {
@ -234,13 +231,24 @@ var View = Base.extend(Emitter, /** @lends View# */{
if (that._animate) { if (that._animate) {
// Request next update before handling the current frame // Request next update before handling the current frame
that.requestUpdate(); that.requestUpdate();
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(); that._handleFrame();
} }
}
// Even if we're not animating, update the view now since this // 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) if (that._autoUpdate)
that.update(); that.update();
}, this._element); });
this._requested = true; this._requested = true;
} }
}, },