2023-03-14 14:47:50 +01:00

599 lines
11 KiB
JavaScript

// 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';
}