Float
The element bobs gently up and down, as if hovering, while a soft shadow beneath it breathes: smaller and fainter as it rises, fuller as it sinks back.
How to use it
- Paste the snippet just before
</head>. - Add
data-floatto any element. It floats while it’s on screen.
Options
Add any of these attributes to the same element.
data-height | How far it rises, in px. Default 12. |
data-duration | How long one rise and fall takes. Default 3000 ms. |
data-tilt | Rock this many degrees as it bobs. Default 0. |
data-gap | Space between its bottom edge and the shadow, in px. Default 14. |
data-shadow | Set to false for no shadow. |
data-delay | Wait before it starts. Default 0 ms. |
The shadow is added inside the element and sits just below it, so don’t clip the element’s overflow, and leave a little room underneath. Elements that can’t hold children, like <img>, float without a shadow: wrap them to get one.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Float: the element bobs gently up and down, as if hovering, while a soft
// shadow beneath it breathes: smaller and fainter as it rises, fuller as it
// sinks back. It pauses while off screen.
// Usage: <div data-float>…</div>
// Optional: data-height (px it rises, default 12), data-duration (ms for one
// rise and fall, default 3000), data-tilt (degrees it rocks as it bobs,
// default 0), data-gap (px from its bottom edge to the shadow, default 14),
// data-shadow="false" (no shadow), data-delay (ms before it starts).
// The shadow sits just below the element, so don't clip its overflow.
// An <img> can't hold the shadow: wrap it in a div to get one.
var DEFAULTS = { height: 12, duration: 3000, tilt: 0, gap: 14, delay: 0 };
var css =
'.flt-shadow{position:absolute;left:50%;top:100%;width:76%;height:12px;margin-left:-38%;border-radius:50%;' +
'background:radial-gradient(closest-side,rgba(15,15,30,.32),rgba(15,15,30,0));pointer-events:none}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function setup(el) {
var d = el.dataset;
var s = (el._flt = {});
// Elements that can't hold children (img, input…) float without a shadow.
if (d.shadow !== 'false' && !/^(IMG|INPUT|VIDEO|CANVAS|TEXTAREA|SELECT)$/.test(el.tagName)) {
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
s.shadow = document.createElement('span');
s.shadow.className = 'flt-shadow';
s.shadow.setAttribute('aria-hidden', 'true');
s.shadow.style.marginTop = (d.gap === undefined ? DEFAULTS.gap : +d.gap) + 'px';
el.appendChild(s.shadow);
}
}
function start(el) {
var d = el.dataset;
var s = el._flt;
var h = d.height === undefined ? DEFAULTS.height : +d.height;
var tilt = +d.tilt || 0;
var p = { t: 0 };
s.anim = anime.animate(p, {
t: 1,
duration: +d.duration || DEFAULTS.duration,
ease: 'linear',
loop: true,
autoplay: false,
onUpdate: function () {
var lift = 0.5 - 0.5 * Math.cos(p.t * Math.PI * 2); // 0 at rest, 1 at the top
var y = -h * lift;
var r = tilt * Math.sin(p.t * Math.PI * 2);
el.style.transform = 'translateY(' + y.toFixed(2) + 'px) rotate(' + r.toFixed(2) + 'deg)';
if (s.shadow) {
// The shadow stays on the ground: undo the lift and the tilt.
s.shadow.style.transform = 'rotate(' + (-r).toFixed(2) + 'deg) translateY(' + (-y).toFixed(2) + 'px) scale(' + (1 - 0.3 * lift).toFixed(3) + ')';
s.shadow.style.opacity = (1 - 0.5 * lift).toFixed(3);
}
}
});
}
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
var el = e.target;
var s = el._flt;
s.visible = e.isIntersecting;
if (e.isIntersecting && !s.started) {
s.started = true;
setTimeout(function () {
start(el);
if (s.visible) s.anim.play();
}, +el.dataset.delay || DEFAULTS.delay);
} else if (s.anim) {
if (s.visible) s.anim.play();
else s.anim.pause();
}
});
});
document.querySelectorAll('[data-float]').forEach(function (el) {
if (el._flt) return;
setup(el);
if (!reduced()) io.observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div class="hero-art" data-float><img src="rocket.png" alt=""></div>
<!-- or higher, with a slight rock: -->
<span class="hero-art" data-float data-height="20" data-tilt="2">…</span>