Shine Button
Hover the button and a glint sweeps across it, a broad slanted band with a thin streak chasing it, like light catching glass. Loop it and it shines by itself every few seconds to draw the eye.
How to use it
- Paste the snippet just before
</head>. - Add
data-shine-buttonto a button or link with a solid or gradient background.
Options
Add any of these attributes to the same element.
data-loop | Also shine by itself every few seconds while in view. |
data-interval | Time between those shines. Default 2600 ms. |
data-duration | How long one sweep takes. Default 800 ms. |
data-color | Colour of the glint. Default white. |
Keyboard focus sets it off too.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Shine Button: hover the button and a bright glint sweeps across it, a
// broad slanted band with a thin streak chasing it, like light catching
// glass. Keyboard focus sets it off too.
// Usage: <button data-shine-button>Get the deal</button>
// Optional: data-color (glint colour, default white), data-duration (ms
// one sweep takes, default 800), data-loop (also shine by itself every few
// seconds while in view, to draw the eye), data-interval (ms between those
// shines, default 2600).
var DEFAULTS = { duration: 800, interval: 2600 };
var css =
'.shb{position:relative;overflow:hidden;isolation:isolate}' +
'.shb-glint{position:absolute;top:-20%;bottom:-20%;left:0;width:100%;pointer-events:none;z-index:1;transform:translateX(-100%)}' +
'.shb-glint::before, .shb-glint::after{content:"";position:absolute;top:0;bottom:0;transform:skewX(-22deg)}' +
'.shb-glint::before{left:20%;width:34%;' +
'background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--shb-color) 55%,transparent),transparent)}' +
'.shb-glint::after{left:62%;width:8%;' +
'background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--shb-color) 70%,transparent),transparent)}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function setup(el) {
var d = el.dataset;
var duration = +d.duration || DEFAULTS.duration;
el.classList.add('shb');
var glint = document.createElement('span');
glint.className = 'shb-glint';
glint.setAttribute('aria-hidden', 'true');
glint.style.setProperty('--shb-color', d.color || '#fff');
el.appendChild(glint);
var running = false;
function shine() {
if (running) return;
running = true;
// From just off the left edge to just off the right, quick in the middle.
anime.animate(glint, {
x: ['-100%', '100%'],
duration: duration,
ease: 'inOut(2.5)',
onComplete: function () { running = false; }
});
// A small lift as the light passes.
anime.animate(el, {
filter: ['brightness(1)', 'brightness(1.12)', 'brightness(1)'],
duration: duration,
ease: 'inOut(2)'
});
}
el.addEventListener('pointerenter', function (e) { if (e.pointerType !== 'touch') shine(); });
el.addEventListener('focus', shine);
el.addEventListener('touchstart', shine, { passive: true });
if (d.loop !== undefined && d.loop !== 'false') {
var every = +d.interval || DEFAULTS.interval;
var timer = null;
var tick = function () {
if (!el.matches(':hover')) shine();
timer = setTimeout(tick, every);
};
new IntersectionObserver(function (entries) {
clearTimeout(timer);
if (entries[0].isIntersecting) timer = setTimeout(tick, 500);
}).observe(el);
}
}
function init() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
document.querySelectorAll('[data-shine-button]').forEach(function (el) {
if (el._shb) return;
el._shb = true;
setup(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><a class="button" href="/pricing" data-shine-button>Get the deal</a>
<!-- shining every few seconds: -->
<a class="button" href="/pricing" data-shine-button data-loop>Get the deal</a>