Back In
The element glides in from off screen at reduced size, as if it’s still far away, then steps forward to full size once it’s in place.
ReminderTeam call in 10 minutes
How to use it
- Paste the snippet just before
</head>. - Add
data-back-into any element. - Choose an edge with
data-fromif it shouldn’t come from the top.
Options
Add any of these attributes to the same element.
data-from | The edge it comes from: top, bottom, left or right. Give several and each loop uses the next. Default top. |
data-scale | Its size on the way in, before it grows. Default 0.7. |
data-distance | Travel this many px instead of starting fully off screen. |
data-duration | How long the whole entrance takes. Default 1200 ms. |
data-delay | Wait before the first entrance. Default 0 ms. |
data-loop | Hold, fade away and come in again. |
data-hold | How long it stays before fading, when looping. Default 1400 ms. |
It starts just past the edge of the nearest container that clips its overflow (overflow: hidden or clip), or of the window if there isn’t one. Put it in a clipping wrapper, or give the page overflow-x: clip, so it can’t make the page scroll sideways while it’s off screen.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Back In: the element approaches from off screen at reduced size, as if
// from a distance, then grows to full size once it's in place.
// Usage: <div data-back-in>…</div>
// It starts just past the edge of the nearest container that clips its
// overflow (overflow: hidden or clip), or of the window.
// Optional: data-from ("top", default, "bottom", "left" or "right"; give
// several, like "top right left", and each loop uses the next),
// data-distance (px to travel instead of from off screen), data-scale (its
// size on the way in, default 0.7), data-duration (ms, default 1200),
// data-delay (ms before the first entrance), data-loop (hold, fade away
// and come in again), data-hold (ms to stay when looping, default 1400).
var DEFAULTS = { from: 'top', scale: 0.7, duration: 1200, delay: 0, hold: 1400 };
function reduced() {
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
// The box the element must leave to be "off screen".
function clipBox(el) {
for (var p = el.parentElement; p && p !== document.body; p = p.parentElement) {
var s = getComputedStyle(p);
if (s.overflowX !== 'visible' || s.overflowY !== 'visible') return p.getBoundingClientRect();
}
return { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight };
}
// Where to start: an x/y offset that puts the element just outside the box.
function offset(el, from) {
var d = +el.dataset.distance;
var r = el.getBoundingClientRect();
var c = clipBox(el);
if (from === 'right') return { x: d || c.right - r.left + 8, y: 0 };
if (from === 'left') return { x: -(d || r.right - c.left + 8), y: 0 };
if (from === 'bottom') return { x: 0, y: d || c.bottom - r.top + 8 };
return { x: 0, y: -(d || r.bottom - c.top + 8) };
}
function enter(el, from, done) {
var d = el.dataset;
var t = +d.duration || DEFAULTS.duration;
var o = offset(el, from);
anime.utils.set(el, { x: o.x, y: o.y, scale: +d.scale || DEFAULTS.scale, opacity: 0.7 });
anime.createTimeline({ onComplete: done })
// Glide in while still small and far away...
.add(el, { x: 0, y: 0, opacity: 1, duration: t * 0.6, ease: 'out(3)' })
// ...then step forward to full size.
.add(el, { scale: 1, duration: t * 0.4, ease: 'outBack(1.3)' });
}
function cycle(el) {
var d = el.dataset;
var s = el._bki;
var froms = (d.from || DEFAULTS.from).split(/[\s,]+/);
enter(el, froms[s.n++ % froms.length], function () {
if (d.loop === undefined || d.loop === 'false') return;
setTimeout(function () {
anime.animate(el, {
opacity: 0,
duration: 400,
ease: 'out(2)',
onComplete: function () {
// Wait until it's back on screen before the next entrance.
setTimeout(function () {
if (s.visible) cycle(el);
else s.waiting = true;
}, 500);
}
});
}, +d.hold || DEFAULTS.hold);
});
}
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
var s = e.target._bki;
s.visible = e.isIntersecting;
if (!e.isIntersecting) return;
if (!s.started) {
s.started = true;
setTimeout(function () { cycle(e.target); }, +e.target.dataset.delay || DEFAULTS.delay);
} else if (s.waiting) {
s.waiting = false;
cycle(e.target);
}
});
}, { threshold: 0.2 });
document.querySelectorAll('[data-back-in]').forEach(function (el) {
if (el._bki) return;
el._bki = { n: 0 };
if (reduced()) return;
anime.utils.set(el, { opacity: 0 });
io.observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div class="card" data-back-in>Reminder</div>
<!-- or from the left: -->
<div class="card" data-back-in data-from="left">Reminder</div>