toolshed-8.x-1.x-dev/assets/Animate.es6.js
assets/Animate.es6.js
/**
* Add animation methods to the Element wrapper class.
*/
(({ Toolshed: ts }) => {
/**
* Element has completed its CSS transition.
*
* @param {TransitionEvent} e
* The event object with the transition information.
*/
function onTransitionEnd(e) {
const prop = e.propertyName;
if (prop === 'height' || prop === 'width') {
const { style } = this;
if (style[prop] === '0px') {
style.display = 'none';
}
else {
style.display = '';
style.boxSizing = '';
style[prop] = '';
}
style.overflow = '';
style.transition = '';
// Update statuses and check for any pending animations.
this.isAnimating = false;
const item = this.animateQueue.pop();
if (item) {
item[0].call(this, item[1]);
}
}
};
// Capture the current destructor to call in our override.
const destroy = ts.Element.prototype.destroy;
// Apply basic animation methods to the Element prototype.
Object.assign(ts.Element.prototype, {
destroy(detach) {
// Terminate any in progress animations.
if (this.isAnimating && this.el.Animation) this.el.Animation.cancel();
if (this.animateQueue) delete this.animateQueue;
// Call original destructor.
destroy.call(this, detach);
},
/**
* Collapse the height of the element (animate element close vertically).
*
* @param {float} duration A time in seconds for the transition to take place.
*/
collapse(duration = '0.5s') {
if (this.animating) this._queueAnimation(this.collapse, duration);
else {
// Capture the element height before starting the animation.
const height = this.el.clientHeight;
this._animate(
{
// Browsers are not able to perform transitions on elements with
// auto-height, because the browser doesn't have a value to transition
// between. Set the current height, wait for the an animation frame
// from the change to take effect and then set the transition.
height: `${height}px`,
boxSizing: 'border-box',
transition: 'none',
},
{
height: '0px',
paddingTop: '0px',
paddingBottom: '0px',
overflow: 'hidden',
},
(state) => {
this.style.transition = `height ${duration}, padding ${duration}`;
return state;
}
);
}
},
/**
* Expand the height of the element (animate element open vertically).
*
* @param {float} duration A time in seconds for the transition to take place.
*/
expand(duration = '0.5s') {
if (this.animating) this._queueAnimation(this.expand, duration);
else {
this._animate(
{
display: null,
boxSizing: 'border-box',
height: '0px',
paddingTop: '0px',
paddingBottom: '0px',
transition: `height ${duration}, padding ${duration}`,
overflow: 'hidden',
},
{
height: `50px`,
paddingTop: null,
paddingBottom: null,
},
(state) => {
state.height = `${this.el.scrollHeight||50}px`;
return state;
}
);
}
},
/**
* Collapse the width of the element (animate element close horizontally).
*
* @param {float} duration A time in seconds for the transition to take place.
*/
slideOut(duration = '0.5s') {
if (this.animating) this._queueAnimation(this.slideOut, duration);
else {
// Capture the element height before starting the animation.
const width = this.el.clientWidth;
this._animate(
{
// Browsers are not able to perform transitions on elements with
// auto-width, because the browser doesn't have a value to
// transition to. Set the current height, wait for the an animation
// frame from the change to take effect and then set the transition.
width: `${width}px`,
boxSizing: 'border-box',
transition: 'none',
},
{
width: '0px',
paddingLeft: '0px',
paddingRight: '0px',
overflow: 'hidden',
},
(state) => {
this.style.transition = `width ${duration}, padding ${duration}`;
return state;
}
);
}
},
/**
* Expand the width of the element (animate element open horizontally).
*
* @param {float} duration A time in seconds for the transition to take place.
*/
slideIn(duration = '0.5s') {
if (this.animating) this._queueAnimation(this.slideIn, duration);
else {
this._animate(
{
display: null,
boxSizing: 'border-box',
width: '0px',
paddingLeft: '0px',
paddingRight: '0px',
transition: `width ${duration}, padding ${duration}`,
overflow: 'hidden',
},
{
width: `50px`,
paddingLeft: '',
paddingRight: '',
},
(state) => {
state.width = `${this.el.scrollWidth||50}px`;
return state;
}
);
}
},
/**
* Initiate an animation sequence.
*
* @param {Object} init
* Initial states and styles to apply before starting the animation frames.
* @param {Object} end
* The ending states and styles which complete the transition.
* @param {function|closure=} transition
* An optional interframe callback to make adjustments to the ending
* transition states and alterations.
*/
_animate(init, end, transition) {
this.isAnimating = true;
this._initAnimation();
this.setStyles(init);
requestAnimationFrame(() => {
if (transition) {
end = transition(end);
}
requestAnimationFrame(() => this.setStyles(end));
});
},
/**
* Initialize the element before the first animation is run.
*/
_initAnimation() {
if (!this.animateQueue) {
this.animateQueue = [];
this.on('transitionend', onTransitionEnd.bind(this));
}
},
/**
* Queue animations if pending animations are already in progress.
*
* @param {function|closure} method
* Animation method to add to the animation queue.
* @param {string} duration
* The CSS transition time in seconds (ie "0.3s").
*/
_queueAnimation(method, duration) {
this._initAnimation();
this.animateQueue.push([method, duration]);
},
});
})(Drupal);
