Wave Text
Letters bob up and down in a travelling wave, like a flag in the breeze. It keeps going while the text is on screen and rests when it isn’t.
Riding the wave
How to use it
- Paste the snippet just before
</head>. - Add
data-wave-textto any heading or text element.
Options
Add any of these attributes to the same element.
data-height | How high each letter rises, in em. Default 0.22. |
data-speed | Time for one bob. Default 1400 ms. |
data-stagger | Gap between letters: smaller is a longer wave. Default 140 ms. |
data-color | Colour letters turn as the crest passes. |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Wave Text: letters bob up and down in a travelling wave, like a flag in
// the breeze. Keeps going while the text is on screen.
// Usage: <h1 data-wave-text>Riding the wave</h1>
// Optional: data-height (how high letters rise, in em; default 0.22),
// data-speed (ms for one bob; default 1400), data-stagger (ms between
// letters; default 140), data-color (letters take this colour at the crest).
var DEFAULTS = { height: 0.22, speed: 1400, stagger: 140 };
var css =
'.wvt-word{display:inline-block;white-space:nowrap}' +
'.wvt-char{display:inline-block;will-change:transform}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function split(el) {
var text = el.textContent.replace(/\s+/g, ' ').trim();
el.setAttribute('aria-label', text);
el.textContent = '';
var chars = [];
text.split(' ').forEach(function (w, i) {
if (i) el.appendChild(document.createTextNode(' '));
var word = document.createElement('span');
word.className = 'wvt-word';
word.setAttribute('aria-hidden', 'true');
Array.from(w).forEach(function (c) {
var s = document.createElement('span');
s.className = 'wvt-char';
s.textContent = c;
word.appendChild(s);
chars.push(s);
});
el.appendChild(word);
});
return chars;
}
function start(el, chars) {
var d = el.dataset;
var amp = parseFloat(getComputedStyle(el).fontSize) * (+d.height || DEFAULTS.height);
var speed = +d.speed || DEFAULTS.speed;
var props = {
y: [0, -amp, 0],
duration: speed,
ease: 'inOut(2)',
loop: true,
// Each letter starts a little later, so the crest travels along the line.
delay: anime.stagger(+d.stagger || DEFAULTS.stagger)
};
if (d.color) {
var base = getComputedStyle(el).color;
props.color = [base, d.color, base];
}
return anime.animate(chars, props);
}
function init() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
document.querySelectorAll('[data-wave-text]').forEach(function (el) {
if (el._wvt) return;
el._wvt = true;
var chars = split(el);
var anim = null;
// Only wave while visible, to save battery.
new IntersectionObserver(function (entries) {
if (!entries[0].isIntersecting) { if (anim) anim.pause(); return; }
if (anim) anim.play();
else anim = start(el, chars);
}).observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><h1 data-wave-text>Riding the wave</h1>
<!-- letters turn purple at the crest: -->
<h1 data-wave-text data-color="#a855f7">Riding the wave</h1>