Path: blob/trunk/third_party/closure/goog/dom/animationframe/animationframe.js
2868 views
// Copyright 2014 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview goog.dom.animationFrame permits work to be done in-sync with16* the render refresh rate of the browser and to divide work up globally based17* on whether the intent is to measure or to mutate the DOM. The latter avoids18* repeated style recalculation which can be really slow.19*20* Goals of the API:21* <ul>22* <li>Make it easy to schedule work for the next animation frame.23* <li>Make it easy to only do work once per animation frame, even if two24* events fire that trigger the same work.25* <li>Make it easy to do all work in two phases to avoid repeated style26* recalculation caused by interleaved reads and writes.27* <li>Avoid creating closures per schedule operation.28* </ul>29*30*31* Programmatic:32* <pre>33* var animationTask = goog.dom.animationFrame.createTask({34* measure: function(state) {35* state.width = goog.style.getSize(elem).width;36* this.animationTask();37* },38* mutate: function(state) {39* goog.style.setWidth(elem, Math.floor(state.width / 2));40* }41* }, this);42* });43* </pre>44*45* See also46* https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame47*/4849goog.provide('goog.dom.animationFrame');50goog.provide('goog.dom.animationFrame.Spec');51goog.provide('goog.dom.animationFrame.State');5253goog.require('goog.dom.animationFrame.polyfill');5455// Install the polyfill.56goog.dom.animationFrame.polyfill.install();575859/**60* @typedef {{61* id: number,62* fn: !Function,63* context: (!Object|undefined)64* }}65* @private66*/67goog.dom.animationFrame.Task_;686970/**71* @typedef {{72* measureTask: goog.dom.animationFrame.Task_,73* mutateTask: goog.dom.animationFrame.Task_,74* state: (!Object|undefined),75* args: (!Array|undefined),76* isScheduled: boolean77* }}78* @private79*/80goog.dom.animationFrame.TaskSet_;818283/**84* @typedef {{85* measure: (!Function|undefined),86* mutate: (!Function|undefined)87* }}88*/89goog.dom.animationFrame.Spec;90919293/**94* A type to represent state. Users may add properties as desired.95* @constructor96* @final97*/98goog.dom.animationFrame.State = function() {};99100101/**102* Saves a set of tasks to be executed in the next requestAnimationFrame phase.103* This list is initialized once before any event firing occurs. It is not104* affected by the fired events or the requestAnimationFrame processing (unless105* a new event is created during the processing).106* @private {!Array<!Array<goog.dom.animationFrame.TaskSet_>>}107*/108goog.dom.animationFrame.tasks_ = [[], []];109110111/**112* Values are 0 or 1, for whether the first or second array should be used to113* lookup or add tasks.114* @private {number}115*/116goog.dom.animationFrame.doubleBufferIndex_ = 0;117118119/**120* Whether we have already requested an animation frame that hasn't happened121* yet.122* @private {boolean}123*/124goog.dom.animationFrame.requestedFrame_ = false;125126127/**128* Counter to generate IDs for tasks.129* @private {number}130*/131goog.dom.animationFrame.taskId_ = 0;132133134/**135* Whether the animationframe runTasks_ loop is currently running.136* @private {boolean}137*/138goog.dom.animationFrame.running_ = false;139140141/**142* Returns a function that schedules the two passed-in functions to be run upon143* the next animation frame. Calling the function again during the same144* animation frame does nothing.145*146* The function under the "measure" key will run first and together with all147* other functions scheduled under this key and the function under "mutate" will148* run after that.149*150* @param {{151* measure: (function(this:THIS, !goog.dom.animationFrame.State)|undefined),152* mutate: (function(this:THIS, !goog.dom.animationFrame.State)|undefined)153* }} spec154* @param {THIS=} opt_context Context in which to run the function.155* @return {function(...?)}156* @template THIS157*/158goog.dom.animationFrame.createTask = function(spec, opt_context) {159var id = goog.dom.animationFrame.taskId_++;160var measureTask = {id: id, fn: spec.measure, context: opt_context};161var mutateTask = {id: id, fn: spec.mutate, context: opt_context};162163var taskSet = {164measureTask: measureTask,165mutateTask: mutateTask,166state: {},167args: undefined,168isScheduled: false169};170171return function() {172// Save args and state.173if (arguments.length > 0) {174// The state argument goes last. That is kinda horrible but compatible175// with {@see wiz.async.method}.176if (!taskSet.args) {177taskSet.args = [];178}179taskSet.args.length = 0;180taskSet.args.push.apply(taskSet.args, arguments);181taskSet.args.push(taskSet.state);182} else {183if (!taskSet.args || taskSet.args.length == 0) {184taskSet.args = [taskSet.state];185} else {186taskSet.args[0] = taskSet.state;187taskSet.args.length = 1;188}189}190if (!taskSet.isScheduled) {191taskSet.isScheduled = true;192var tasksArray = goog.dom.animationFrame193.tasks_[goog.dom.animationFrame.doubleBufferIndex_];194tasksArray.push(195/** @type {goog.dom.animationFrame.TaskSet_} */ (taskSet));196}197goog.dom.animationFrame.requestAnimationFrame_();198};199};200201202/**203* Run scheduled tasks.204* @private205*/206goog.dom.animationFrame.runTasks_ = function() {207goog.dom.animationFrame.running_ = true;208goog.dom.animationFrame.requestedFrame_ = false;209var tasksArray = goog.dom.animationFrame210.tasks_[goog.dom.animationFrame.doubleBufferIndex_];211var taskLength = tasksArray.length;212213// During the runTasks_, if there is a recursive call to queue up more214// task(s) for the next frame, we use double-buffering for that.215goog.dom.animationFrame.doubleBufferIndex_ =216(goog.dom.animationFrame.doubleBufferIndex_ + 1) % 2;217218var task;219220// Run all the measure tasks first.221for (var i = 0; i < taskLength; ++i) {222task = tasksArray[i];223var measureTask = task.measureTask;224task.isScheduled = false;225if (measureTask.fn) {226// TODO (perumaal): Handle any exceptions thrown by the lambda.227measureTask.fn.apply(measureTask.context, task.args);228}229}230231// Run the mutate tasks next.232for (var i = 0; i < taskLength; ++i) {233task = tasksArray[i];234var mutateTask = task.mutateTask;235task.isScheduled = false;236if (mutateTask.fn) {237// TODO (perumaal): Handle any exceptions thrown by the lambda.238mutateTask.fn.apply(mutateTask.context, task.args);239}240241// Clear state for next vsync.242task.state = {};243}244245// Clear the tasks array as we have finished processing all the tasks.246tasksArray.length = 0;247goog.dom.animationFrame.running_ = false;248};249250251/**252* @return {boolean} Whether the animationframe is currently running. For use253* by callers who need not to delay tasks scheduled during runTasks_ for an254* additional frame.255*/256goog.dom.animationFrame.isRunning = function() {257return goog.dom.animationFrame.running_;258};259260261/**262* Request {@see goog.dom.animationFrame.runTasks_} to be called upon the263* next animation frame if we haven't done so already.264* @private265*/266goog.dom.animationFrame.requestAnimationFrame_ = function() {267if (goog.dom.animationFrame.requestedFrame_) {268return;269}270goog.dom.animationFrame.requestedFrame_ = true;271window.requestAnimationFrame(goog.dom.animationFrame.runTasks_);272};273274275