first commit
This commit is contained in:
+324
@@ -0,0 +1,324 @@
|
||||
!(function(win) {
|
||||
|
||||
/**
|
||||
* FastDom
|
||||
*
|
||||
* Eliminates layout thrashing
|
||||
* by batching DOM read/write
|
||||
* interactions.
|
||||
*
|
||||
* @author Wilson Page <wilsonpage@me.com>
|
||||
* @author Kornel Lesinski <kornel.lesinski@ft.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Mini logger
|
||||
*
|
||||
* @return {Function}
|
||||
*/
|
||||
var debug = 0 ? console.log.bind(console, '[fastdom]') : function() {};
|
||||
|
||||
/**
|
||||
* Normalized rAF
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
var raf = win.requestAnimationFrame
|
||||
|| win.webkitRequestAnimationFrame
|
||||
|| win.mozRequestAnimationFrame
|
||||
|| win.msRequestAnimationFrame
|
||||
|| function(cb) { return setTimeout(cb, 16); };
|
||||
|
||||
/**
|
||||
* Initialize a `FastDom`.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function FastDom() {
|
||||
var self = this;
|
||||
self.reads = [];
|
||||
self.writes = [];
|
||||
self.raf = raf.bind(win); // test hook
|
||||
debug('initialized', self);
|
||||
}
|
||||
|
||||
FastDom.prototype = {
|
||||
constructor: FastDom,
|
||||
|
||||
/**
|
||||
* We run this inside a try catch
|
||||
* so that if any jobs error, we
|
||||
* are able to recover and continue
|
||||
* to flush the batch until it's empty.
|
||||
*
|
||||
* @param {Array} tasks
|
||||
*/
|
||||
runTasks: function(tasks) {
|
||||
debug('run tasks');
|
||||
var task; while (task = tasks.shift()) task();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a job to the read batch and
|
||||
* schedules a new frame if need be.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Object} ctx the context to be bound to `fn` (optional).
|
||||
* @public
|
||||
*/
|
||||
measure: function(fn, ctx) {
|
||||
debug('measure');
|
||||
var task = !ctx ? fn : fn.bind(ctx);
|
||||
this.reads.push(task);
|
||||
scheduleFlush(this);
|
||||
return task;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a job to the
|
||||
* write batch and schedules
|
||||
* a new frame if need be.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Object} ctx the context to be bound to `fn` (optional).
|
||||
* @public
|
||||
*/
|
||||
mutate: function(fn, ctx) {
|
||||
debug('mutate');
|
||||
var task = !ctx ? fn : fn.bind(ctx);
|
||||
this.writes.push(task);
|
||||
scheduleFlush(this);
|
||||
return task;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears a scheduled 'read' or 'write' task.
|
||||
*
|
||||
* @param {Object} task
|
||||
* @return {Boolean} success
|
||||
* @public
|
||||
*/
|
||||
clear: function(task) {
|
||||
debug('clear', task);
|
||||
return remove(this.reads, task) || remove(this.writes, task);
|
||||
},
|
||||
|
||||
/**
|
||||
* Extend this FastDom with some
|
||||
* custom functionality.
|
||||
*
|
||||
* Because fastdom must *always* be a
|
||||
* singleton, we're actually extending
|
||||
* the fastdom instance. This means tasks
|
||||
* scheduled by an extension still enter
|
||||
* fastdom's global task queue.
|
||||
*
|
||||
* The 'super' instance can be accessed
|
||||
* from `this.fastdom`.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var myFastdom = fastdom.extend({
|
||||
* initialize: function() {
|
||||
* // runs on creation
|
||||
* },
|
||||
*
|
||||
* // override a method
|
||||
* measure: function(fn) {
|
||||
* // do extra stuff ...
|
||||
*
|
||||
* // then call the original
|
||||
* return this.fastdom.measure(fn);
|
||||
* },
|
||||
*
|
||||
* ...
|
||||
* });
|
||||
*
|
||||
* @param {Object} props properties to mixin
|
||||
* @return {FastDom}
|
||||
*/
|
||||
extend: function(props) {
|
||||
debug('extend', props);
|
||||
if (typeof props != 'object') throw new Error('expected object');
|
||||
|
||||
var child = Object.create(this);
|
||||
mixin(child, props);
|
||||
child.fastdom = this;
|
||||
|
||||
// run optional creation hook
|
||||
if (child.initialize) child.initialize();
|
||||
|
||||
return child;
|
||||
},
|
||||
|
||||
// override this with a function
|
||||
// to prevent Errors in console
|
||||
// when tasks throw
|
||||
catch: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Schedules a new read/write
|
||||
* batch if one isn't pending.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function scheduleFlush(fastdom) {
|
||||
if (!fastdom.scheduled) {
|
||||
fastdom.scheduled = true;
|
||||
fastdom.raf(flush.bind(null, fastdom));
|
||||
debug('flush scheduled');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs queued `read` and `write` tasks.
|
||||
*
|
||||
* Errors are caught and thrown by default.
|
||||
* If a `.catch` function has been defined
|
||||
* it is called instead.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function flush(fastdom) {
|
||||
debug('flush');
|
||||
|
||||
var writes = fastdom.writes;
|
||||
var reads = fastdom.reads;
|
||||
var error;
|
||||
|
||||
try {
|
||||
debug('flushing reads', reads.length);
|
||||
fastdom.runTasks(reads);
|
||||
debug('flushing writes', writes.length);
|
||||
fastdom.runTasks(writes);
|
||||
} catch (e) { error = e; }
|
||||
|
||||
fastdom.scheduled = false;
|
||||
|
||||
// If the batch errored we may still have tasks queued
|
||||
if (reads.length || writes.length) scheduleFlush(fastdom);
|
||||
|
||||
if (error) {
|
||||
debug('task errored', error.message);
|
||||
if (fastdom.catch) fastdom.catch(error);
|
||||
else throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from an Array.
|
||||
*
|
||||
* @param {Array} array
|
||||
* @param {*} item
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function remove(array, item) {
|
||||
var index = array.indexOf(item);
|
||||
return !!~index && !!array.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixin own properties of source
|
||||
* object into the target.
|
||||
*
|
||||
* @param {Object} target
|
||||
* @param {Object} source
|
||||
*/
|
||||
function mixin(target, source) {
|
||||
for (var key in source) {
|
||||
if (source.hasOwnProperty(key)) target[key] = source[key];
|
||||
}
|
||||
}
|
||||
|
||||
// There should never be more than
|
||||
// one instance of `FastDom` in an app
|
||||
var exports = win.fastdom = (win.fastdom || new FastDom()); // jshint ignore:line
|
||||
|
||||
// Expose to CJS & AMD
|
||||
if ((typeof define) == 'function') define(function() { return exports; });
|
||||
else if ((typeof module) == 'object') module.exports = exports;
|
||||
|
||||
})( typeof window !== 'undefined' ? window : this);
|
||||
|
||||
!(function() {
|
||||
|
||||
/**
|
||||
* Wraps fastdom in a Promise API
|
||||
* for improved control-flow.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // returning a result
|
||||
* fastdom.measure(() => el.clientWidth)
|
||||
* .then(result => ...);
|
||||
*
|
||||
* // returning promises from tasks
|
||||
* fastdom.measure(() => {
|
||||
* var w = el1.clientWidth;
|
||||
* return fastdom.mutate(() => el2.style.width = w + 'px');
|
||||
* }).then(() => console.log('all done'));
|
||||
*
|
||||
* // clearing pending tasks
|
||||
* var promise = fastdom.measure(...)
|
||||
* fastdom.clear(promise);
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var exports = {
|
||||
initialize: function() {
|
||||
this._tasks = new Map();
|
||||
},
|
||||
|
||||
mutate: function(fn, ctx) {
|
||||
return create(this, 'mutate', fn, ctx);
|
||||
},
|
||||
|
||||
measure: function(fn, ctx) {
|
||||
return create(this, 'measure', fn, ctx);
|
||||
},
|
||||
|
||||
clear: function(promise) {
|
||||
var tasks = this._tasks;
|
||||
var task = tasks.get(promise);
|
||||
this.fastdom.clear(task);
|
||||
tasks.delete(promise);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a fastdom task wrapped in
|
||||
* a 'cancellable' Promise.
|
||||
*
|
||||
* @param {FastDom} fastdom
|
||||
* @param {String} type - 'measure'|'muatate'
|
||||
* @param {Function} fn
|
||||
* @return {Promise}
|
||||
*/
|
||||
function create(promised, type, fn, ctx) {
|
||||
var tasks = promised._tasks;
|
||||
var fastdom = promised.fastdom;
|
||||
var task;
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
task = fastdom[type](function() {
|
||||
tasks.delete(promise);
|
||||
try { resolve(ctx ? fn.call(ctx) : fn()); }
|
||||
catch (e) { reject(e); }
|
||||
}, ctx);
|
||||
});
|
||||
|
||||
tasks.set(promise, task);
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Expose to CJS, AMD or global
|
||||
if ((typeof define)[0] == 'f') define(function() { return exports; });
|
||||
else if ((typeof module)[0] == 'o') module.exports = exports;
|
||||
else window.fastdomPromised = exports;
|
||||
|
||||
})();
|
||||
|
||||
window.fastdomPromised = fastdom.extend(fastdomPromised);
|
||||
Reference in New Issue
Block a user