Teleport out effect

This commit is contained in:
Fraxle
2026-06-02 12:55:49 +01:00
parent 9303dc7f4d
commit 5a9fc51de5
+65 -6
View File
@@ -293,20 +293,21 @@ window.addEventListener('scroll', function () {
document.body.appendChild(s);
s.animate([
{ transform: 'translate(-50%,-50%) scale(0)' },
{ transform: 'translate(-50%,-50%) scale(' + size + ')', offset: 0.3 },
{ transform: 'translate(-50%,-50%) scale(' + size + ')', offset: 0.7 },
{ transform: 'translate(-50%,-50%) scale(' + size + ')', offset: 0.08 }, // pop in almost instantly
{ transform: 'translate(-50%,-50%) scale(' + size + ')', offset: 0.6 },
{ transform: 'translate(-50%,-50%) scale(0)' }
], { duration: life, easing: 'ease-in-out' }).onfinish = function () { s.remove(); };
], { duration: life, easing: 'ease-out' }).onfinish = function () { s.remove(); };
}
function teleport(img) {
function teleport(img, startDelay) {
var r = img.getBoundingClientRect();
var START_DELAY = 500; // pause before anything happens
var START_DELAY = (typeof startDelay === 'number') ? startDelay : 500; // pause before anything happens
var CHARGE = 1000; // sparkles gather along the bottom before the figure grows
var GROW = 2200; // the bottom-up materialise
var baseShadow = 'drop-shadow(0 8px 24px rgba(0,0,0,0.35))';
function ease(t) { return t * t; } // ease-in: gentle at the bottom, no slow-down toward the top
img.dataset.teleBusy = '1';
// Keep it invisible (clipped) but opaque from the outset, so nothing flashes during the delay
img.style.opacity = '1';
img.style.webkitClipPath = img.style.clipPath = 'inset(100% 0 0 0)';
@@ -369,16 +370,74 @@ window.addEventListener('scroll', function () {
img.style.filter = ''; // CSS drop-shadow returns
img.classList.remove('mascot-teleport');
img.style.opacity = '1';
delete img.dataset.teleBusy;
}
}
requestAnimationFrame(frame);
}, START_DELAY);
}
// Reverse "beam out": dissolve top-down into a puff of sparkles, then stay hidden
function dematerialize(img, done) {
var r = img.getBoundingClientRect();
var DUR = 800;
var baseShadow = 'drop-shadow(0 8px 24px rgba(0,0,0,0.35))';
var start = null;
function frame(ts) {
if (!start) start = ts;
var p = Math.min((ts - start) / DUR, 1);
// Dissolve away from the top downwards
img.style.webkitClipPath = img.style.clipPath = 'inset(' + (p * 100).toFixed(2) + '% 0 0 0)';
// Energy flash that builds as it beams out
var bright = (1 + 1.2 * p).toFixed(2);
var glow = (0.7 * p).toFixed(2);
img.style.filter = baseShadow + ' drop-shadow(0 0 14px rgba(255,255,255,' + glow + ')) brightness(' + bright + ')';
// Sparkles riding the dissolving edge
var lineY = r.top + p * r.height;
if (p < 0.98) {
for (var i = 0; i < 6; i++) {
var x = r.left + Math.random() * r.width;
var y = lineY + (Math.random() - 0.5) * r.height * 0.1;
y = Math.max(r.top, Math.min(r.bottom, y));
teleSpark(x, y, 480 + Math.random() * 360);
}
}
if (p < 1) {
requestAnimationFrame(frame);
} else {
// A final puff of sparkles where it stood
for (var j = 0; j < 16; j++) {
teleSpark(r.left + Math.random() * r.width, r.top + Math.random() * r.height, 420 + Math.random() * 360);
}
img.style.opacity = '0'; // stay hidden during the wait
img.style.webkitClipPath = img.style.clipPath = '';
img.style.filter = '';
if (done) done();
}
}
requestAnimationFrame(frame);
}
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { io.unobserve(e.target); teleport(e.target); }
});
}, { threshold: 0.25 });
imgs.forEach(function (im) { io.observe(im); });
imgs.forEach(function (im) {
io.observe(im);
// Click to beam out, then re-materialise after ~3s
im.style.cursor = 'pointer';
im.addEventListener('click', function () {
if (im.dataset.teleBusy) return; // ignore while an effect is already running
im.dataset.teleBusy = '1';
dematerialize(im, function () {
setTimeout(function () { teleport(im, 0); }, 3000);
});
});
});
})();