mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
perf(raf): use rafFrames instead of fastdom
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import {CSS} from '../util/dom';
|
||||
import {CSS, rafFrames} from '../util/dom';
|
||||
import {extend} from '../util/util';
|
||||
import {FastDom} from '../util/fastdom';
|
||||
|
||||
|
||||
/**
|
||||
@@ -25,9 +24,8 @@ import {FastDom} from '../util/fastdom';
|
||||
|
||||
export class Animation {
|
||||
|
||||
constructor(ele, opts={}, fastdom=null) {
|
||||
constructor(ele, opts={}) {
|
||||
this.reset();
|
||||
this._fastdom = fastdom;
|
||||
|
||||
this._opts = extend({
|
||||
renderDelay: 16
|
||||
@@ -259,12 +257,7 @@ export class Animation {
|
||||
if (self._duration > 16 && this._opts.renderDelay > 0) {
|
||||
// begin each animation when everything is rendered in their starting point
|
||||
// give the browser some time to render everything in place before starting
|
||||
if (this._fastdom) {
|
||||
this._fastdom.write(kickoff);
|
||||
|
||||
} else {
|
||||
setTimeout(kickoff, this._opts.renderDelay);
|
||||
}
|
||||
rafFrames(this._opts.renderDelay / 16, kickoff);
|
||||
|
||||
} else {
|
||||
// no need to render everything in there place before animating in
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {Title} from 'angular2/angular2';
|
||||
|
||||
import {rafFrames} from '../../util/dom';
|
||||
import {ClickBlock} from '../../util/click-block';
|
||||
import {ScrollTo} from '../../animations/scroll-to';
|
||||
|
||||
@@ -10,9 +11,8 @@ import {ScrollTo} from '../../animations/scroll-to';
|
||||
*/
|
||||
export class IonicApp {
|
||||
|
||||
constructor(config, fastdom) {
|
||||
constructor(config) {
|
||||
this._config = config;
|
||||
this._fastdom = fastdom;
|
||||
this._titleSrv = new Title();
|
||||
this._title = '';
|
||||
this._disTime = 0;
|
||||
@@ -29,7 +29,7 @@ export class IonicApp {
|
||||
setTitle(val) {
|
||||
if (val !== this._title) {
|
||||
this._title = val;
|
||||
this._fastdom.defer(4, () => {
|
||||
rafFrames(4, () => {
|
||||
this._titleSrv.setTitle(this._title);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import {raf} from '../../util/dom';
|
||||
import {raf, rafFrames} from '../../util/dom';
|
||||
|
||||
|
||||
export class Activator {
|
||||
|
||||
constructor(app, config, fastdom) {
|
||||
constructor(app, config) {
|
||||
this.app = app;
|
||||
this.fastdom = fastdom;
|
||||
this.queue = [];
|
||||
this.active = [];
|
||||
this.clearStateDefers = 5;
|
||||
@@ -27,7 +26,7 @@ export class Activator {
|
||||
// queue to have this element activated
|
||||
this.queue.push(activatableEle);
|
||||
|
||||
this.fastdom.write(() => {
|
||||
rafFrames(2, () => {
|
||||
let activatableEle;
|
||||
for (let i = 0; i < this.queue.length; i++) {
|
||||
activatableEle = this.queue[i];
|
||||
@@ -44,7 +43,7 @@ export class Activator {
|
||||
|
||||
upAction() {
|
||||
// the user was pressing down, then just let up
|
||||
this.fastdom.defer(this.clearStateDefers, () => {
|
||||
rafFrames(this.clearStateDefers, () => {
|
||||
this.clearState();
|
||||
});
|
||||
}
|
||||
@@ -52,17 +51,17 @@ export class Activator {
|
||||
clearState() {
|
||||
// all states should return to normal
|
||||
|
||||
if ((!this.app.isEnabled() || this.app.isTransitioning()) && this.clearAttempt < 100) {
|
||||
if ((!this.app.isEnabled() || this.app.isTransitioning())) {
|
||||
// the app is actively disabled, so don't bother deactivating anything.
|
||||
// this makes it easier on the GPU so it doesn't have to redraw any
|
||||
// buttons during a transition. This will retry in XX milliseconds.
|
||||
++this.clearAttempt;
|
||||
this.upAction();
|
||||
setTimeout(() => {
|
||||
this.clearState();
|
||||
}, 600);
|
||||
|
||||
} else {
|
||||
// not actively transitioning, good to deactivate any elements
|
||||
this.deactivate();
|
||||
this.clearAttempt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +69,7 @@ export class Activator {
|
||||
// remove the active class from all active elements
|
||||
this.queue = [];
|
||||
|
||||
this.fastdom.write(() => {
|
||||
rafFrames(2, () => {
|
||||
for (let i = 0; i < this.active.length; i++) {
|
||||
this.active[i].classList.remove(this.activatedClass);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {Activator} from './activator';
|
||||
import {Animation} from '../../animations/animation';
|
||||
import {raf} from '../../util/dom';
|
||||
import {raf, rafFrames} from '../../util/dom';
|
||||
|
||||
|
||||
export class RippleActivator extends Activator {
|
||||
|
||||
constructor(app, config, fastdom) {
|
||||
super(app, config, fastdom);
|
||||
constructor(app, config) {
|
||||
super(app, config);
|
||||
|
||||
this.expands = {};
|
||||
this.fades = {};
|
||||
@@ -18,16 +18,12 @@ export class RippleActivator extends Activator {
|
||||
// create a new ripple element
|
||||
this.expandSpeed = EXPAND_DOWN_PLAYBACK_RATE;
|
||||
|
||||
this.fastdom.defer(2, () => {
|
||||
rafFrames(2, () => {
|
||||
let clientRect = activatableEle.getBoundingClientRect();
|
||||
|
||||
this.fastdom.read(() => {
|
||||
let clientRect = activatableEle.getBoundingClientRect();
|
||||
|
||||
this.fastdom.write(() => {
|
||||
this.createRipple(activatableEle, pointerX, pointerY, clientRect);
|
||||
});
|
||||
raf(() => {
|
||||
this.createRipple(activatableEle, pointerX, pointerY, clientRect);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -60,7 +56,7 @@ export class RippleActivator extends Activator {
|
||||
.duration(FADE_OUT_DURATION)
|
||||
.playbackRate(1)
|
||||
.onFinish(() => {
|
||||
this.fastdom.write(() => {
|
||||
raf(() => {
|
||||
this.fades[rippleId].dispose(true);
|
||||
delete this.fades[rippleId];
|
||||
});
|
||||
@@ -87,7 +83,7 @@ export class RippleActivator extends Activator {
|
||||
|
||||
this.expandSpeed = 1;
|
||||
|
||||
this.fastdom.defer(4, () => {
|
||||
rafFrames(4, () => {
|
||||
this.next();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,16 +19,16 @@ let doc = null;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function initTapClick(windowInstance, documentInstance, appInstance, config, fastdom) {
|
||||
export function initTapClick(windowInstance, documentInstance, appInstance, config) {
|
||||
win = windowInstance;
|
||||
doc = documentInstance;
|
||||
app = appInstance;
|
||||
|
||||
if (config.get('activator') == 'ripple') {
|
||||
activator = new RippleActivator(app, config, fastdom);
|
||||
activator = new RippleActivator(app, config);
|
||||
|
||||
} else if (config.get('activator') == 'highlight') {
|
||||
activator = new Activator(app, config, fastdom);
|
||||
activator = new Activator(app, config);
|
||||
}
|
||||
|
||||
isTapPolyfill = (config.get('tapPolyfill') === true);
|
||||
|
||||
@@ -6,7 +6,6 @@ import {IonicApp} from '../components/app/app';
|
||||
import {Config} from './config';
|
||||
import {Platform} from '../platform/platform';
|
||||
import {OverlayController} from '../components/overlay/overlay-controller';
|
||||
import {FastDom} from '../util/fastdom';
|
||||
import {Form} from '../util/form';
|
||||
import {Keyboard} from '../util/keyboard';
|
||||
import {ActionSheet} from '../components/action-sheet/action-sheet';
|
||||
@@ -22,8 +21,6 @@ import * as dom from '../util/dom';
|
||||
|
||||
|
||||
export function ionicProviders(args={}) {
|
||||
let fastdom = new FastDom();
|
||||
|
||||
let platform = new Platform();
|
||||
let navRegistry = new NavRegistry(args.pages);
|
||||
|
||||
@@ -39,10 +36,10 @@ export function ionicProviders(args={}) {
|
||||
platform.load();
|
||||
config.setPlatform(platform);
|
||||
|
||||
let app = new IonicApp(config, fastdom);
|
||||
let app = new IonicApp(config);
|
||||
|
||||
let events = new Events();
|
||||
initTapClick(window, document, app, config, fastdom);
|
||||
initTapClick(window, document, app, config);
|
||||
let featureDetect = new FeatureDetect();
|
||||
|
||||
setupDom(window, document, config, platform, featureDetect);
|
||||
@@ -52,7 +49,6 @@ export function ionicProviders(args={}) {
|
||||
platform.prepareReady(config);
|
||||
|
||||
return [
|
||||
provide(FastDom, {useValue: fastdom}),
|
||||
provide(IonicApp, {useValue: app}),
|
||||
provide(Config, {useValue: config}),
|
||||
provide(Platform, {useValue: platform}),
|
||||
|
||||
@@ -7,15 +7,15 @@ const nativeCancelRaf = window.cancelAnimationFrame ||
|
||||
window.webkitCancelRequestAnimationFrame;
|
||||
|
||||
export const raf = nativeRaf || function(callback) {
|
||||
let timeCurrent = (new Date()).getTime(),
|
||||
timeDelta;
|
||||
let timeCurrent = (new Date()).getTime(),
|
||||
timeDelta;
|
||||
|
||||
/* Dynamically set delay on a per-tick basis to match 60fps. */
|
||||
/* Technique by Erik Moller. MIT license: https://gist.github.com/paulirish/1579671 */
|
||||
timeDelta = Math.max(0, 16 - (timeCurrent - timeLast));
|
||||
timeLast = timeCurrent + timeDelta;
|
||||
/* Dynamically set delay on a per-tick basis to match 60fps. */
|
||||
/* Technique by Erik Moller. MIT license: https://gist.github.com/paulirish/1579671 */
|
||||
timeDelta = Math.max(0, 16 - (timeCurrent - timeLast));
|
||||
timeLast = timeCurrent + timeDelta;
|
||||
|
||||
return setTimeout(function() { callback(timeCurrent + timeDelta); }, timeDelta);
|
||||
return setTimeout(function() { callback(timeCurrent + timeDelta); }, timeDelta);
|
||||
}
|
||||
|
||||
export const rafCancel = nativeRaf ? nativeCancelRaf : function(id) {
|
||||
@@ -26,6 +26,19 @@ export function rafPromise() {
|
||||
return new Promise(resolve => raf(resolve));
|
||||
}
|
||||
|
||||
export function rafFrames(framesToWait, callback) {
|
||||
framesToWait = Math.ceil(framesToWait);
|
||||
|
||||
if (framesToWait < 2) {
|
||||
raf(callback);
|
||||
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
raf(callback);
|
||||
}, (framesToWait - 1) * 17);
|
||||
}
|
||||
}
|
||||
|
||||
export let CSS = {};
|
||||
(function() {
|
||||
// transform
|
||||
|
||||
@@ -1,389 +0,0 @@
|
||||
import {raf} from './dom';
|
||||
|
||||
/**
|
||||
* FastDom
|
||||
*
|
||||
* Eliminates layout thrashing
|
||||
* by batching DOM read/write
|
||||
* interactions.
|
||||
*
|
||||
* @author Wilson Page <wilsonpage@me.com>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a fresh
|
||||
* FastDom instance.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
export function FastDom() {
|
||||
this.frames = [];
|
||||
this.lastId = 0;
|
||||
|
||||
// Placing the rAF method
|
||||
// on the instance allows
|
||||
// us to replace it with
|
||||
// a stub for testing.
|
||||
this.raf = raf;
|
||||
|
||||
this.batch = {
|
||||
hash: {},
|
||||
read: [],
|
||||
write: [],
|
||||
mode: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a job to the
|
||||
* read batch and schedules
|
||||
* a new frame if need be.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @public
|
||||
*/
|
||||
FastDom.prototype.read = function(fn, ctx) {
|
||||
var job = this.add('read', fn, ctx);
|
||||
var id = job.id;
|
||||
|
||||
// Add this job to the read queue
|
||||
this.batch.read.push(job.id);
|
||||
|
||||
// We should *not* schedule a new frame if:
|
||||
// 1. We're 'reading'
|
||||
// 2. A frame is already scheduled
|
||||
var doesntNeedFrame = this.batch.mode === 'reading'
|
||||
|| this.batch.scheduled;
|
||||
|
||||
// If a frame isn't needed, return
|
||||
if (doesntNeedFrame) return id;
|
||||
|
||||
// Schedule a new
|
||||
// frame, then return
|
||||
this.scheduleBatch();
|
||||
return id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a job to the
|
||||
* write batch and schedules
|
||||
* a new frame if need be.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @public
|
||||
*/
|
||||
FastDom.prototype.write = function(fn, ctx) {
|
||||
var job = this.add('write', fn, ctx);
|
||||
var mode = this.batch.mode;
|
||||
var id = job.id;
|
||||
|
||||
// Push the job id into the queue
|
||||
this.batch.write.push(job.id);
|
||||
|
||||
// We should *not* schedule a new frame if:
|
||||
// 1. We are 'writing'
|
||||
// 2. We are 'reading'
|
||||
// 3. A frame is already scheduled.
|
||||
var doesntNeedFrame = mode === 'writing'
|
||||
|| mode === 'reading'
|
||||
|| this.batch.scheduled;
|
||||
|
||||
// If a frame isn't needed, return
|
||||
if (doesntNeedFrame) return id;
|
||||
|
||||
// Schedule a new
|
||||
// frame, then return
|
||||
this.scheduleBatch();
|
||||
return id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defers the given job
|
||||
* by the number of frames
|
||||
* specified.
|
||||
*
|
||||
* If no frames are given
|
||||
* then the job is run in
|
||||
* the next free frame.
|
||||
*
|
||||
* @param {Number} frame
|
||||
* @param {Function} fn
|
||||
* @public
|
||||
*/
|
||||
FastDom.prototype.defer = function(frame, fn, ctx) {
|
||||
|
||||
// Accepts two arguments
|
||||
if (typeof frame === 'function') {
|
||||
ctx = fn;
|
||||
fn = frame;
|
||||
frame = 1;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var index = frame - 1;
|
||||
|
||||
return this.schedule(index, function() {
|
||||
self.run({
|
||||
fn: fn,
|
||||
ctx: ctx
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears a scheduled 'read',
|
||||
* 'write' or 'defer' job.
|
||||
*
|
||||
* @param {Number|String} id
|
||||
* @public
|
||||
*/
|
||||
FastDom.prototype.clear = function(id) {
|
||||
|
||||
// Defer jobs are cleared differently
|
||||
if (typeof id === 'function') {
|
||||
return this.clearFrame(id);
|
||||
}
|
||||
|
||||
// Allow ids to be passed as strings
|
||||
id = Number(id);
|
||||
|
||||
var job = this.batch.hash[id];
|
||||
if (!job) return;
|
||||
|
||||
var list = this.batch[job.type];
|
||||
var index = list.indexOf(id);
|
||||
|
||||
// Clear references
|
||||
delete this.batch.hash[id];
|
||||
if (~index) list.splice(index, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears a scheduled frame.
|
||||
*
|
||||
* @param {Function} frame
|
||||
* @private
|
||||
*/
|
||||
FastDom.prototype.clearFrame = function(frame) {
|
||||
var index = this.frames.indexOf(frame);
|
||||
if (~index) this.frames.splice(index, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Schedules a new read/write
|
||||
* batch if one isn't pending.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
FastDom.prototype.scheduleBatch = function() {
|
||||
var self = this;
|
||||
|
||||
// Schedule batch for next frame
|
||||
this.schedule(0, function() {
|
||||
self.batch.scheduled = false;
|
||||
self.runBatch();
|
||||
});
|
||||
|
||||
// Set flag to indicate
|
||||
// a frame has been scheduled
|
||||
this.batch.scheduled = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a unique
|
||||
* id for a job.
|
||||
*
|
||||
* @return {Number}
|
||||
* @private
|
||||
*/
|
||||
FastDom.prototype.uniqueId = function() {
|
||||
return ++this.lastId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls each job in
|
||||
* the list passed.
|
||||
*
|
||||
* If a context has been
|
||||
* stored on the function
|
||||
* then it is used, else the
|
||||
* current `this` is used.
|
||||
*
|
||||
* @param {Array} list
|
||||
* @private
|
||||
*/
|
||||
FastDom.prototype.flush = function(list) {
|
||||
var id;
|
||||
|
||||
while (id = list.shift()) {
|
||||
this.run(this.batch.hash[id]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs any 'read' jobs followed
|
||||
* by any 'write' jobs.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
FastDom.prototype.runBatch = function() {
|
||||
try {
|
||||
|
||||
// Set the mode to 'reading',
|
||||
// then empty all read jobs
|
||||
this.batch.mode = 'reading';
|
||||
this.flush(this.batch.read);
|
||||
|
||||
// Set the mode to 'writing'
|
||||
// then empty all write jobs
|
||||
this.batch.mode = 'writing';
|
||||
this.flush(this.batch.write);
|
||||
|
||||
this.batch.mode = null;
|
||||
|
||||
} catch (e) {
|
||||
this.runBatch();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new job to
|
||||
* the given batch.
|
||||
*
|
||||
* @param {Array} list
|
||||
* @param {Function} fn
|
||||
* @param {Object} ctx
|
||||
* @returns {Number} id
|
||||
* @private
|
||||
*/
|
||||
FastDom.prototype.add = function(type, fn, ctx) {
|
||||
var id = this.uniqueId();
|
||||
return this.batch.hash[id] = {
|
||||
id: id,
|
||||
fn: fn,
|
||||
ctx: ctx,
|
||||
type: type
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs a given job.
|
||||
*
|
||||
* Applications using FastDom
|
||||
* have the options of setting
|
||||
* `fastdom.onError`.
|
||||
*
|
||||
* This will catch any
|
||||
* errors that may throw
|
||||
* inside callbacks, which
|
||||
* is useful as often DOM
|
||||
* nodes have been removed
|
||||
* since a job was scheduled.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* fastdom.onError = function(e) {
|
||||
* // Runs when jobs error
|
||||
* };
|
||||
*
|
||||
* @param {Object} job
|
||||
* @private
|
||||
*/
|
||||
FastDom.prototype.run = function(job){
|
||||
var ctx = job.ctx || this;
|
||||
var fn = job.fn;
|
||||
|
||||
// Clear reference to the job
|
||||
delete this.batch.hash[job.id];
|
||||
|
||||
// If no `onError` handler
|
||||
// has been registered, just
|
||||
// run the job normally.
|
||||
if (!this.onError) {
|
||||
return fn.call(ctx);
|
||||
}
|
||||
|
||||
// If an `onError` handler
|
||||
// has been registered, catch
|
||||
// errors that throw inside
|
||||
// callbacks, and run the
|
||||
// handler instead.
|
||||
try { fn.call(ctx); } catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts a rAF loop
|
||||
* to empty the frame queue.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
FastDom.prototype.loop = function() {
|
||||
var self = this;
|
||||
var raf = this.raf;
|
||||
|
||||
// Don't start more than one loop
|
||||
if (this.looping) return;
|
||||
|
||||
raf(function frame() {
|
||||
var fn = self.frames.shift();
|
||||
|
||||
// If no more frames,
|
||||
// stop looping
|
||||
if (!self.frames.length) {
|
||||
self.looping = false;
|
||||
|
||||
// Otherwise, schedule the
|
||||
// next frame
|
||||
} else {
|
||||
raf(frame);
|
||||
}
|
||||
|
||||
// Run the frame. Note that
|
||||
// this may throw an error
|
||||
// in user code, but all
|
||||
// fastdom tasks are dealt
|
||||
// with already so the code
|
||||
// will continue to iterate
|
||||
if (fn) fn();
|
||||
});
|
||||
|
||||
this.looping = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a function to
|
||||
* a specified index
|
||||
* of the frame queue.
|
||||
*
|
||||
* @param {Number} index
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
* @private
|
||||
*/
|
||||
FastDom.prototype.schedule = function(index, fn) {
|
||||
|
||||
// Make sure this slot
|
||||
// hasn't already been
|
||||
// taken. If it has, try
|
||||
// re-scheduling for the next slot
|
||||
if (this.frames[index]) {
|
||||
return this.schedule(index + 1, fn);
|
||||
}
|
||||
|
||||
// Start the rAF
|
||||
// loop to empty
|
||||
// the frame queue
|
||||
this.loop();
|
||||
|
||||
// Insert this function into
|
||||
// the frames queue and return
|
||||
return this.frames[index] = fn;
|
||||
};
|
||||
Reference in New Issue
Block a user