UNPKG

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.

117 lines 4.46 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = cannonBall; function cannonBall(flyingId, targetId, options = {}) { const { moveX = true, moveY = true, duration = 1.2, acceleration = 9.81, scale = 1, fly3D = false, onTransitionEnd = () => { }, removeOriginal = true, resetTransformation = false, } = options; const flyingEl = document.getElementById(flyingId); if (!flyingEl) return; const targetEl = document.getElementById(targetId); if (!targetEl) return; const flyingRect = flyingEl.getBoundingClientRect(); const targetRect = targetEl.getBoundingClientRect(); const deltaX = (targetRect.left + targetRect.right) / 2 - (flyingRect.left + flyingRect.right) / 2; const deltaY = (targetRect.top + targetRect.bottom) / 2 - (flyingRect.top + flyingRect.bottom) / 2; const wrapper = document.createElement('div'); Object.assign(wrapper.style, { position: 'absolute', top: `${flyingRect.top}px`, left: `${flyingRect.left}px`, width: `${flyingRect.width}px`, height: `${flyingRect.height}px`, zIndex: '99', overflow: 'visible', pointerEvents: 'none', }); const clone = flyingEl.cloneNode(true); Object.assign(clone.style, { position: 'absolute', top: '0px', left: '0px', width: '100%', height: '100%', }); // Reset transformOrigin if option is enabled if (resetTransformation) { clone.style.transform = 'none'; clone.style.transformOrigin = 'center center'; } // Add html-trajectory-cloned class clone.classList.add("html-trajectory-cloned"); wrapper.appendChild(clone); document.body.appendChild(wrapper); wrapper.getBoundingClientRect(); // Remove the original element if removeOriginal is true if (removeOriginal) { flyingEl.remove(); } const effectiveAcceleration = fly3D ? 0 : acceleration; const accelerationPx = effectiveAcceleration * 100; // convert m/s² to px/s² if (moveX) { wrapper.style.transform = `translateX(${deltaX}px)`; wrapper.style.transition = `transform ${duration}s linear`; } if (moveY) { const s = deltaY; const v0 = accelerationPx === 0 ? s / duration : (s - 0.5 * accelerationPx * duration * duration) / duration; let startTime = null; function animateY(timestamp) { if (!startTime) startTime = timestamp; const elapsed = (timestamp - startTime) / 1000; if (elapsed >= duration) { clone.style.transform = `translateY(${deltaY}px) scale(${scale})`; // Make sure this callback is definitely executed try { onTransitionEnd(); } catch (e) { console.error("Error in onTransitionEnd callback", e); } wrapper.remove(); if (removeOriginal) { flyingEl === null || flyingEl === void 0 ? void 0 : flyingEl.remove(); } return; } const displacement = accelerationPx === 0 ? v0 * elapsed : v0 * elapsed + 0.5 * accelerationPx * elapsed * elapsed; const progress = elapsed / duration; let currentScale = 1; if (progress > 0.66) { const t = (progress - 0.66) / 0.34; currentScale = 1 + (scale - 1) * t; } clone.style.transform = `translateY(${displacement}px) scale(${currentScale})`; requestAnimationFrame(animateY); } requestAnimationFrame(animateY); } if (!moveY) { clone.style.transform = `none`; } if (!moveX && !moveY) { setTimeout(() => { onTransitionEnd(); wrapper.remove(); if (removeOriginal) { flyingEl.remove(); } }, duration * 1000); } else if (!moveX && moveY) { // handled by animateY } else if (moveX && !moveY) { wrapper.addEventListener('transitionend', () => { onTransitionEnd(); wrapper.remove(); if (removeOriginal) { flyingEl.remove(); } }, { once: true }); } } //# sourceMappingURL=cannonBall.js.map