Typed Word Rotator
One word in your sentence is typed in, held, then backspaced and replaced by the next, while the rest of the sentence stays put. Or have each word highlighted and typed over, the way you’d replace it in an editor.
We design websites
How to use it
- Paste the snippet just before
</head>. - Wrap the changing word in a
<span>and list every word, separated by commas, indata-typed-word-rotator.
Options
Add any of these attributes to the same element.
data-erase | backspace (default) deletes letter by letter. select highlights the word and types over it. |
data-speed | Typing time per character. Default 80 ms. |
data-delete-speed | Time per character when backspacing. Default 40 ms. |
data-hold | How long a finished word stays. Default 1800 ms. |
data-cursor-color | The cursor’s colour. Default the word’s. |
Unlike the Typewriter, only the one word changes, so it slots into a longer headline.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Typed Word Rotator: one word in your sentence is typed in, held, then
// backspaced and replaced by the next, with a cursor that blinks while it
// waits. Or set data-erase="select" to have the word highlighted and typed
// over, the way you would replace it in a text editor.
// Usage: <h1>We design <span data-typed-word-rotator="websites, brands, apps">websites</span></h1>
// Optional: data-speed (ms per character, default 80), data-delete-speed
// (ms per character, default 40), data-hold (ms a word stays, default
// 1800), data-erase ("backspace" (default) or "select"), data-cursor-color.
var DEFAULTS = { speed: 80, deleteSpeed: 40, hold: 1800 };
var css =
'.twr{white-space:nowrap}' +
'.twr-text{border-radius:0.08em;transition:background-color .12s}' +
'.twr-text.is-selected{background:rgba(168,85,247,.28)}' +
'.twr-cursor{display:inline-block;width:0.07em;height:0.95em;margin-left:0.05em;vertical-align:-0.1em;background:currentColor;border-radius:1px}' +
'.twr-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function span(cls, parent, text) {
var s = document.createElement('span');
s.className = cls;
if (text !== undefined) s.textContent = text;
parent.appendChild(s);
return s;
}
function build(el) {
var words = (el.getAttribute('data-typed-word-rotator') || el.textContent)
.split(',').map(function (w) { return w.trim(); }).filter(Boolean);
el.classList.add('twr');
el.textContent = '';
span('twr-sr', el, words.join(', '));
var text = span('twr-text', el, words[0]);
var cursor = span('twr-cursor', el);
text.setAttribute('aria-hidden', 'true');
cursor.setAttribute('aria-hidden', 'true');
if (el.dataset.cursorColor) cursor.style.background = el.dataset.cursorColor;
return { el: el, words: words, text: text, cursor: cursor };
}
function run(t) {
var d = t.el.dataset;
var speed = +d.speed || DEFAULTS.speed;
var delSpeed = +d.deleteSpeed || DEFAULTS.deleteSpeed;
var hold = +d.hold || DEFAULTS.hold;
var select = d.erase === 'select';
var blink = null;
var index = 0;
// A soft blink while waiting; solid while typing, like a real caret.
function blinkOn() {
if (blink) return;
blink = anime.animate(t.cursor, { opacity: [1, 0], duration: 500, ease: 'inOutQuad', loop: true, alternate: true, loopDelay: 120 });
}
function blinkOff() {
if (blink) { blink.revert(); blink = null; }
}
function type(word, i) {
blinkOff();
t.text.textContent = word.slice(0, i);
if (i < word.length) {
return setTimeout(function () { type(word, i + 1); }, speed * anime.utils.random(0.6, 1.5, 2));
}
blinkOn();
setTimeout(erase, hold);
}
function erase() {
var next = function () {
index = (index + 1) % t.words.length;
setTimeout(function () { type(t.words[index], 1); }, speed * 3);
};
if (select) {
// Highlight the whole word, then the first new letter replaces it.
blinkOff();
t.text.classList.add('is-selected');
t.cursor.style.opacity = 0;
return setTimeout(function () {
t.text.classList.remove('is-selected');
t.cursor.style.opacity = '';
t.text.textContent = '';
next();
}, 450);
}
(function back(i) {
blinkOff();
t.text.textContent = t.words[index].slice(0, i);
if (i > 0) return setTimeout(function () { back(i - 1); }, delSpeed);
next();
})(t.words[index].length);
}
// Coming into view: the first word types itself in.
t.text.textContent = '';
blinkOn();
setTimeout(function () { type(t.words[0], 1); }, 500);
}
function init() {
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (!e.isIntersecting) return;
io.unobserve(e.target);
run(e.target._twr);
});
}, { threshold: 0.3 });
document.querySelectorAll('[data-typed-word-rotator]').forEach(function (el) {
if (el._twr) return;
el._twr = build(el);
// Reduced motion: the first word stays, without a cursor.
if (reduced) { el._twr.cursor.style.display = 'none'; return; }
if (el._twr.words.length > 1) io.observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><h1>
We design
<span data-typed-word-rotator="websites, brands, apps">websites</span>
</h1>