Title Card
A bold title card that builds itself for a video intro. Corner brackets snap out, a rule draws outwards from the centre, the kicker spreads in, the title’s words rise from behind the line, a second rule draws underneath and the subtitle fades up.
Episode 04
The Long Way Home
A film by Ava Moreno
How to use it
- Paste the snippet just before
</head>. - Add
data-title-cardto a box holding a heading. Anything before the heading becomes the small kicker line, anything after it the subtitle. - Size it with
font-size; the title is 2.6 times that.
Options
Add any of these attributes to the same element.
data-color | Colour of the lines and brackets. Default: the text colour. |
data-loop | Take itself apart and build again. |
data-hold | How long it stays built before looping. Default 2600 ms. |
data-corners="false" | Leave out the corner brackets. |
data-delay | Wait before it starts building. Default 200 ms. |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Title Card: a bold title card that builds itself, for video intros.
// Corner brackets snap out, a rule draws outwards from the centre, the
// small kicker line spreads in, the title's words rise from behind the
// rule, a second rule draws under it and the subtitle fades up. Loop it and
// it takes itself apart again before rebuilding.
// Usage: <div data-title-card>
// <small>Episode 04</small>
// <h2>The Long Way Home</h2>
// <p>A film by Ava Moreno</p>
// </div>
// Everything before the heading is the kicker, everything after it the
// subtitle; both are optional. Size it with font-size.
// Optional: data-color (lines and brackets, default the text colour),
// data-loop (take it apart and build it again), data-hold (ms it stays
// built before looping, default 2600), data-corners="false" (no brackets),
// data-delay (ms before it starts, default 200).
var DEFAULTS = { hold: 2600, delay: 200 };
var css =
'.ttc{display:inline-block;text-align:center}' +
'.ttc-in{position:relative;display:flex;flex-direction:column;align-items:center;padding:1.1em 1.6em;--ttc-c:currentColor}' +
'.ttc-in>*{margin:0}' +
'.ttc-kick{display:block;text-transform:uppercase;letter-spacing:0.3em;font-size:0.8em;font-weight:600;opacity:.8;margin-bottom:0.7em;padding-left:0.3em}' +
'.ttc-head{font-size:2.6em;line-height:1.02;font-weight:800;letter-spacing:-0.03em;margin:0.18em 0 0.22em}' +
'.ttc-w{display:inline-block;overflow:hidden;vertical-align:top;padding:0.06em 0.04em 0.1em;margin:-0.06em -0.04em -0.1em}' +
'.ttc-wi{display:inline-block}' +
'.ttc-sub{display:block;margin-top:0.8em;font-size:0.95em;opacity:.8}' +
'.ttc-rule{display:block;width:100%;height:0.12em;min-height:2px;background:var(--ttc-c);border-radius:1px}' +
'.ttc-corner{position:absolute;width:1em;height:1em;border:0 solid var(--ttc-c)}' +
'.ttc-tl{left:0;top:0;border-width:0.14em 0 0 0.14em;transform-origin:0 0}' +
'.ttc-tr{right:0;top:0;border-width:0.14em 0.14em 0 0;transform-origin:100% 0}' +
'.ttc-bl{left:0;bottom:0;border-width:0 0 0.14em 0.14em;transform-origin:0 100%}' +
'.ttc-br{right:0;bottom:0;border-width:0 0.14em 0.14em 0;transform-origin:100% 100%}';
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 kids = Array.prototype.slice.call(el.children);
var hi = -1;
kids.forEach(function (k, i) { if (hi < 0 && /^H[1-6]$/.test(k.tagName)) hi = i; });
if (hi < 0) hi = kids.length > 1 ? 1 : 0;
var head = kids[hi];
var before = kids.slice(0, hi), after = kids.slice(hi + 1);
el.classList.add('ttc');
var inner = document.createElement('div');
inner.className = 'ttc-in';
if (d.color) inner.style.setProperty('--ttc-c', d.color);
// The title's words, each behind its own little mask.
var text = head ? head.textContent.replace(/\s+/g, ' ').trim() : '';
var words = [];
if (head) {
head.classList.add('ttc-head');
head.setAttribute('aria-label', text);
head.textContent = '';
text.split(' ').forEach(function (w, i) {
if (i) head.appendChild(document.createTextNode(' '));
var o = document.createElement('span');
o.className = 'ttc-w';
o.setAttribute('aria-hidden', 'true');
var wi = document.createElement('span');
wi.className = 'ttc-wi';
wi.textContent = w;
o.appendChild(wi);
head.appendChild(o);
words.push(wi);
});
}
function rule() {
var r = document.createElement('span');
r.className = 'ttc-rule';
r.setAttribute('aria-hidden', 'true');
return r;
}
var top = rule(), bottom = rule();
before.forEach(function (k) { k.classList.add('ttc-kick'); inner.appendChild(k); });
inner.appendChild(top);
if (head) inner.appendChild(head);
inner.appendChild(bottom);
after.forEach(function (k) { k.classList.add('ttc-sub'); inner.appendChild(k); });
var corners = [];
if (d.corners !== 'false') {
['tl', 'tr', 'bl', 'br'].forEach(function (c) {
var s = document.createElement('span');
s.className = 'ttc-corner ttc-' + c;
s.setAttribute('aria-hidden', 'true');
inner.appendChild(s);
corners.push(s);
});
}
el.textContent = '';
el.appendChild(inner);
el._ttc = { corners: corners, top: top, bottom: bottom, words: words, before: before, after: after };
if (!reduced()) reset(el);
}
function reset(el) {
var s = el._ttc;
if (s.corners.length) anime.utils.set(s.corners, { scale: 0, opacity: 0 });
anime.utils.set([s.top, s.bottom], { scaleX: 0 });
anime.utils.set(s.words, { y: '110%' });
if (s.before.length) anime.utils.set(s.before, { opacity: 0, letterSpacing: '0.9em' });
if (s.after.length) anime.utils.set(s.after, { opacity: 0, y: '0.6em' });
}
function play(el) {
var s = el._ttc, d = el.dataset;
var loop = d.loop !== undefined && d.loop !== 'false';
var hold = d.hold !== undefined ? +d.hold : DEFAULTS.hold;
var tl = anime.createTimeline({
delay: d.delay !== undefined ? +d.delay : DEFAULTS.delay,
loop: loop,
loopDelay: 500
});
if (s.corners.length) {
tl.add(s.corners, { scale: [0, 1], opacity: [0, 1], duration: 500, delay: anime.stagger(70), ease: 'outBack(2)' });
}
tl.add(s.top, { scaleX: [0, 1], duration: 650, ease: 'inOut(3)' }, s.corners.length ? '-=350' : 0);
if (s.before.length) {
tl.add(s.before, { opacity: [0, 1], letterSpacing: ['0.9em', '0.3em'], duration: 900, ease: 'out(4)' }, '-=400');
}
tl.add(s.words, { y: ['110%', '0%'], duration: 750, delay: anime.stagger(80), ease: 'out(4)' }, s.before.length ? '-=700' : '-=250')
.add(s.bottom, { scaleX: [0, 1], duration: 650, ease: 'inOut(3)' }, '-=650');
if (s.after.length) {
tl.add(s.after, { opacity: [0, 1], y: ['0.6em', '0em'], duration: 600, ease: 'out(3)' }, '-=300');
}
if (loop) {
// Take it apart: words drop away, lines pull back to the centre.
tl.add(s.words, { y: '-110%', duration: 450, delay: anime.stagger(40), ease: 'in(3)' }, '+=' + hold);
var fade = s.before.concat(s.after);
if (fade.length) tl.add(fade, { opacity: 0, duration: 350, ease: 'in(2)' }, '<<');
tl.add([s.top, s.bottom], { scaleX: 0, duration: 450, ease: 'inOut(3)' }, '-=200');
if (s.corners.length) tl.add(s.corners, { scale: 0, opacity: 0, duration: 300, ease: 'in(2)' }, '-=300');
}
}
function init() {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (!e.isIntersecting) return;
io.unobserve(e.target);
play(e.target);
});
}, { threshold: 0.2 });
document.querySelectorAll('[data-title-card]').forEach(function (el) {
if (el._ttc) return;
setup(el);
if (!reduced()) io.observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div data-title-card data-color="#c084fc">
<small>Episode 04</small>
<h2>The Long Way Home</h2>
<p>A film by Ava Moreno</p>
</div>