198 lines
5.0 KiB
JavaScript
198 lines
5.0 KiB
JavaScript
/* global VT */
|
|
window.VT = window.VT || {};
|
|
|
|
VT.AppFlip = function (el, options) {
|
|
var enabled = options.initialDelay === 0;
|
|
var first;
|
|
var level = 0;
|
|
|
|
// enable animations only after an initial delay
|
|
setTimeout(function () {
|
|
enabled = true;
|
|
}, options.initialDelay || 100);
|
|
|
|
// take a snapshot before any HTML changes
|
|
// do this only for the first beforeFlip event in the current cycle
|
|
el.addEventListener('beforeFlip', function () {
|
|
if (!enabled) return;
|
|
if (++level > 1) return;
|
|
|
|
first = snapshot();
|
|
});
|
|
|
|
// take a snapshot after HTML changes, calculate and play animations
|
|
// do this only for the last flip event in the current cycle
|
|
el.addEventListener('flip', function () {
|
|
if (!enabled) return;
|
|
if (--level > 0) return;
|
|
|
|
var last = snapshot();
|
|
var toRemove = invertForRemoval(first, last);
|
|
var toAnimate = invertForAnimation(first, last);
|
|
|
|
requestAnimationFrame(function () {
|
|
requestAnimationFrame(function () {
|
|
remove(toRemove);
|
|
animate(toAnimate);
|
|
|
|
first = null;
|
|
});
|
|
});
|
|
});
|
|
|
|
// build a snapshot of the current HTML's client rectangles
|
|
// includes original transforms and hierarchy
|
|
function snapshot() {
|
|
var map = new Map();
|
|
|
|
el.querySelectorAll(options.selector).forEach(function (el) {
|
|
var key = el.dataset.key || el;
|
|
|
|
// parse original transform
|
|
// i.e. strip inverse transform using "scale(1)" marker
|
|
var transform = el.style.transform
|
|
? el.style.transform.replace(/^.*scale\(1\)/, '')
|
|
: '';
|
|
|
|
map.set(key, {
|
|
key: key,
|
|
el: el,
|
|
rect: el.getBoundingClientRect(),
|
|
ancestor: null,
|
|
transform: transform,
|
|
});
|
|
});
|
|
|
|
resolveAncestors(map);
|
|
|
|
return map;
|
|
}
|
|
|
|
function resolveAncestors(map) {
|
|
map.forEach(function (entry) {
|
|
var current = entry.el.parentNode;
|
|
|
|
while (current && current !== el) {
|
|
var ancestor = map.get(current.dataset.key || current);
|
|
|
|
if (ancestor) {
|
|
entry.ancestor = ancestor;
|
|
return;
|
|
}
|
|
|
|
current = current.parentNode;
|
|
}
|
|
});
|
|
}
|
|
|
|
// reinsert removed elements at their original position
|
|
function invertForRemoval(first, last) {
|
|
var toRemove = [];
|
|
|
|
first.forEach(function (entry) {
|
|
if (entry.el.classList.contains('_noflip')) return;
|
|
if (!needsRemoval(entry)) return;
|
|
|
|
entry.el.style.position = 'fixed';
|
|
entry.el.style.left = entry.rect.left + 'px';
|
|
entry.el.style.top = entry.rect.top + 'px';
|
|
entry.el.style.width = entry.rect.right - entry.rect.left + 'px';
|
|
entry.el.style.transition = 'none';
|
|
entry.el.style.transform = '';
|
|
|
|
el.appendChild(entry.el);
|
|
toRemove.push(entry);
|
|
});
|
|
|
|
return toRemove;
|
|
|
|
function needsRemoval(entry) {
|
|
if (entry.ancestor && needsRemoval(entry.ancestor)) {
|
|
return false;
|
|
}
|
|
|
|
return !last.has(entry.key);
|
|
}
|
|
}
|
|
|
|
// set position of moved elements to their original position
|
|
// or set opacity to zero for new elements to appear nicely
|
|
function invertForAnimation(first, last) {
|
|
var toAnimate = [];
|
|
|
|
last.forEach(function (entry) {
|
|
if (entry.el.classList.contains('_noflip')) return;
|
|
|
|
calculate(entry);
|
|
|
|
if (entry.appear) {
|
|
entry.el.style.transition = 'none';
|
|
entry.el.style.opacity = '0';
|
|
toAnimate.push(entry);
|
|
} else if (entry.deltaX !== 0 || entry.deltaY !== 0) {
|
|
// set inverted transform with "scale(1)" marker, see above
|
|
entry.el.style.transition = 'none';
|
|
entry.el.style.transform =
|
|
'translate(' +
|
|
entry.deltaX +
|
|
'px, ' +
|
|
entry.deltaY +
|
|
'px) scale(1) ' +
|
|
entry.transform;
|
|
toAnimate.push(entry);
|
|
}
|
|
});
|
|
|
|
return toAnimate;
|
|
|
|
// calculate inverse transform relative to any animated ancestors
|
|
function calculate(entry) {
|
|
if (entry.calculated) return;
|
|
entry.calculated = true;
|
|
|
|
var b = first.get(entry.key);
|
|
|
|
if (b) {
|
|
entry.deltaX = b.rect.left - entry.rect.left;
|
|
entry.deltaY = b.rect.top - entry.rect.top;
|
|
|
|
if (entry.ancestor) {
|
|
calculate(entry.ancestor);
|
|
|
|
entry.deltaX -= entry.ancestor.deltaX;
|
|
entry.deltaY -= entry.ancestor.deltaY;
|
|
}
|
|
} else {
|
|
entry.appear = true;
|
|
entry.deltaX = 0;
|
|
entry.deltaY = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// play remove animations and remove elements after timeout
|
|
function remove(entries) {
|
|
entries.forEach(function (entry) {
|
|
entry.el.style.transition = '';
|
|
entry.el.style.opacity = '0';
|
|
});
|
|
|
|
setTimeout(function () {
|
|
entries.forEach(function (entry) {
|
|
if (entry.el.parentNode) {
|
|
entry.el.parentNode.removeChild(entry.el);
|
|
}
|
|
});
|
|
}, options.removeTimeout);
|
|
}
|
|
|
|
// play move/appear animations
|
|
function animate(entries) {
|
|
entries.forEach(function (entry) {
|
|
entry.el.style.transition = '';
|
|
entry.el.style.transform = entry.transform;
|
|
entry.el.style.opacity = '';
|
|
});
|
|
}
|
|
};
|