// Patch IE9 and below try { document.createElement('DIV').style.setProperty('opacity', 0, ''); } catch (error) { CSSStyleDeclaration.prototype.getProperty = function(a) { return this.getAttribute(a); }; CSSStyleDeclaration.prototype.setProperty = function(a,b) { return this.setAttribute(a, b + ''); }; CSSStyleDeclaration.prototype.removeProperty = function(a) { return this.removeAttribute(a); }; } /** * Module Dependencies. */ var Emitter = require('component-emitter'); var query = require('component-query'); var after = require('after-transition'); var has3d = require('has-translate3d'); var ease = require('css-ease'); /** * CSS Translate */ var translate = has3d ? ['translate3d(', ', 0)'] : ['translate(', ')']; /** * Export `Move` */ module.exports = Move; /** * Get computed style. */ var style = window.getComputedStyle || window.currentStyle; /** * Library version. */ Move.version = '0.5.0'; /** * Export `ease` */ Move.ease = ease; /** * Defaults. * * `duration` - default duration of 500ms * */ Move.defaults = { duration: 500 }; /** * Default element selection utilized by `move(selector)`. * * Override to implement your own selection, for example * with jQuery one might write: * * move.select = function(selector) { * return jQuery(selector).get(0); * }; * * @param {Object|String} selector * @return {Element} * @api public */ Move.select = function(selector){ if ('string' != typeof selector) return selector; return query(selector); }; /** * Initialize a new `Move` with the given `el`. * * @param {Element} el * @api public */ function Move(el) { if (!(this instanceof Move)) return new Move(el); if ('string' == typeof el) el = query(el); if (!el) throw new TypeError('Move must be initialized with element or selector'); this.el = el; this._props = {}; this._rotate = 0; this._transitionProps = []; this._transforms = []; this.duration(Move.defaults.duration) }; /** * Inherit from `EventEmitter.prototype`. */ Emitter(Move.prototype); /** * Buffer `transform`. * * @param {String} transform * @return {Move} for chaining * @api private */ Move.prototype.transform = function(transform){ this._transforms.push(transform); return this; }; /** * Skew `x` and `y`. * * @param {Number} x * @param {Number} y * @return {Move} for chaining * @api public */ Move.prototype.skew = function(x, y){ return this.transform('skew(' + x + 'deg, ' + (y || 0) + 'deg)'); }; /** * Skew x by `n`. * * @param {Number} n * @return {Move} for chaining * @api public */ Move.prototype.skewX = function(n){ return this.transform('skewX(' + n + 'deg)'); }; /** * Skew y by `n`. * * @param {Number} n * @return {Move} for chaining * @api public */ Move.prototype.skewY = function(n){ return this.transform('skewY(' + n + 'deg)'); }; /** * Translate `x` and `y` axis. * * @param {Number|String} x * @param {Number|String} y * @return {Move} for chaining * @api public */ Move.prototype.translate = Move.prototype.to = function(x, y){ return this.transform(translate.join('' + fixUnits(x) + ', ' + fixUnits(y || 0))); }; /** * Translate on the x axis to `n`. * * @param {Number|String} n * @return {Move} for chaining * @api public */ Move.prototype.translateX = Move.prototype.x = function(n){ return this.transform('translateX(' + fixUnits(n) + ')'); }; /** * Translate on the y axis to `n`. * * @param {Number|String} n * @return {Move} for chaining * @api public */ Move.prototype.translateY = Move.prototype.y = function(n){ return this.transform('translateY(' + fixUnits(n) + ')'); }; /** * Scale the x and y axis by `x`, or * individually scale `x` and `y`. * * @param {Number} x * @param {Number} y * @return {Move} for chaining * @api public */ Move.prototype.scale = function(x, y){ return this.transform('scale(' + x + ', ' + (y || x) + ')'); }; /** * Scale x axis by `n`. * * @param {Number} n * @return {Move} for chaining * @api public */ Move.prototype.scaleX = function(n){ return this.transform('scaleX(' + n + ')') }; /** * Apply a matrix transformation * * @param {Number} m11 A matrix coefficient * @param {Number} m12 A matrix coefficient * @param {Number} m21 A matrix coefficient * @param {Number} m22 A matrix coefficient * @param {Number} m31 A matrix coefficient * @param {Number} m32 A matrix coefficient * @return {Move} for chaining * @api public */ Move.prototype.matrix = function(m11, m12, m21, m22, m31, m32){ return this.transform('matrix(' + [m11,m12,m21,m22,m31,m32].join(',') + ')'); }; /** * Scale y axis by `n`. * * @param {Number} n * @return {Move} for chaining * @api public */ Move.prototype.scaleY = function(n){ return this.transform('scaleY(' + n + ')') }; /** * Rotate `n` degrees. * * @param {Number} n * @return {Move} for chaining * @api public */ Move.prototype.rotate = function(n){ return this.transform('rotate(' + n + 'deg)'); }; /** * Set transition easing function to to `fn` string. * * When: * * - null "ease" is used * - "in" "ease-in" is used * - "out" "ease-out" is used * - "in-out" "ease-in-out" is used * * @param {String} fn * @return {Move} for chaining * @api public */ Move.prototype.ease = function(fn){ fn = ease[fn] || fn || 'ease'; return this.setVendorProperty('transition-timing-function', fn); }; /** * Set animation properties * * @param {String} name * @param {Object} props * @return {Move} for chaining * @api public */ Move.prototype.animate = function(name, props){ for (var i in props){ if (props.hasOwnProperty(i)){ this.setVendorProperty('animation-' + i, props[i]) } } return this.setVendorProperty('animation-name', name); } /** * Set duration to `n`. * * @param {Number|String} n * @return {Move} for chaining * @api public */ Move.prototype.duration = function(n){ n = this._duration = 'string' == typeof n ? parseFloat(n) * 1000 : n; return this.setVendorProperty('transition-duration', n + 'ms'); }; /** * Delay the animation by `n`. * * @param {Number|String} n * @return {Move} for chaining * @api public */ Move.prototype.delay = function(n){ n = 'string' == typeof n ? parseFloat(n) * 1000 : n; return this.setVendorProperty('transition-delay', n + 'ms'); }; /** * Set `prop` to `val`, deferred until `.end()` is invoked. * * @param {String} prop * @param {String} val * @return {Move} for chaining * @api public */ Move.prototype.setProperty = function(prop, val){ this._props[prop] = val; return this; }; /** * Set a vendor prefixed `prop` with the given `val`. * * @param {String} prop * @param {String} val * @return {Move} for chaining * @api public */ Move.prototype.setVendorProperty = function(prop, val){ this.setProperty('-webkit-' + prop, val); this.setProperty('-moz-' + prop, val); this.setProperty('-ms-' + prop, val); this.setProperty('-o-' + prop, val); return this; }; /** * Set `prop` to `value`, deferred until `.end()` is invoked * and adds the property to the list of transition props. * * @param {String} prop * @param {String} val * @return {Move} for chaining * @api public */ Move.prototype.set = function(prop, val){ this.transition(prop); this._props[prop] = val; return this; }; /** * Increment `prop` by `val`, deferred until `.end()` is invoked * and adds the property to the list of transition props. * * @param {String} prop * @param {Number} val * @return {Move} for chaining * @api public */ Move.prototype.add = function(prop, val){ if (!style) return; var self = this; return this.on('start', function(){ var curr = parseInt(self.current(prop), 10); self.set(prop, curr + val + 'px'); }); }; /** * Decrement `prop` by `val`, deferred until `.end()` is invoked * and adds the property to the list of transition props. * * @param {String} prop * @param {Number} val * @return {Move} for chaining * @api public */ Move.prototype.sub = function(prop, val){ if (!style) return; var self = this; return this.on('start', function(){ var curr = parseInt(self.current(prop), 10); self.set(prop, curr - val + 'px'); }); }; /** * Get computed or "current" value of `prop`. * * @param {String} prop * @return {String} * @api public */ Move.prototype.current = function(prop){ return style(this.el).getPropertyValue(prop); }; /** * Add `prop` to the list of internal transition properties. * * @param {String} prop * @return {Move} for chaining * @api private */ Move.prototype.transition = function(prop){ if (!this._transitionProps.indexOf(prop)) return this; this._transitionProps.push(prop); return this; }; /** * Commit style properties, aka apply them to `el.style`. * * @return {Move} for chaining * @see Move#end() * @api private */ Move.prototype.applyProperties = function(){ for (var prop in this._props) { this.el.style.setProperty(prop, this._props[prop], ''); } return this; }; /** * Re-select element via `selector`, replacing * the current element. * * @param {String} selector * @return {Move} for chaining * @api public */ Move.prototype.move = Move.prototype.select = function(selector){ this.el = Move.select(selector); return this; }; /** * Defer the given `fn` until the animation * is complete. `fn` may be one of the following: * * - a function to invoke * - an instanceof `Move` to call `.end()` * - nothing, to return a clone of this `Move` instance for chaining * * @param {Function|Move} fn * @return {Move} for chaining * @api public */ Move.prototype.then = function(fn){ // invoke .end() if (fn instanceof Move) { this.on('end', function(){ fn.end(); }); // callback } else if ('function' == typeof fn) { this.on('end', fn); // chain } else { var clone = new Move(this.el); clone._transforms = this._transforms.slice(0); this.then(clone); clone.parent = this; return clone; } return this; }; /** * Pop the move context. * * @return {Move} parent Move * @api public */ Move.prototype.pop = function(){ return this.parent; }; /** * Reset duration. * * @return {Move} * @api public */ Move.prototype.reset = function(){ this.el.style.webkitTransitionDuration = this.el.style.mozTransitionDuration = this.el.style.msTransitionDuration = this.el.style.oTransitionDuration = ''; return this; }; /** * Start animation, optionally calling `fn` when complete. * * @param {Function} fn * @return {Move} for chaining * @api public */ Move.prototype.end = function(fn){ var self = this; // emit "start" event this.emit('start'); // transforms if (this._transforms.length) { this.setVendorProperty('transform', this._transforms.join(' ')); } // transition properties this.setVendorProperty('transition-properties', this._transitionProps.join(', ')); this.applyProperties(); // callback given if (fn) this.then(fn); // emit "end" when complete after.once(this.el, function(){ self.reset(); self.emit('end'); }); return this; }; /** * Fix value units * * @param {Number|String} val * @return {String} * @api private */ function fixUnits(val) { return 'string' === typeof val && isNaN(+val) ? val : val + 'px'; }