react-flip-toolkit
Version:
Configurable FLIP animation helpers for React
252 lines (203 loc) • 9.7 kB
JavaScript
;
exports.__esModule = true;
exports.animateMove = exports.getFlippedElementPositions = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _tween = require("popmotion/animations/tween");
var _tween2 = _interopRequireDefault(_tween);
var _easing = require("popmotion/easing");
var popmotionEasing = _interopRequireWildcard(_easing);
var _parallel = require("popmotion/compositors/parallel");
var _parallel2 = _interopRequireDefault(_parallel);
var _rematrix = require("rematrix");
var Rematrix = _interopRequireWildcard(_rematrix);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var getInvertedChildren = function getInvertedChildren(element, id) {
return [].slice.call(element.querySelectorAll("*[data-inverse-flip-id=\"" + id + "\"]"));
};
var passesComponentFilter = function passesComponentFilter(flipFilters, flipId) {
if (typeof flipFilters === "string") {
flipFilters = flipFilters.split(",").filter(function (x) {
return x;
});
if (!flipFilters.some(function (f) {
return f === flipId;
})) {
return false;
}
}
return true;
};
var applyStyles = function applyStyles(element, _ref) {
var matrix = _ref.matrix,
opacity = _ref.opacity;
// because matrix3d screws with opacity animations in Chrome (why??)
element.style.transform = "matrix(" + [matrix[0], matrix[1], matrix[4], matrix[5], matrix[12], matrix[13]].join(", ") + ")";
element.style.opacity = opacity;
};
var shouldApplyTransform = function shouldApplyTransform(element, flipStartId, flipEndId) {
if (element.dataset.flipComponentIdFilter && !passesComponentFilter(element.dataset.flipComponentIdFilter, flipStartId) && !passesComponentFilter(element.dataset.flipComponentIdFilter, flipEndId)) {
return false;
}
return true;
};
// if we're scaling an element and we have element children with data-inverse-flip-ids,
// apply the inverse of the transforms so that the children don't distort
var invertTransformsForChildren = function invertTransformsForChildren(childElements, matrix) {
var _ref2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
flipStartId = _ref2.flipStartId,
flipEndId = _ref2.flipEndId;
childElements.forEach(function (child) {
if (!shouldApplyTransform(child, flipStartId, flipEndId)) return;
var translateX = matrix[12];
var translateY = matrix[13];
var scaleX = matrix[0];
var scaleY = matrix[5];
var inverseVals = {};
if (child.dataset.translateX) inverseVals.translateX = -translateX / scaleX;
if (child.dataset.translateY) inverseVals.translateY = -translateY / scaleY;
if (child.dataset.scaleX) inverseVals.scaleX = 1 / scaleX;
if (child.dataset.scaleY) inverseVals.scaleY = 1 / scaleY;
child.style.transform = "translate(" + inverseVals.translateX + "px, " + inverseVals.translateY + "px) scale(" + inverseVals.scaleX + ", " + inverseVals.scaleY + ")";
});
};
var getFlippedElementPositions = exports.getFlippedElementPositions = function getFlippedElementPositions(element) {
return [].slice.apply(element.querySelectorAll("*[data-flip-id]")).map(function (child) {
return [child.dataset.flipId, {
rect: child.getBoundingClientRect(),
opacity: parseFloat(window.getComputedStyle(child).opacity),
flipComponentId: child.dataset.flipComponentId
}];
}).reduce(function (acc, curr) {
var _extends2;
return _extends({}, acc, (_extends2 = {}, _extends2[curr[0]] = curr[1], _extends2));
}, {});
};
var rectInViewport = function rectInViewport(_ref3) {
var top = _ref3.top,
bottom = _ref3.bottom,
left = _ref3.left,
right = _ref3.right;
return bottom > 0 && top < window.innerHeight && right > 0 && left < window.innerWidth;
};
var animateMove = exports.animateMove = function animateMove(_ref4) {
var _ref4$inProgressAnima = _ref4.inProgressAnimations,
inProgressAnimations = _ref4$inProgressAnima === undefined ? {} : _ref4$inProgressAnima,
_ref4$cachedFlipChild = _ref4.cachedFlipChildrenPositions,
cachedFlipChildrenPositions = _ref4$cachedFlipChild === undefined ? {} : _ref4$cachedFlipChild,
_ref4$flipCallbacks = _ref4.flipCallbacks,
flipCallbacks = _ref4$flipCallbacks === undefined ? {} : _ref4$flipCallbacks,
containerEl = _ref4.containerEl,
duration = _ref4.duration,
ease = _ref4.ease;
var body = document.querySelector("body");
var newFlipChildrenPositions = getFlippedElementPositions(containerEl);
Object.keys(newFlipChildrenPositions).forEach(function (id) {
if (!cachedFlipChildrenPositions[id] || !newFlipChildrenPositions[id]) {
return;
}
var prevRect = cachedFlipChildrenPositions[id].rect;
var currentRect = newFlipChildrenPositions[id].rect;
var prevOpacity = cachedFlipChildrenPositions[id].opacity;
var currentOpacity = newFlipChildrenPositions[id].opacity;
// don't animate invisible elements
if (!rectInViewport(prevRect) && !rectInViewport(currentRect)) {
return;
}
// don't animate elements that didn't change
if (prevRect.left === currentRect.left && prevRect.top === currentRect.top && prevRect.width === currentRect.width && prevRect.height === currentRect.height && prevOpacity === currentOpacity) {
return;
}
var element = containerEl.querySelector("*[data-flip-id=\"" + id + "\"]");
var flipStartId = cachedFlipChildrenPositions[id].flipComponentId;
var flipEndId = element.dataset.flipComponentId;
if (!shouldApplyTransform(element, flipStartId, flipEndId)) return;
var currentTransform = Rematrix.parse(getComputedStyle(element)["transform"]);
var toVals = { matrix: currentTransform };
var fromVals = {};
var transformsArray = [currentTransform];
// we're only going to animate the values that the child wants animated,
// based on its data-* attributes
if (element.dataset.translateX) {
transformsArray.push(Rematrix.translateX(prevRect.left - currentRect.left));
}
if (element.dataset.translateY) {
transformsArray.push(Rematrix.translateY(prevRect.top - currentRect.top));
}
if (element.dataset.scaleX) {
transformsArray.push(Rematrix.scaleX(prevRect.width / Math.max(currentRect.width, 0.0001)));
}
if (element.dataset.scaleY) {
transformsArray.push(Rematrix.scaleY(prevRect.height / Math.max(currentRect.height, 0.0001)));
}
if (element.dataset.opacity) {
fromVals.opacity = prevOpacity;
toVals.opacity = currentOpacity;
}
if (element.dataset.transformOrigin) {
element.style.transformOrigin = element.dataset.transformOrigin;
}
getInvertedChildren(element, id).forEach(function (child) {
if (child.dataset.transformOrigin) {
child.style.transformOrigin = child.dataset.transformOrigin;
}
});
if (inProgressAnimations[id]) {
inProgressAnimations[id].stop();
inProgressAnimations[id].onComplete();
delete inProgressAnimations[id];
}
fromVals.matrix = transformsArray.reduce(Rematrix.multiply);
// before animating, immediately apply FLIP styles to prevent flicker
applyStyles(element, fromVals);
invertTransformsForChildren(getInvertedChildren(element, id), fromVals, {
flipStartId: flipStartId,
flipEndId: flipEndId
});
if (flipCallbacks[id] && flipCallbacks[id].onStart) flipCallbacks[id].onStart(element, flipStartId);
var settings = {
duration: element.dataset.flipDuration || duration,
ease: element.dataset.flipEase && popmotionEasing[element.dataset.flipEase] || popmotionEasing[ease]
};
var onComplete = function onComplete() {};
if (flipCallbacks[id] && flipCallbacks[id].onComplete) {
onComplete = function onComplete() {
return flipCallbacks[id].onComplete(element, flipStartId);
};
}
// now start the animation
var _parallel$start = (0, _parallel2.default)((0, _tween2.default)(_extends({
from: fromVals.matrix,
to: toVals.matrix
}, settings)), (0, _tween2.default)(_extends({
from: { opacity: fromVals.opacity },
to: { opacity: toVals.opacity }
}, settings))).start({
update: function update(_ref5) {
var matrix = _ref5[0],
otherVals = _ref5[1];
if (!body.contains(element)) {
stop && stop();
return;
}
applyStyles(element, _extends({}, otherVals, { matrix: matrix }));
// for children that requested it, cancel out the transform by applying the inverse transform
invertTransformsForChildren(getInvertedChildren(element, id), matrix, {
flipStartId: flipStartId,
flipEndId: flipEndId
});
},
complete: function complete() {
delete inProgressAnimations[id];
requestAnimationFrame(function () {
element.style.transform = "";
});
onComplete();
}
}),
stop = _parallel$start.stop;
// in case we have to cancel
inProgressAnimations[id] = { stop: stop, onComplete: onComplete };
});
return inProgressAnimations;
};