Comparing version 1.0.0 to 2.0.0
449
animore.js
@@ -16,15 +16,9 @@ (function (global, factory) { | ||
// is it a node list? | ||
if ( | ||
/^\[object (HTMLCollection|NodeList|Object)\]$/ | ||
.test(Object.prototype.toString.call(els)) | ||
&& typeof els.length === 'number' | ||
) | ||
{ return Array.from(els) } | ||
else | ||
if (/^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(els)) && typeof els.length === 'number') return Array.from(els);else | ||
// if it's a single node | ||
// it will be returned as "array" with one single entry | ||
{ return [els] } | ||
return [els]; | ||
} | ||
// this object could be looped out of the box | ||
return els | ||
return els; | ||
} | ||
@@ -39,6 +33,3 @@ | ||
function $(selector, ctx) { | ||
return domToArray(typeof selector === 'string' ? | ||
(ctx || document).querySelectorAll(selector) : | ||
selector | ||
) | ||
return domToArray(typeof selector === 'string' ? (ctx || document).querySelectorAll(selector) : selector); | ||
} | ||
@@ -52,3 +43,5 @@ | ||
*/ | ||
var split = function (l) { return l.split(/\s/); }; | ||
var split = function split(l) { | ||
return l.split(/\s/); | ||
}; | ||
@@ -68,3 +61,17 @@ /** | ||
split(evList).forEach(function (e) { | ||
for (var el of els) el[method](e, cb, options || false); | ||
for (var _iterator = els, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { | ||
var _ref; | ||
if (_isArray) { | ||
if (_i >= _iterator.length) break; | ||
_ref = _iterator[_i++]; | ||
} else { | ||
_i = _iterator.next(); | ||
if (_i.done) break; | ||
_ref = _i.value; | ||
} | ||
var el = _ref; | ||
el[method](e, cb, options || false); | ||
} | ||
}); | ||
@@ -83,3 +90,3 @@ } | ||
manageEvents(els, evList, cb, 'addEventListener', options); | ||
return els | ||
return els; | ||
} | ||
@@ -107,6 +114,192 @@ | ||
manageEvents(els, evList, cb, 'removeEventListener', options); | ||
return els | ||
return els; | ||
} | ||
var OBSERVER_CONFIG = { attributes: true, childList: true, characterData: true }; | ||
function createCommonjsModule(fn, module) { | ||
return module = { exports: {} }, fn(module, module.exports), module.exports; | ||
} | ||
/** | ||
* Converts any DOM node/s to a loopable array | ||
* @param { HTMLElement|NodeList } els - single html element or a node list | ||
* @returns { Object } always a loopable object | ||
*/ | ||
function domToArray$1(els) { | ||
// can this object be already looped? | ||
if (!Array.isArray(els)) { | ||
// is it a node list? | ||
if (els.length) return Array.from(els);else | ||
// if it's a single node | ||
// it will be returned as "array" with one single entry | ||
return [els]; | ||
} | ||
// this object could be looped out of the box | ||
return els; | ||
} | ||
var index$2 = domToArray$1; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
var index = createCommonjsModule(function (module, exports) { | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
function _interopDefault(ex) { | ||
return ex && (typeof ex === 'undefined' ? 'undefined' : _typeof(ex)) === 'object' && 'default' in ex ? ex['default'] : ex; | ||
} | ||
var domToArray = _interopDefault(index$2); | ||
/** | ||
* Normalize the return values, in case of a single value we avoid to return an array | ||
* @param { Array } values - list of values we want to return | ||
* @returns { Array|String|Boolean } either the whole list of values or the single one found | ||
* @private | ||
*/ | ||
var normalize = function normalize(values) { | ||
return values.length === 1 ? values[0] : values; | ||
}; | ||
/** | ||
* Parse all the nodes received to get/remove/check their attributes | ||
* @param { HTMLElement|NodeList|Array } els - DOM node/s to parse | ||
* @param { String|Array } name - name or list of attributes | ||
* @param { String } method - method that will be used to parse the attributes | ||
* @returns { Array|String } result of the parsing in a list or a single value | ||
* @private | ||
*/ | ||
function parseNodes(els, name, method) { | ||
var names = typeof name === 'string' ? [name] : name; | ||
return normalize(domToArray(els).map(function (el) { | ||
return normalize(names.map(function (n) { | ||
return el[method](n); | ||
})); | ||
})); | ||
} | ||
/** | ||
* Set any attribute on a single or a list of DOM nodes | ||
* @param { HTMLElement|NodeList|Array } els - DOM node/s to parse | ||
* @param { String|Object } name - either the name of the attribute to set | ||
* or a list of properties as object key - value | ||
* @param { String } value - the new value of the attribute (optional) | ||
* @returns { HTMLElement|NodeList|Array } the original array of elements passed to this function | ||
* | ||
* @example | ||
* | ||
* import { set } from 'bianco.attr' | ||
* | ||
* const img = document.createElement('img') | ||
* | ||
* set(img, 'width', 100) | ||
* | ||
* // or also | ||
* set(img, { | ||
* width: 300, | ||
* height: 300 | ||
* }) | ||
* | ||
*/ | ||
function set(els, name, value) { | ||
var _ref; | ||
var attrs = (typeof name === 'undefined' ? 'undefined' : _typeof(name)) === 'object' ? name : (_ref = {}, _ref[name] = value, _ref); | ||
var props = Object.keys(attrs); | ||
domToArray(els).forEach(function (el) { | ||
props.forEach(function (prop) { | ||
return el.setAttribute(prop, attrs[prop]); | ||
}); | ||
}); | ||
return els; | ||
} | ||
/** | ||
* Get any attribute from a single or a list of DOM nodes | ||
* @param { HTMLElement|NodeList|Array } els - DOM node/s to parse | ||
* @param { String|Array } name - name or list of attributes to get | ||
* @returns { Array|String } list of the attributes found | ||
* | ||
* @example | ||
* | ||
* import { get } from 'bianco.attr' | ||
* | ||
* const img = document.createElement('img') | ||
* | ||
* get(img, 'width') // => '200' | ||
* | ||
* // or also | ||
* get(img, ['width', 'height']) // => ['200', '300'] | ||
* | ||
* // or also | ||
* get([img1, img2], ['width', 'height']) // => [['200', '300'], ['500', '200']] | ||
*/ | ||
function get(els, name) { | ||
return parseNodes(els, name, 'getAttribute'); | ||
} | ||
/** | ||
* Remove any attribute from a single or a list of DOM nodes | ||
* @param { HTMLElement|NodeList|Array } els - DOM node/s to parse | ||
* @param { String|Array } name - name or list of attributes to remove | ||
* @returns { HTMLElement|NodeList|Array } the original array of elements passed to this function | ||
* | ||
* @example | ||
* | ||
* import { remove } from 'bianco.attr' | ||
* | ||
* remove(img, 'width') // remove the width attribute | ||
* | ||
* // or also | ||
* remove(img, ['width', 'height']) // remove the width and the height attribute | ||
* | ||
* // or also | ||
* remove([img1, img2], ['width', 'height']) // remove the width and the height attribute from both images | ||
*/ | ||
function remove(els, name) { | ||
return parseNodes(els, name, 'removeAttribute'); | ||
} | ||
/** | ||
* Set any attribute on a single or a list of DOM nodes | ||
* @param { HTMLElement|NodeList|Array } els - DOM node/s to parse | ||
* @param { String|Array } name - name or list of attributes to detect | ||
* @returns { Boolean|Array } true or false or an array of boolean values | ||
* @example | ||
* | ||
* import { has } from 'bianco.attr' | ||
* | ||
* has(img, 'width') // false | ||
* | ||
* // or also | ||
* has(img, ['width', 'height']) // => [false, false] | ||
* | ||
* // or also | ||
* has([img1, img2], ['width', 'height']) // => [[false, false], [false, false]] | ||
*/ | ||
function has(els, name) { | ||
return parseNodes(els, name, 'hasAttribute'); | ||
} | ||
var index_next = { | ||
get: get, | ||
set: set, | ||
remove: remove, | ||
has: has | ||
}; | ||
exports.set = set; | ||
exports.get = get; | ||
exports.remove = remove; | ||
exports.has = has; | ||
exports['default'] = index_next; | ||
}); | ||
var index_1 = index.set; | ||
var index_3 = index.remove; | ||
var index_4 = index.has; | ||
var DEFAULT_OPTIONS = { | ||
@@ -120,7 +313,87 @@ duration: 300, | ||
}; | ||
var IS_ANIMATING_ATTR = 'is-animating'; | ||
var TIMER_OFFSET = 5; // ms | ||
var ANIMORE_STRUCT = Object.seal({ | ||
/** | ||
* Cleanup function triggered when the animations will be complete | ||
* @returns { ANIMORE_STRUCT } self | ||
*/ | ||
clear: function clear() { | ||
removeEvents.call(this); | ||
index_3(this.el, IS_ANIMATING_ATTR); | ||
style(this.el, { | ||
opacity: null, | ||
transition: null, | ||
transform: null, | ||
transformOrigin: null, | ||
willChange: null | ||
}); | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
this.opts[args[0].type === 'transitioncancel' ? 'onCancel' : 'onEnd'](args); | ||
return this; | ||
}, | ||
/** | ||
* Store the element initial properties | ||
* @returns { ANIMORE_STRUCT } self | ||
*/ | ||
stash: function stash() { | ||
this.props.old = inspect(this.el); | ||
return this; | ||
}, | ||
/** | ||
* Apply a flip animation | ||
* @returns { ANIMORE_STRUCT } self | ||
*/ | ||
apply: function apply() { | ||
var _this = this; | ||
if (!this.props.old) throw new Error('Make sure to trigger animore.stash() before any animore.apply()'); | ||
this.props.new = inspect(this.el); | ||
index_1(this.el, IS_ANIMATING_ATTR, IS_ANIMATING_ATTR); | ||
addEvents.call(this); | ||
flip(this.el, this.props, this.opts); | ||
Object.assign(this.props.old, this.props.new); | ||
// make sure the transition end will always be triggered | ||
// this will enable the testing of this script also in a node environment | ||
setTimeout(function () { | ||
if (index_4(_this.el, IS_ANIMATING_ATTR)) { | ||
_this.el.dispatchEvent(new Event('transitionend')); | ||
} | ||
}, this.opts.duration + TIMER_OFFSET); | ||
return this; | ||
} | ||
}); | ||
// noop function | ||
function noop() {} | ||
/** | ||
* Add all the event listeners to the animore.el | ||
* @this animore | ||
*/ | ||
function addEvents() { | ||
add(this.el, 'transitionstart', this.opts.onStart); | ||
add(this.el, 'transitionend transitioncancel', this.clear); | ||
} | ||
/** | ||
* Remove all the event listeners from the animore.el | ||
* @this animore | ||
*/ | ||
function removeEvents() { | ||
remove(this.el, 'transitionstart', this.opts.onStart); | ||
remove(this.el, 'transitionend transitioncancel', this.clear); | ||
} | ||
/** | ||
* Inspect the transitionable properties of a DOM node | ||
@@ -131,7 +404,8 @@ * @param { HTMLElement } el - DOM node to inspect | ||
function inspect(el) { | ||
var ref = el.getBoundingClientRect(); | ||
var left = ref.left; | ||
var top = ref.top; | ||
var height = ref.height; | ||
var width = ref.width; | ||
var _el$getBoundingClient = el.getBoundingClientRect(), | ||
left = _el$getBoundingClient.left, | ||
top = _el$getBoundingClient.top, | ||
height = _el$getBoundingClient.height, | ||
width = _el$getBoundingClient.width; | ||
return { | ||
@@ -143,3 +417,3 @@ left: left, | ||
opacity: +(el.style.opacity || 1) | ||
} | ||
}; | ||
} | ||
@@ -161,12 +435,11 @@ | ||
* @param { HTMLElement } el - DOM element we want to animate | ||
* @param { Object } newProps - new element properties | ||
* @param { Object } prevProps - old element properties | ||
* @param { Object } props - object containing the old and new properties to animate | ||
* @param { Object } opts - animation options | ||
*/ | ||
function flip(el, newProps, prevProps, opts) { | ||
function flip(el, props, opts) { | ||
style(el, { | ||
opacity: prevProps.opacity, | ||
opacity: props.old.opacity, | ||
willChange: 'transform', | ||
transformOrigin: '0 0', | ||
transform: ("\n translateX(" + (prevProps.left - newProps.left) + "px)\n translateY(" + (prevProps.top - newProps.top) + "px)\n scaleX(" + (prevProps.width / newProps.width) + ")\n scaleY(" + (prevProps.height / newProps.height) + ")\n") | ||
transform: '\n translateX(' + (props.old.left - props.new.left) + 'px)\n translateY(' + (props.old.top - props.new.top) + 'px)\n scaleX(' + props.old.width / props.new.width + ')\n scaleY(' + props.old.height / props.new.height + ')\n' | ||
}); | ||
@@ -178,5 +451,5 @@ | ||
style(el, { | ||
opacity: newProps.opacity, | ||
transition: ("transform " + (opts.duration) + "ms " + (opts.easing) + " " + (opts.delay) + "ms"), | ||
transform: "\n translateX(0)\n translateY(0)\n scaleX(1)\n scaleY(1)\n" | ||
opacity: props.new.opacity, | ||
transition: 'transform ' + opts.duration + 'ms ' + opts.easing + ' ' + opts.delay + 'ms', | ||
transform: '\n translateX(0)\n translateY(0)\n scaleX(1)\n scaleY(1)\n' | ||
}); | ||
@@ -186,89 +459,38 @@ } | ||
/** | ||
* Create a single animore object | ||
* Return an object linked to the context prototype but with all the methods bound to it | ||
* @param { Object } src - object that will receive our bound methods | ||
* @param { Array } methods - array containing all the methods we want to bind | ||
* @param { * } context (optional) - context where we want to bind our methods | ||
* @returns { Object } new object linked to the src prototype | ||
*/ | ||
function bind(src, methods, context) { | ||
if (!context) context = src; | ||
methods.forEach(function (method) { | ||
return src[method] = src[method].bind(context); | ||
}); | ||
return Object.create(src); | ||
} | ||
/** | ||
* Factory funciton to create a single animore object | ||
* @param { HTMLElement } el - DOM node we need to animate | ||
* @param { Object } opts - animations options | ||
* @returns { Object } ret | ||
* @returns { HTMLElement } ret.el - DOM element originally received | ||
* @returns { Function } ret.destroy - unsubscribe function | ||
* @returns { Object } animore - animore object | ||
*/ | ||
function create(el, opts) { | ||
var isFrozen = false; | ||
var isAnimating = false; | ||
function create(el) { | ||
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var | ||
props = inspect(el), | ||
// create an observer instance | ||
observer = new MutationObserver(apply); | ||
observer.observe(el, OBSERVER_CONFIG); | ||
function removeEvents() { | ||
remove(el, 'transitionstart', opts.onStart); | ||
remove(el, 'transitionend transitioncancel', cleanup); | ||
} | ||
function addEvents() { | ||
add(el, 'transitionstart', opts.onStart); | ||
add(el, 'transitionend transitioncancel', cleanup); | ||
} | ||
function apply() { | ||
if (isFrozen || isAnimating) { return } | ||
isAnimating = true; | ||
addEvents(); | ||
var newProps = inspect(el); | ||
flip(el, newProps, props, opts); | ||
Object.assign(props, newProps); | ||
// make sure the transition end will always be triggered | ||
// this will enable the testing of this script also in a node environment | ||
setTimeout(function () { | ||
if (isAnimating) { | ||
el.dispatchEvent(new Event('transitionend')); | ||
} | ||
}, opts.duration + TIMER_OFFSET); | ||
} | ||
// cleanup function triggered when the animations are complete | ||
function cleanup() { | ||
var args = [], len = arguments.length; | ||
while ( len-- ) args[ len ] = arguments[ len ]; | ||
removeEvents(); | ||
style(el, { | ||
opacity: null, | ||
transition: null, | ||
transform: null, | ||
transformOrigin: null, | ||
willChange: null | ||
}); | ||
opts[args[0].type === 'transitioncancel' ? 'onCancel' : 'onEnd'].apply(null, args); | ||
requestAnimationFrame(function () { return isAnimating = false; }); | ||
} | ||
return { | ||
var animore = bind(Object.assign({}, ANIMORE_STRUCT, { | ||
el: el, | ||
freeze: function freeze() { | ||
isFrozen = true; | ||
return this | ||
}, | ||
unfreeze: function unfreeze() { | ||
isFrozen = false; | ||
return this | ||
}, | ||
apply: function apply$1() { | ||
apply(); | ||
return this | ||
}, | ||
destroy: function destroy() { | ||
removeEvents(); | ||
observer.disconnect(); | ||
return this | ||
opts: bind(Object.assign({}, DEFAULT_OPTIONS, opts), ['onStart', 'onEnd', 'onCancel'], animore), | ||
props: { | ||
old: null, | ||
new: null | ||
} | ||
} | ||
}), ['clear', 'stash', 'apply']); | ||
return Object.seal(animore); | ||
} | ||
/** | ||
* Returns always an array containing | ||
* the destroy method to disconnect the MutationObserver instances created | ||
* and the result of the DOM query first argument of this function | ||
* Returns always an array containing all the animore objects | ||
* @param { Array|String|HTMLElement } el - element/s we want to animate | ||
@@ -285,6 +507,5 @@ * @param { Object } opts - options object | ||
function animore(el, opts) { | ||
if ( opts === void 0 ) opts = {}; | ||
opts = Object.assign({}, DEFAULT_OPTIONS, opts); | ||
return $(el).map(function (e) { return create(e, opts); }) | ||
return $(el).map(function (e) { | ||
return create(e, opts); | ||
}); | ||
} | ||
@@ -291,0 +512,0 @@ |
206
lib/index.js
import $ from 'bianco.query' | ||
import { add as addEvent, remove as removeEvent } from 'bianco.events' | ||
import { has as hasAttr, set as setAttr, remove as removeAttr } from 'bianco.attr' | ||
const OBSERVER_CONFIG = { attributes: true, childList: true, characterData: true } | ||
const DEFAULT_OPTIONS = { | ||
@@ -13,7 +13,78 @@ duration: 300, | ||
} | ||
const IS_ANIMATING_ATTR = 'is-animating' | ||
const TIMER_OFFSET = 5 // ms | ||
const ANIMORE_STRUCT = Object.seal({ | ||
/** | ||
* Cleanup function triggered when the animations will be complete | ||
* @returns { ANIMORE_STRUCT } self | ||
*/ | ||
clear(...args) { | ||
removeEvents.call(this) | ||
removeAttr(this.el, IS_ANIMATING_ATTR) | ||
style(this.el, { | ||
opacity: null, | ||
transition: null, | ||
transform: null, | ||
transformOrigin: null, | ||
willChange: null | ||
}) | ||
this.opts[args[0].type === 'transitioncancel' ? 'onCancel' : 'onEnd'](args) | ||
return this | ||
}, | ||
/** | ||
* Store the element initial properties | ||
* @returns { ANIMORE_STRUCT } self | ||
*/ | ||
stash() { | ||
this.props.old = inspect(this.el) | ||
return this | ||
}, | ||
/** | ||
* Apply a flip animation | ||
* @returns { ANIMORE_STRUCT } self | ||
*/ | ||
apply() { | ||
if (!this.props.old) throw new Error('Make sure to trigger animore.stash() before any animore.apply()') | ||
this.props.new = inspect(this.el) | ||
setAttr(this.el, IS_ANIMATING_ATTR, IS_ANIMATING_ATTR) | ||
addEvents.call(this) | ||
flip(this.el, this.props, this.opts) | ||
Object.assign(this.props.old, this.props.new) | ||
// make sure the transition end will always be triggered | ||
// this will enable the testing of this script also in a node environment | ||
setTimeout(() => { | ||
if (hasAttr(this.el, IS_ANIMATING_ATTR)) { | ||
this.el.dispatchEvent(new Event('transitionend')) | ||
} | ||
}, this.opts.duration + TIMER_OFFSET) | ||
return this | ||
} | ||
}) | ||
// noop function | ||
function noop() {} | ||
/** | ||
* Add all the event listeners to the animore.el | ||
* @this animore | ||
*/ | ||
function addEvents() { | ||
addEvent(this.el, 'transitionstart', this.opts.onStart) | ||
addEvent(this.el, 'transitionend transitioncancel', this.clear) | ||
} | ||
/** | ||
* Remove all the event listeners from the animore.el | ||
* @this animore | ||
*/ | ||
function removeEvents() { | ||
removeEvent(this.el, 'transitionstart', this.opts.onStart) | ||
removeEvent(this.el, 'transitionend transitioncancel', this.clear) | ||
} | ||
/** | ||
* Inspect the transitionable properties of a DOM node | ||
@@ -48,16 +119,15 @@ * @param { HTMLElement } el - DOM node to inspect | ||
* @param { HTMLElement } el - DOM element we want to animate | ||
* @param { Object } newProps - new element properties | ||
* @param { Object } prevProps - old element properties | ||
* @param { Object } props - object containing the old and new properties to animate | ||
* @param { Object } opts - animation options | ||
*/ | ||
function flip(el, newProps, prevProps, opts) { | ||
function flip(el, props, opts) { | ||
style(el, { | ||
opacity: prevProps.opacity, | ||
opacity: props.old.opacity, | ||
willChange: 'transform', | ||
transformOrigin: '0 0', | ||
transform: ` | ||
translateX(${prevProps.left - newProps.left}px) | ||
translateY(${prevProps.top - newProps.top}px) | ||
scaleX(${prevProps.width / newProps.width}) | ||
scaleY(${prevProps.height / newProps.height}) | ||
translateX(${props.old.left - props.new.left}px) | ||
translateY(${props.old.top - props.new.top}px) | ||
scaleX(${props.old.width / props.new.width}) | ||
scaleY(${props.old.height / props.new.height}) | ||
` | ||
@@ -70,3 +140,3 @@ }) | ||
style(el, { | ||
opacity: newProps.opacity, | ||
opacity: props.new.opacity, | ||
transition: `transform ${opts.duration}ms ${opts.easing} ${opts.delay}ms`, | ||
@@ -83,86 +153,45 @@ transform: ` | ||
/** | ||
* Create a single animore object | ||
* Return an object linked to the context prototype but with all the methods bound to it | ||
* @param { Object } src - object that will receive our bound methods | ||
* @param { Array } methods - array containing all the methods we want to bind | ||
* @param { * } context (optional) - context where we want to bind our methods | ||
* @returns { Object } new object linked to the src prototype | ||
*/ | ||
function bind(src, methods, context) { | ||
if (!context) context = src | ||
methods.forEach(method => src[method] = src[method].bind(context)) | ||
return Object.create(src) | ||
} | ||
/** | ||
* Factory funciton to create a single animore object | ||
* @param { HTMLElement } el - DOM node we need to animate | ||
* @param { Object } opts - animations options | ||
* @returns { Object } ret | ||
* @returns { HTMLElement } ret.el - DOM element originally received | ||
* @returns { Function } ret.destroy - unsubscribe function | ||
* @returns { Object } animore - animore object | ||
*/ | ||
function create(el, opts) { | ||
let isFrozen = false | ||
let isAnimating = false | ||
const | ||
props = inspect(el), | ||
// create an observer instance | ||
observer = new MutationObserver(apply) | ||
observer.observe(el, OBSERVER_CONFIG) | ||
function removeEvents() { | ||
removeEvent(el, 'transitionstart', opts.onStart) | ||
removeEvent(el, 'transitionend transitioncancel', cleanup) | ||
} | ||
function addEvents() { | ||
addEvent(el, 'transitionstart', opts.onStart) | ||
addEvent(el, 'transitionend transitioncancel', cleanup) | ||
} | ||
function apply() { | ||
if (isFrozen || isAnimating) return | ||
isAnimating = true | ||
addEvents() | ||
const newProps = inspect(el) | ||
flip(el, newProps, props, opts) | ||
Object.assign(props, newProps) | ||
// make sure the transition end will always be triggered | ||
// this will enable the testing of this script also in a node environment | ||
setTimeout(() => { | ||
if (isAnimating) { | ||
el.dispatchEvent(new Event('transitionend')) | ||
function create(el, opts = {}) { | ||
const animore = bind( | ||
Object.assign({}, ANIMORE_STRUCT, { | ||
el, | ||
opts: bind( | ||
Object.assign( | ||
{}, | ||
DEFAULT_OPTIONS, | ||
opts | ||
), | ||
['onStart', 'onEnd', 'onCancel'], | ||
animore | ||
), | ||
props: { | ||
old: null, | ||
new: null | ||
} | ||
}, opts.duration + TIMER_OFFSET) | ||
} | ||
// cleanup function triggered when the animations are complete | ||
function cleanup(...args) { | ||
removeEvents() | ||
style(el, { | ||
opacity: null, | ||
transition: null, | ||
transform: null, | ||
transformOrigin: null, | ||
willChange: null | ||
}) | ||
opts[args[0].type === 'transitioncancel' ? 'onCancel' : 'onEnd'].apply(null, args) | ||
requestAnimationFrame(() => isAnimating = false) | ||
} | ||
return { | ||
el, | ||
freeze() { | ||
isFrozen = true | ||
return this | ||
}, | ||
unfreeze() { | ||
isFrozen = false | ||
return this | ||
}, | ||
apply() { | ||
apply() | ||
return this | ||
}, | ||
destroy() { | ||
removeEvents() | ||
observer.disconnect() | ||
return this | ||
} | ||
} | ||
}), | ||
['clear', 'stash', 'apply'] | ||
) | ||
return Object.seal(animore) | ||
} | ||
/** | ||
* Returns always an array containing | ||
* the destroy method to disconnect the MutationObserver instances created | ||
* and the result of the DOM query first argument of this function | ||
* Returns always an array containing all the animore objects | ||
* @param { Array|String|HTMLElement } el - element/s we want to animate | ||
@@ -178,5 +207,4 @@ * @param { Object } opts - options object | ||
*/ | ||
export default function animore(el, opts = {}) { | ||
opts = Object.assign({}, DEFAULT_OPTIONS, opts) | ||
export default function animore(el, opts) { | ||
return $(el).map(e => create(e, opts)) | ||
} |
{ | ||
"name": "animore", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "Animore makes DOM state transitions easier", | ||
@@ -15,2 +15,3 @@ "main": "animore.js", | ||
"devDependencies": { | ||
"babel-preset-env": "^1.5.0", | ||
"eslint": "^3.19.0", | ||
@@ -20,6 +21,4 @@ "eslint-config-riot": "^1.0.0", | ||
"jsdom-global": "3.0.2", | ||
"mutationobserver-shim": "^0.3.2", | ||
"raf": "^3.3.2", | ||
"rollup": "^0.41.6", | ||
"rollup-plugin-buble": "^0.15.0", | ||
"rollup-plugin-babel": "^2.7.1", | ||
"rollup-plugin-commonjs": "^8.0.2", | ||
@@ -31,2 +30,3 @@ "rollup-plugin-node-resolve": "^3.0.0" | ||
"dependencies": { | ||
"bianco.attr": "0.0.2", | ||
"bianco.events": "0.0.5", | ||
@@ -33,0 +33,0 @@ "bianco.query": "0.0.6" |
# animore | ||
Animore makes DOM state transitions easier | ||
It uses internally the [`MutationObserver`](https://developer.mozilla.org/it/docs/Web/API/MutationObserver) API to determinate whether a DOM node should be transitioned to a different state. It was inspired by [riot-animore](https://github.com/riot/animore) and works thanks to the [flip technique](https://aerotwist.com/blog/flip-your-animations/) by Paul Lewis | ||
It uses internally the css transitions to animate 2 different states of the same DOM node. It was inspired by [riot-animore](https://github.com/riot/animore) and works thanks to the [flip technique](https://aerotwist.com/blog/flip-your-animations/) by Paul Lewis | ||
@@ -12,5 +12,6 @@ [![Build Status][travis-image]][travis-url] | ||
- [the box](https://cdn.rawgit.com/GianlucaGuarini/animore/master/demos/box.html) | ||
- [the list](https://cdn.rawgit.com/GianlucaGuarini/animore/master/demos/list.html) | ||
- [textarea](https://cdn.rawgit.com/GianlucaGuarini/animore/master/demos/textarea.html) | ||
- [The Gallery](https://cdn.rawgit.com/GianlucaGuarini/animore/master/demos/gallery.html) | ||
- [The Box](https://cdn.rawgit.com/GianlucaGuarini/animore/master/demos/box.html) | ||
- [The List](https://cdn.rawgit.com/GianlucaGuarini/animore/master/demos/list.html) | ||
- [The Textarea](https://cdn.rawgit.com/GianlucaGuarini/animore/master/demos/textarea.html) | ||
@@ -46,31 +47,25 @@ # Installation | ||
## Simple automatic transitions | ||
## Simple transitions | ||
You can pass a query or a DOM node to `animore` and it will start watching its changes through a `MutationObserver` to trigger automatically the transitions. | ||
You can pass a query or a DOM node to `animore` in the following way: | ||
```js | ||
const anima = animore('.my-div')[0] // animore returns always an array! | ||
const animaQuery = animore('.my-div')[0] // animore returns always an array! | ||
const animaNode = animore(myDiv)[0] // DOM nodes are also valid | ||
const animaList = animore([myDiv, myUl]) // array are also valid | ||
const animaNodeList = animore(myUl.children) // NodeList are valid as well | ||
anima.el.style.marginTop = '300px' | ||
// animore will autimatically detect this change and transition the y position of the `div` | ||
``` | ||
## Manual transitions | ||
Remeber to use `stash` and `apply` to create your transitions | ||
You can temporary `freeze` the watcher to trigger manually multiple transitions at same time: | ||
```js | ||
const anima = animore('.my-div')[0] | ||
anima.freeze() | ||
anima.stash() // store the previous DOM position | ||
anima.el.style.marginTop = '300px' | ||
anima.el.style.marginLeft = '500px' | ||
anima.unfreze().apply() | ||
// animore will autimatically detect this change and transition the y position of the `div` | ||
anima.apply() // apply the transition | ||
``` | ||
## Options | ||
@@ -93,35 +88,22 @@ | ||
Any animore function will return an object with the following properties | ||
Any animore call will return an object with the following properties | ||
## animore.destroy | ||
## animore.stash | ||
Remove the DOM events disconnecting the MutationObserver internally created | ||
Store the current DOM node position and size | ||
### @returns self | ||
__@returns self__ | ||
## animore.apply | ||
Apply manually an animation comparing the current DOM node state with its previous state | ||
Apply the transition comparing the current DOM node state with its previous state (it can be called only after a `stash`) | ||
### @returns self | ||
__@returns self__ | ||
## animore.freeze | ||
Freeze temporarily all the MutationObserver automatic updates | ||
### @returns self | ||
## animore.unfreeze | ||
Re enable again the automatic transitions updates | ||
### @returns self | ||
## animore.el | ||
Reference to the DOM node observed | ||
Reference to the DOM node queried | ||
### @returns HTMLElement | ||
__@returns HTMLElement__ | ||
[travis-image]:https://img.shields.io/travis/GianlucaGuarini/animore.svg?style=flat-square | ||
@@ -128,0 +110,0 @@ [travis-url]:https://travis-ci.org/GianlucaGuarini/animore |
import commonjs from 'rollup-plugin-commonjs' | ||
import resolve from 'rollup-plugin-node-resolve' | ||
import buble from 'rollup-plugin-buble' | ||
import babel from 'rollup-plugin-babel' | ||
@@ -9,5 +9,13 @@ export default { | ||
moduleName: 'animore', | ||
plugins: [ resolve(), commonjs(), buble({ | ||
transforms: { forOf: false } | ||
}) ] | ||
plugins: [ resolve(), commonjs(), babel({ | ||
presets: [ | ||
['env', { | ||
modules: false, | ||
loose: true, | ||
targets: { | ||
browsers: ['last 2 versions', 'safari >= 7'] | ||
} | ||
}] | ||
] | ||
})] | ||
} |
const assert = require('assert') | ||
const raf = require('raf') | ||
// node js DOM polyfills | ||
require('jsdom-global')() | ||
require('mutationobserver-shim') | ||
global.MutationObserver = window.MutationObserver | ||
global.requestAnimationFrame = raf | ||
global.cancelAnimationFrame = raf.cancel | ||
// require the lib | ||
@@ -22,25 +17,15 @@ const animore = require('../') | ||
assert.equal(el.style.transform, null) | ||
a.destroy() | ||
done() | ||
} | ||
})[0] | ||
a.stash() | ||
el.style.marginTop = '20px' | ||
a.apply() | ||
}) | ||
it('The destroy method will properly unsubscribe all the listeners', (done) => { | ||
it('It throws if no stash was called before apply', () => { | ||
const el = dummyEl() | ||
const a = animore(el, { | ||
onEnd() { | ||
assert.ok(false) | ||
} | ||
})[0] | ||
el.style.marginTop = '20px' | ||
a.destroy() | ||
setTimeout(() => { | ||
done() | ||
}, 500) | ||
const a = animore(el)[0] | ||
assert.throws(a.apply, Error) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
37320
9
15
679
3
115
1
+ Addedbianco.attr@0.0.2
+ Addedbianco.attr@0.0.2(transitive)
+ Addedbianco.dom-to-array@0.0.4(transitive)