123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- /*
- Copyright 2013-2015 ASIAL CORPORATION
-
- 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.
-
- */
-
- /**
- * Minimal animation library for managing css transition on mobile browsers.
- */
- 'use strict';
-
- import _Object$keys from 'babel-runtime/core-js/object/keys';
- import _setImmediate from 'babel-runtime/core-js/set-immediate';
- var TIMEOUT_RATIO = 1.4;
-
- var util = {};
-
- // capitalize string
- util.capitalize = function (str) {
- return str.charAt(0).toUpperCase() + str.slice(1);
- };
-
- /**
- * @param {Object} params
- * @param {String} params.property
- * @param {Float} params.duration
- * @param {String} params.timing
- */
- util.buildTransitionValue = function (params) {
- params.property = params.property || 'all';
- params.duration = params.duration || 0.4;
- params.timing = params.timing || 'linear';
-
- var props = params.property.split(/ +/);
-
- return props.map(function (prop) {
- return prop + ' ' + params.duration + 's ' + params.timing;
- }).join(', ');
- };
-
- /**
- * Add an event handler on "transitionend" event.
- */
- util.onceOnTransitionEnd = function (element, callback) {
- if (!element) {
- return function () {};
- }
-
- var fn = function fn(event) {
- if (element == event.target) {
- event.stopPropagation();
- removeListeners();
-
- callback();
- }
- };
-
- var removeListeners = function removeListeners() {
- util._transitionEndEvents.forEach(function (eventName) {
- element.removeEventListener(eventName, fn, false);
- });
- };
-
- util._transitionEndEvents.forEach(function (eventName) {
- element.addEventListener(eventName, fn, false);
- });
-
- return removeListeners;
- };
-
- util._transitionEndEvents = function () {
-
- if ('ontransitionend' in window) {
- return ['transitionend'];
- }
-
- if ('onwebkittransitionend' in window) {
- return ['webkitTransitionEnd'];
- }
-
- if (util.vendorPrefix === 'webkit' || util.vendorPrefix === 'o' || util.vendorPrefix === 'moz' || util.vendorPrefix === 'ms') {
- return [util.vendorPrefix + 'TransitionEnd', 'transitionend'];
- }
-
- return [];
- }();
-
- util._cssPropertyDict = function () {
- var styles = window.getComputedStyle(document.documentElement, '');
- var dict = {};
- var a = 'A'.charCodeAt(0);
- var z = 'z'.charCodeAt(0);
-
- var upper = function upper(s) {
- return s.substr(1).toUpperCase();
- };
-
- for (var i = 0; i < styles.length; i++) {
-
- var key = styles[i].replace(/^[-]+/, '').replace(/[-][a-z]/g, upper).replace(/^moz/, 'Moz');
-
- if (a <= key.charCodeAt(0) && z >= key.charCodeAt(0)) {
- if (key !== 'cssText' && key !== 'parentText') {
- dict[key] = true;
- }
- }
- }
-
- return dict;
- }();
-
- util.hasCssProperty = function (name) {
- return name in util._cssPropertyDict;
- };
-
- /**
- * Vendor prefix for css property.
- */
- util.vendorPrefix = function () {
- var styles = window.getComputedStyle(document.documentElement, ''),
- pre = (Array.prototype.slice.call(styles).join('').match(/-(moz|webkit|ms)-/) || styles.OLink === '' && ['', 'o'])[1];
- return pre;
- }();
-
- util.forceLayoutAtOnce = function (elements, callback) {
- this.batchImmediate(function () {
- elements.forEach(function (element) {
- // force layout
- element.offsetHeight;
- });
- callback();
- });
- };
-
- util.batchImmediate = function () {
- var callbacks = [];
-
- return function (callback) {
- if (callbacks.length === 0) {
- _setImmediate(function () {
- var concreateCallbacks = callbacks.slice(0);
- callbacks = [];
- concreateCallbacks.forEach(function (callback) {
- callback();
- });
- });
- }
-
- callbacks.push(callback);
- };
- }();
-
- util.batchAnimationFrame = function () {
- var callbacks = [];
-
- var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
- setTimeout(callback, 1000 / 60);
- };
-
- return function (callback) {
- if (callbacks.length === 0) {
- raf(function () {
- var concreateCallbacks = callbacks.slice(0);
- callbacks = [];
- concreateCallbacks.forEach(function (callback) {
- callback();
- });
- });
- }
-
- callbacks.push(callback);
- };
- }();
-
- util.transitionPropertyName = function () {
- if (util.hasCssProperty('transitionDuration')) {
- return 'transition';
- }
-
- if (util.hasCssProperty(util.vendorPrefix + 'TransitionDuration')) {
- return util.vendorPrefix + 'Transition';
- }
-
- throw new Error('Invalid state');
- }();
-
- /**
- * @param {HTMLElement} element
- */
- var Animit = function Animit(element, defaults) {
- if (!(this instanceof Animit)) {
- return new Animit(element, defaults);
- }
-
- if (element instanceof HTMLElement) {
- this.elements = [element];
- } else if (Object.prototype.toString.call(element) === '[object Array]') {
- this.elements = element;
- } else {
- throw new Error('First argument must be an array or an instance of HTMLElement.');
- }
-
- this.defaults = defaults;
- this.transitionQueue = [];
- this.lastStyleAttributeDict = [];
- };
-
- Animit.prototype = {
-
- /**
- * @property {Array}
- */
- transitionQueue: undefined,
-
- /**
- * @property {Array}
- */
- elements: undefined,
-
- /**
- * @property {Object}
- */
- defaults: undefined,
-
- /**
- * Start animation sequence with passed animations.
- *
- * @param {Function} callback
- */
- play: function play(callback) {
- if (typeof callback === 'function') {
- this.transitionQueue.push(function (done) {
- callback();
- done();
- });
- }
-
- this.startAnimation();
-
- return this;
- },
-
- /**
- * Most of the animations follow this default process.
- *
- * @param {from} css or options object containing css
- * @param {to} css or options object containing css
- * @param {delay} delay to wait
- */
- default: function _default(from, to, delay) {
- function step(params, duration, timing) {
- if (params.duration !== undefined) {
- duration = params.duration;
- }
- if (params.timing !== undefined) {
- timing = params.timing;
- }
-
- return {
- css: params.css || params,
- duration: duration,
- timing: timing
- };
- }
-
- return this.saveStyle().queue(step(from, 0, this.defaults.timing)).wait(delay === undefined ? this.defaults.delay : delay).queue(step(to, this.defaults.duration, this.defaults.timing)).restoreStyle();
- },
-
- /**
- * Queue transition animations or other function.
- *
- * e.g. animit(elt).queue({color: 'red'})
- * e.g. animit(elt).queue({color: 'red'}, {duration: 0.4})
- * e.g. animit(elt).queue({css: {color: 'red'}, duration: 0.2})
- *
- * @param {Object|Animit.Transition|Function} transition
- * @param {Object} [options]
- */
- queue: function queue(transition, options) {
- var queue = this.transitionQueue;
-
- if (transition && options) {
- options.css = transition;
- transition = new Animit.Transition(options);
- }
-
- if (!(transition instanceof Function || transition instanceof Animit.Transition)) {
- if (transition.css) {
- transition = new Animit.Transition(transition);
- } else {
- transition = new Animit.Transition({
- css: transition
- });
- }
- }
-
- if (transition instanceof Function) {
- queue.push(transition);
- } else if (transition instanceof Animit.Transition) {
- queue.push(transition.build());
- } else {
- throw new Error('Invalid arguments');
- }
-
- return this;
- },
-
- /**
- * Queue transition animations.
- *
- * @param {Float} seconds
- */
- wait: function wait(seconds) {
- if (seconds > 0) {
- this.transitionQueue.push(function (done) {
- setTimeout(done, 1000 * seconds);
- });
- }
-
- return this;
- },
-
- saveStyle: function saveStyle() {
-
- this.transitionQueue.push(function (done) {
- this.elements.forEach(function (element, index) {
- var css = this.lastStyleAttributeDict[index] = {};
-
- for (var i = 0; i < element.style.length; i++) {
- css[element.style[i]] = element.style[element.style[i]];
- }
- }.bind(this));
- done();
- }.bind(this));
-
- return this;
- },
-
- /**
- * Restore element's style.
- *
- * @param {Object} [options]
- * @param {Float} [options.duration]
- * @param {String} [options.timing]
- * @param {String} [options.transition]
- */
- restoreStyle: function restoreStyle(options) {
- options = options || {};
- var self = this;
-
- if (options.transition && !options.duration) {
- throw new Error('"options.duration" is required when "options.transition" is enabled.');
- }
-
- var transitionName = util.transitionPropertyName;
-
- if (options.transition || options.duration && options.duration > 0) {
- var transitionValue = options.transition || 'all ' + options.duration + 's ' + (options.timing || 'linear');
-
- this.transitionQueue.push(function (done) {
- var elements = this.elements;
- var timeoutId;
-
- var clearTransition = function clearTransition() {
- elements.forEach(function (element) {
- element.style[transitionName] = '';
- });
- };
-
- // add "transitionend" event handler
- var removeListeners = util.onceOnTransitionEnd(elements[0], function () {
- clearTimeout(timeoutId);
- clearTransition();
- done();
- });
-
- // for fail safe.
- timeoutId = setTimeout(function () {
- removeListeners();
- clearTransition();
- done();
- }, options.duration * 1000 * TIMEOUT_RATIO);
-
- // transition and style settings
- elements.forEach(function (element, index) {
-
- var css = self.lastStyleAttributeDict[index];
-
- if (!css) {
- throw new Error('restoreStyle(): The style is not saved. Invoke saveStyle() before.');
- }
-
- self.lastStyleAttributeDict[index] = undefined;
-
- var name;
- for (var i = 0, len = element.style.length; i < len; i++) {
- name = element.style[i];
- if (css[name] === undefined) {
- css[name] = '';
- }
- }
-
- element.style[transitionName] = transitionValue;
-
- _Object$keys(css).forEach(function (key) {
- if (key !== transitionName) {
- element.style[key] = css[key];
- }
- });
-
- element.style[transitionName] = transitionValue;
- });
- });
- } else {
- this.transitionQueue.push(function (done) {
- reset();
- done();
- });
- }
-
- return this;
-
- function reset() {
- // Clear transition animation settings.
- self.elements.forEach(function (element, index) {
- element.style[transitionName] = 'none';
-
- var css = self.lastStyleAttributeDict[index];
-
- if (!css) {
- throw new Error('restoreStyle(): The style is not saved. Invoke saveStyle() before.');
- }
-
- self.lastStyleAttributeDict[index] = undefined;
-
- for (var i = 0, name = ''; i < element.style.length; i++) {
- name = element.style[i];
- if (typeof css[element.style[i]] === 'undefined') {
- css[element.style[i]] = '';
- }
- }
-
- _Object$keys(css).forEach(function (key) {
- element.style[key] = css[key];
- });
- });
- }
- },
-
- /**
- * Start animation sequence.
- */
- startAnimation: function startAnimation() {
- this._dequeueTransition();
-
- return this;
- },
-
- _dequeueTransition: function _dequeueTransition() {
- var transition = this.transitionQueue.shift();
- if (this._currentTransition) {
- throw new Error('Current transition exists.');
- }
- this._currentTransition = transition;
- var self = this;
- var called = false;
-
- var done = function done() {
- if (!called) {
- called = true;
- self._currentTransition = undefined;
- self._dequeueTransition();
- } else {
- throw new Error('Invalid state: This callback is called twice.');
- }
- };
-
- if (transition) {
- transition.call(this, done);
- }
- }
-
- };
-
- /**
- * @param {Animit} arguments
- */
- Animit.runAll = function () /* arguments... */{
- for (var i = 0; i < arguments.length; i++) {
- arguments[i].play();
- }
- };
-
- /**
- * @param {Object} options
- * @param {Float} [options.duration]
- * @param {String} [options.property]
- * @param {String} [options.timing]
- */
- Animit.Transition = function (options) {
- this.options = options || {};
- this.options.duration = this.options.duration || 0;
- this.options.timing = this.options.timing || 'linear';
- this.options.css = this.options.css || {};
- this.options.property = this.options.property || 'all';
- };
-
- Animit.Transition.prototype = {
-
- /**
- * @param {HTMLElement} element
- * @return {Function}
- */
- build: function build() {
-
- if (_Object$keys(this.options.css).length === 0) {
- throw new Error('options.css is required.');
- }
-
- var css = createActualCssProps(this.options.css);
-
- if (this.options.duration > 0) {
- var transitionValue = util.buildTransitionValue(this.options);
- var self = this;
-
- return function (callback) {
- var elements = this.elements;
- var timeout = self.options.duration * 1000 * TIMEOUT_RATIO;
- var timeoutId;
-
- var removeListeners = util.onceOnTransitionEnd(elements[0], function () {
- clearTimeout(timeoutId);
- callback();
- });
-
- timeoutId = setTimeout(function () {
- removeListeners();
- callback();
- }, timeout);
-
- elements.forEach(function (element) {
- element.style[util.transitionPropertyName] = transitionValue;
-
- _Object$keys(css).forEach(function (name) {
- element.style[name] = css[name];
- });
- });
- };
- }
-
- if (this.options.duration <= 0) {
- return function (callback) {
- var elements = this.elements;
-
- elements.forEach(function (element) {
- element.style[util.transitionPropertyName] = '';
-
- _Object$keys(css).forEach(function (name) {
- element.style[name] = css[name];
- });
- });
-
- if (elements.length > 0) {
- util.forceLayoutAtOnce(elements, function () {
- util.batchAnimationFrame(callback);
- });
- } else {
- util.batchAnimationFrame(callback);
- }
- };
- }
-
- function createActualCssProps(css) {
- var result = {};
-
- _Object$keys(css).forEach(function (name) {
- var value = css[name];
-
- if (util.hasCssProperty(name)) {
- result[name] = value;
- return;
- }
-
- var prefixed = util.vendorPrefix + util.capitalize(name);
- if (util.hasCssProperty(prefixed)) {
- result[prefixed] = value;
- } else {
- result[prefixed] = value;
- result[name] = value;
- }
- });
-
- return result;
- }
- }
- };
-
- export default Animit;
|