123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- // Copyright 2014 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview goog.dom.animationFrame permits work to be done in-sync with
- * the render refresh rate of the browser and to divide work up globally based
- * on whether the intent is to measure or to mutate the DOM. The latter avoids
- * repeated style recalculation which can be really slow.
- *
- * Goals of the API:
- * <ul>
- * <li>Make it easy to schedule work for the next animation frame.
- * <li>Make it easy to only do work once per animation frame, even if two
- * events fire that trigger the same work.
- * <li>Make it easy to do all work in two phases to avoid repeated style
- * recalculation caused by interleaved reads and writes.
- * <li>Avoid creating closures per schedule operation.
- * </ul>
- *
- *
- * Programmatic:
- * <pre>
- * var animationTask = goog.dom.animationFrame.createTask({
- * measure: function(state) {
- * state.width = goog.style.getSize(elem).width;
- * this.animationTask();
- * },
- * mutate: function(state) {
- * goog.style.setWidth(elem, Math.floor(state.width / 2));
- * }
- * }, this);
- * });
- * </pre>
- *
- * See also
- * https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame
- */
- goog.provide('goog.dom.animationFrame');
- goog.provide('goog.dom.animationFrame.Spec');
- goog.provide('goog.dom.animationFrame.State');
- goog.require('goog.dom.animationFrame.polyfill');
- // Install the polyfill.
- goog.dom.animationFrame.polyfill.install();
- /**
- * @typedef {{
- * id: number,
- * fn: !Function,
- * context: (!Object|undefined)
- * }}
- * @private
- */
- goog.dom.animationFrame.Task_;
- /**
- * @typedef {{
- * measureTask: goog.dom.animationFrame.Task_,
- * mutateTask: goog.dom.animationFrame.Task_,
- * state: (!Object|undefined),
- * args: (!Array|undefined),
- * isScheduled: boolean
- * }}
- * @private
- */
- goog.dom.animationFrame.TaskSet_;
- /**
- * @typedef {{
- * measure: (!Function|undefined),
- * mutate: (!Function|undefined)
- * }}
- */
- goog.dom.animationFrame.Spec;
- /**
- * A type to represent state. Users may add properties as desired.
- * @constructor
- * @final
- */
- goog.dom.animationFrame.State = function() {};
- /**
- * Saves a set of tasks to be executed in the next requestAnimationFrame phase.
- * This list is initialized once before any event firing occurs. It is not
- * affected by the fired events or the requestAnimationFrame processing (unless
- * a new event is created during the processing).
- * @private {!Array<!Array<goog.dom.animationFrame.TaskSet_>>}
- */
- goog.dom.animationFrame.tasks_ = [[], []];
- /**
- * Values are 0 or 1, for whether the first or second array should be used to
- * lookup or add tasks.
- * @private {number}
- */
- goog.dom.animationFrame.doubleBufferIndex_ = 0;
- /**
- * Whether we have already requested an animation frame that hasn't happened
- * yet.
- * @private {boolean}
- */
- goog.dom.animationFrame.requestedFrame_ = false;
- /**
- * Counter to generate IDs for tasks.
- * @private {number}
- */
- goog.dom.animationFrame.taskId_ = 0;
- /**
- * Whether the animationframe runTasks_ loop is currently running.
- * @private {boolean}
- */
- goog.dom.animationFrame.running_ = false;
- /**
- * Returns a function that schedules the two passed-in functions to be run upon
- * the next animation frame. Calling the function again during the same
- * animation frame does nothing.
- *
- * The function under the "measure" key will run first and together with all
- * other functions scheduled under this key and the function under "mutate" will
- * run after that.
- *
- * @param {{
- * measure: (function(this:THIS, !goog.dom.animationFrame.State)|undefined),
- * mutate: (function(this:THIS, !goog.dom.animationFrame.State)|undefined)
- * }} spec
- * @param {THIS=} opt_context Context in which to run the function.
- * @return {function(...?)}
- * @template THIS
- */
- goog.dom.animationFrame.createTask = function(spec, opt_context) {
- var id = goog.dom.animationFrame.taskId_++;
- var measureTask = {id: id, fn: spec.measure, context: opt_context};
- var mutateTask = {id: id, fn: spec.mutate, context: opt_context};
- var taskSet = {
- measureTask: measureTask,
- mutateTask: mutateTask,
- state: {},
- args: undefined,
- isScheduled: false
- };
- return function() {
- // Save args and state.
- if (arguments.length > 0) {
- // The state argument goes last. That is kinda horrible but compatible
- // with {@see wiz.async.method}.
- if (!taskSet.args) {
- taskSet.args = [];
- }
- taskSet.args.length = 0;
- taskSet.args.push.apply(taskSet.args, arguments);
- taskSet.args.push(taskSet.state);
- } else {
- if (!taskSet.args || taskSet.args.length == 0) {
- taskSet.args = [taskSet.state];
- } else {
- taskSet.args[0] = taskSet.state;
- taskSet.args.length = 1;
- }
- }
- if (!taskSet.isScheduled) {
- taskSet.isScheduled = true;
- var tasksArray = goog.dom.animationFrame
- .tasks_[goog.dom.animationFrame.doubleBufferIndex_];
- tasksArray.push(
- /** @type {goog.dom.animationFrame.TaskSet_} */ (taskSet));
- }
- goog.dom.animationFrame.requestAnimationFrame_();
- };
- };
- /**
- * Run scheduled tasks.
- * @private
- */
- goog.dom.animationFrame.runTasks_ = function() {
- goog.dom.animationFrame.running_ = true;
- goog.dom.animationFrame.requestedFrame_ = false;
- var tasksArray = goog.dom.animationFrame
- .tasks_[goog.dom.animationFrame.doubleBufferIndex_];
- var taskLength = tasksArray.length;
- // During the runTasks_, if there is a recursive call to queue up more
- // task(s) for the next frame, we use double-buffering for that.
- goog.dom.animationFrame.doubleBufferIndex_ =
- (goog.dom.animationFrame.doubleBufferIndex_ + 1) % 2;
- var task;
- // Run all the measure tasks first.
- for (var i = 0; i < taskLength; ++i) {
- task = tasksArray[i];
- var measureTask = task.measureTask;
- task.isScheduled = false;
- if (measureTask.fn) {
- // TODO (perumaal): Handle any exceptions thrown by the lambda.
- measureTask.fn.apply(measureTask.context, task.args);
- }
- }
- // Run the mutate tasks next.
- for (var i = 0; i < taskLength; ++i) {
- task = tasksArray[i];
- var mutateTask = task.mutateTask;
- task.isScheduled = false;
- if (mutateTask.fn) {
- // TODO (perumaal): Handle any exceptions thrown by the lambda.
- mutateTask.fn.apply(mutateTask.context, task.args);
- }
- // Clear state for next vsync.
- task.state = {};
- }
- // Clear the tasks array as we have finished processing all the tasks.
- tasksArray.length = 0;
- goog.dom.animationFrame.running_ = false;
- };
- /**
- * @return {boolean} Whether the animationframe is currently running. For use
- * by callers who need not to delay tasks scheduled during runTasks_ for an
- * additional frame.
- */
- goog.dom.animationFrame.isRunning = function() {
- return goog.dom.animationFrame.running_;
- };
- /**
- * Request {@see goog.dom.animationFrame.runTasks_} to be called upon the
- * next animation frame if we haven't done so already.
- * @private
- */
- goog.dom.animationFrame.requestAnimationFrame_ = function() {
- if (goog.dom.animationFrame.requestedFrame_) {
- return;
- }
- goog.dom.animationFrame.requestedFrame_ = true;
- window.requestAnimationFrame(goog.dom.animationFrame.runTasks_);
- };
|