Hologram
Beam any content in as a hologram. It flickers into being, tinted one glowing blue and cut by rolling scanlines, then floats gently on a cone of light from a little projector. Now and then the signal drops: it flickers, or tears sideways for a moment.
Incoming messageRelay 7 · 03:42
How to use it
- Paste the snippet just before
</head>. - Add
data-hologramto any element: a card, a heading, an image. Light content on a dark background looks best.
Options
Add any of these attributes to the same element.
data-color | The hologram’s colour. Default #5fd4ff. |
data-base | false leaves out the projector and the cone of light. |
data-glitch | How often the signal drops, from 0 to 2. Default 1; 0 for never. |
With reduced motion it keeps the colour and the scanlines but stays still.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Hologram: turns any content into a flickering blue hologram. It is tinted
// one glowing colour, cut by rolling scanlines, floats gently up and down,
// and now and then flickers or jumps sideways as the signal drops. A small
// projector glows underneath with a cone of light rising from it.
// Usage: <div data-hologram>…any content…</div>
// Optional: data-color (the hologram's colour, default "#5fd4ff"),
// data-base ("false" leaves out the projector and the light cone),
// data-glitch (how often it glitches, 0 to 2, default 1; 0 for never).
var DEFAULTS = { color: '#5fd4ff', glitch: 1 };
var css =
'.hlg{position:relative;isolation:isolate}' +
'.hlg-on{padding-bottom:30px}' +
'.hlg-body{position:relative;z-index:1;opacity:.9;' +
'-webkit-mask-image:repeating-linear-gradient(0deg,#000 0 2px,rgba(0,0,0,.4) 2px 4px);mask-image:repeating-linear-gradient(0deg,#000 0 2px,rgba(0,0,0,.4) 2px 4px);' +
'-webkit-mask-size:100% 8px;mask-size:100% 8px}' +
'.hlg-roll .hlg-body{animation:hlg-roll 1.2s linear infinite}' +
'.hlg-base{position:absolute;left:50%;bottom:6px;width:min(60%,180px);height:14px;transform:translateX(-50%);border-radius:50%;z-index:0;pointer-events:none}' +
'.hlg-base::before{content:"";position:absolute;inset:0;border-radius:50%;background:radial-gradient(closest-side,#fff,var(--hlg-c) 45%,transparent);filter:blur(1px)}' +
'.hlg-base::after{content:"";position:absolute;left:50%;bottom:50%;width:min(320%,500px);height:var(--hlg-h,200px);transform:translateX(-50%);' +
'background:linear-gradient(to top,var(--hlg-c),transparent 85%);opacity:.22;clip-path:polygon(42% 100%,58% 100%,100% 0,0 0);filter:blur(3px)}' +
'@keyframes hlg-roll{to{-webkit-mask-position:0 8px;mask-position:0 8px}}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function hex(c) {
var m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec((c || '').trim());
if (!m) return [95, 212, 255];
var s = m[1].length === 3 ? m[1].replace(/./g, '$&$&') : m[1];
return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
}
// One SVG filter per colour: brightness becomes the hologram colour, lifting the darks a little.
var filters = {};
function tint(color) {
if (filters[color]) return filters[color];
var c = hex(color).map(function (v) { return v / 255; });
var id = 'hlg-f' + Object.keys(filters).length;
var row = function (v) {
var k = 1.35;
return (v * 0.3 * k).toFixed(3) + ' ' + (v * 0.59 * k).toFixed(3) + ' ' + (v * 0.11 * k).toFixed(3) + ' 0 ' + (v * 0.12 + 0.04).toFixed(3);
};
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('width', '0');
svg.setAttribute('height', '0');
svg.style.position = 'absolute';
svg.innerHTML = '<filter id="' + id + '" color-interpolation-filters="sRGB"><feColorMatrix type="matrix" values="' +
row(c[0]) + ' ' + row(c[1]) + ' ' + row(c[2]) + ' 0 0 0 1 0"/></filter>';
document.body.appendChild(svg);
filters[color] = id;
return id;
}
function setup(el) {
var d = el.dataset;
var color = d.color || DEFAULTS.color;
var glitch = d.glitch !== undefined ? +d.glitch : DEFAULTS.glitch;
var base = d.base !== 'false';
el.style.setProperty('--hlg-c', color);
var body = document.createElement('div');
body.className = 'hlg-body';
while (el.firstChild) body.appendChild(el.firstChild);
el.appendChild(body);
body.style.filter = 'url(#' + tint(color) + ') drop-shadow(0 0 6px ' + color + ')';
if (base) {
el.classList.add('hlg-on');
var b = document.createElement('span');
b.className = 'hlg-base';
b.setAttribute('aria-hidden', 'true');
el.appendChild(b);
var fit = function () { b.style.setProperty('--hlg-h', Math.max(40, body.offsetHeight + 10) + 'px'); };
fit();
if (window.ResizeObserver) new ResizeObserver(fit).observe(body);
}
if (reduced()) return;
el.classList.add('hlg-roll');
var visible = false, started = false, glitchTimer = 0;
var float = anime.animate(body, {
y: [-4, 4], duration: 2600, ease: 'inOut(2)', loop: true, alternate: true, autoplay: false
});
// Signal trouble: a quick flicker, sometimes with a sideways tear.
function trouble() {
if (!visible || glitch <= 0) return;
var tear = Math.random() < 0.45;
anime.animate(body, {
opacity: [0.9, 0.35, 0.85, 0.5, 0.9],
x: tear ? [0, anime.utils.random(-10, 10), anime.utils.random(-5, 5), 0] : 0,
skewX: tear ? [0, anime.utils.random(-12, 12), 0] : 0,
duration: tear ? 260 : 200,
ease: 'linear'
});
next();
}
function next() {
clearTimeout(glitchTimer);
glitchTimer = setTimeout(trouble, (1200 + Math.random() * 2600) / Math.max(0.2, glitch));
}
// A gentle, constant shimmer in brightness.
var shimmer = anime.createTimer({
loop: true, duration: 1000, autoplay: false,
onUpdate: function () {
if (Math.random() < 0.35) body.style.opacity = (0.82 + Math.random() * 0.1).toFixed(2);
}
});
new IntersectionObserver(function (entries) {
visible = entries[0].isIntersecting;
if (visible) {
if (!started) {
// It flickers into being the first time.
started = true;
anime.animate(body, { opacity: [0, 0.6, 0.1, 0.9, 0.4, 0.9], scaleY: [0.02, 1], duration: 650, ease: 'out(3)' });
}
float.play();
shimmer.play();
next();
} else {
float.pause();
shimmer.pause();
clearTimeout(glitchTimer);
}
}, { threshold: 0.2 }).observe(el);
}
function init() {
document.querySelectorAll('[data-hologram]').forEach(function (el) {
if (el._hlg) return;
el._hlg = true;
el.classList.add('hlg');
setup(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div data-hologram>
<img src="avatar.png" alt="">
<p>Incoming message</p>
</div>