photoswipe
Version:
JavaScript gallery
117 lines (101 loc) • 3.29 kB
JavaScript
import { setTransitionStyle, removeTransitionStyle } from './util.js';
const DEFAULT_EASING = 'cubic-bezier(.4,0,.22,1)';
/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */
/** @typedef {Object} DefaultCssAnimationProps
*
* @prop {HTMLElement} target
* @prop {number} [duration]
* @prop {string} [easing]
* @prop {string} [transform]
* @prop {string} [opacity]
* */
/** @typedef {SharedAnimationProps & DefaultCssAnimationProps} CssAnimationProps */
/**
* Runs CSS transition.
*/
class CSSAnimation {
/**
* onComplete can be unpredictable, be careful about current state
*
* @param {CssAnimationProps} props
*/
constructor(props) {
this.props = props;
const {
target,
onComplete,
transform,
onFinish = () => {},
duration = 333,
easing = DEFAULT_EASING,
} = props;
this.onFinish = onFinish;
// support only transform and opacity
const prop = transform ? 'transform' : 'opacity';
const propValue = props[prop] ?? '';
/** @private */
this._target = target;
/** @private */
this._onComplete = onComplete;
/** @private */
this._finished = false;
/** @private */
this._onTransitionEnd = this._onTransitionEnd.bind(this);
// Using timeout hack to make sure that animation
// starts even if the animated property was changed recently,
// otherwise transitionend might not fire or transition won't start.
// https://drafts.csswg.org/css-transitions/#starting
//
// ¯\_(ツ)_/¯
/** @private */
this._helperTimeout = setTimeout(() => {
setTransitionStyle(target, prop, duration, easing);
this._helperTimeout = setTimeout(() => {
target.addEventListener('transitionend', this._onTransitionEnd, false);
target.addEventListener('transitioncancel', this._onTransitionEnd, false);
// Safari occasionally does not emit transitionend event
// if element property was modified during the transition,
// which may be caused by resize or third party component,
// using timeout as a safety fallback
this._helperTimeout = setTimeout(() => {
this._finalizeAnimation();
}, duration + 500);
target.style[prop] = propValue;
}, 30); // Do not reduce this number
}, 0);
}
/**
* @private
* @param {TransitionEvent} e
*/
_onTransitionEnd(e) {
if (e.target === this._target) {
this._finalizeAnimation();
}
}
/**
* @private
*/
_finalizeAnimation() {
if (!this._finished) {
this._finished = true;
this.onFinish();
if (this._onComplete) {
this._onComplete();
}
}
}
// Destroy is called automatically onFinish
destroy() {
if (this._helperTimeout) {
clearTimeout(this._helperTimeout);
}
removeTransitionStyle(this._target);
this._target.removeEventListener('transitionend', this._onTransitionEnd, false);
this._target.removeEventListener('transitioncancel', this._onTransitionEnd, false);
if (!this._finished) {
this._finalizeAnimation();
}
}
}
export default CSSAnimation;