html-trajectory
Version:
Animate HTML elements along a curved path from their current position to a target element. Eg. a product photo flying into the cart icon.
108 lines • 5.32 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = projectile;
function projectile(flyingId, targetId, options) {
var _a, _b, _c, _d, _e, _f;
const flying = document.getElementById(flyingId);
const target = document.getElementById(targetId);
// Try to get the field element first, fall back to document.body if not found
const field = document.getElementById('field');
const containerElement = field || document.body;
if (!flying || !target) {
throw 'Invalid IDs provided to projectile function';
}
const clone = flying.cloneNode(true);
clone.removeAttribute('id');
clone.style.position = 'absolute';
clone.style.pointerEvents = 'none';
clone.style.margin = '0';
// Reset transformation if option is enabled
if (options === null || options === void 0 ? void 0 : options.resetTransformation) {
clone.style.transform = 'none';
clone.style.transformOrigin = 'center center';
}
// Add html-trajectory-cloned class
clone.classList.add("html-trajectory-cloned");
containerElement.appendChild(clone);
const computedStyle = window.getComputedStyle(flying);
const transform = computedStyle.transform;
let baseRotation = 0;
if (transform && transform !== 'none') {
const matrixMatch = transform.match(/^matrix\(([-\d.,\s]+)\)$/);
if (matrixMatch) {
const values = matrixMatch[1].split(',').map(v => parseFloat(v.trim()));
if (values.length >= 6) {
const [a, b, c, d] = values;
baseRotation = Math.atan2(b, a) * (180 / Math.PI);
}
}
}
clone.getBoundingClientRect();
const flyingRect = flying.getBoundingClientRect();
const containerRect = containerElement.getBoundingClientRect();
const targetRect = target.getBoundingClientRect();
const startX = flyingRect.left - containerRect.left;
const startY = flyingRect.top - containerRect.top;
const endX = targetRect.left - containerRect.left + (targetRect.width - clone.offsetWidth) / 2;
const endY = targetRect.top - containerRect.top + (targetRect.height - clone.offsetHeight) / 2;
clone.style.left = `${startX}px`;
clone.style.top = `${startY}px`;
const duration = ((_a = options === null || options === void 0 ? void 0 : options.duration) !== null && _a !== void 0 ? _a : 1) * 1000;
const userAcceleration = (_b = options === null || options === void 0 ? void 0 : options.acceleration) !== null && _b !== void 0 ? _b : 4;
const acceleration = userAcceleration * 100;
const moveX = (_c = options === null || options === void 0 ? void 0 : options.moveX) !== null && _c !== void 0 ? _c : true;
const moveY = (_d = options === null || options === void 0 ? void 0 : options.moveY) !== null && _d !== void 0 ? _d : true;
const targetScale = (_e = options === null || options === void 0 ? void 0 : options.scale) !== null && _e !== void 0 ? _e : 1;
const removeOriginal = (_f = options === null || options === void 0 ? void 0 : options.removeOriginal) !== null && _f !== void 0 ? _f : true;
if (removeOriginal) {
flying.remove();
}
const distanceX = endX - startX;
const distanceY = endY - startY;
const velocityX = distanceX / (duration / 1000);
const velocityYInitial = (distanceY - 0.5 * acceleration * Math.pow(duration / 1000, 2)) / (duration / 1000);
let startTime;
function animate(currentTime) {
if (startTime === undefined) {
startTime = currentTime;
}
const elapsed = (currentTime - startTime) / 1000; // in seconds
const t = Math.min(elapsed, duration / 1000);
const x = moveX ? startX + velocityX * t : startX;
const y = moveY ? startY + velocityYInitial * t + 0.5 * acceleration * t * t : startY;
const deltaT = 0.001;
const nextT = Math.min(t + deltaT, duration / 1000);
const nextX = moveX ? startX + velocityX * nextT : startX;
const nextY = moveY ? startY + velocityYInitial * nextT + 0.5 * acceleration * nextT * nextT : startY;
const dx = nextX - x;
const dy = nextY - y;
const angleRad = Math.atan2(dy, dx);
const angleDeg = angleRad * (180 / Math.PI);
const dynamicRotation = angleDeg + 90;
const finalRotation = baseRotation + dynamicRotation;
let scaleFactor = 1;
if (t > (duration / 1000) * 0.66) {
const progress = (t - (duration / 1000) * 0.66) / ((duration / 1000) * 0.34);
scaleFactor = 1 + (targetScale - 1) * progress;
}
clone.style.transform = `rotate(${finalRotation}deg) scale(${scaleFactor})`;
clone.style.left = `${x}px`;
clone.style.top = `${y}px`;
if (t < duration / 1000) {
requestAnimationFrame(animate);
}
else {
try {
if (options === null || options === void 0 ? void 0 : options.onTransitionEnd) {
options.onTransitionEnd();
}
}
catch (e) {
console.error("Error in onTransitionEnd callback", e);
}
clone.remove();
}
}
requestAnimationFrame(animate);
}
//# sourceMappingURL=projectile.js.map