Logo Wall Swap
A grid of logos where, every couple of seconds, one tile tips over and comes back with a different logo on it. A small wall can show a long list of customers, and it never flips the same tile twice in a row.
Northwind
Lumen
Kitebox
Pinegrove
Tandem
Quarry
Driftwood
Fieldnote
Halcyon
Brightside
Mosaic
Vela
Harbour
Ember
Nimbus
Orbitly
How to use it
- Paste the snippet just before
</head>. - Add
data-logo-wall-swapto a box holding your logos. - Give it more logos than tiles: the spare ones wait their turn to be flipped in.
Options
Add any of these attributes to the same element.
data-columns | How many tiles across. Default 4. Or set --lws-cols in CSS, say in a media query for phones. |
data-visible | How many tiles to show. Default all the logos but three. |
data-interval | Time between swaps. Default 1800 ms. |
data-duration | Time for one flip. Default 800 ms. |
data-axis | x tips forwards; y turns sideways. Default x. |
It only swaps while on screen, and with reduced motion it just shows the first set.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Logo Wall Swap: a grid of logos where, every so often, one tile flips
// over and comes back showing a different logo, so a small wall can show
// off a long list of names. Never the same tile twice in a row.
// Usage: <div data-logo-wall-swap data-columns="4">
// <img src="acme.svg" alt="Acme"> <img src="globex.svg" alt="Globex"> …
// </div>
// Give it more logos than tiles: the spare ones wait their turn.
// Optional: data-columns (default 4; or set --lws-cols in CSS, e.g. in a
// media query), data-visible (how many tiles, default all the logos but
// three), data-interval (ms between swaps, default 1800), data-duration
// (ms for one flip, default 800), data-axis ("x", default, tips forwards;
// "y" turns sideways).
var DEFAULTS = { columns: 4, interval: 1800, duration: 800 };
var css =
'.lws{display:grid;grid-template-columns:repeat(var(--lws-cols,4),minmax(0,1fr))}' +
'.lws-cell{display:grid;perspective:600px;min-width:0}' +
'.lws-in{display:grid;place-items:center;min-width:0;transform-style:preserve-3d;-webkit-backface-visibility:hidden;backface-visibility:hidden}' +
'.lws-in>*{max-width: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 logos = Array.prototype.slice.call(el.children);
if (logos.length < 2) return;
el.classList.add('lws');
if (d.columns) el.style.setProperty('--lws-cols', d.columns);
var visible = Math.max(1, Math.min(logos.length - 1, parseInt(d.visible, 10) || logos.length - 3));
var cells = [];
logos.forEach(function (logo, i) {
if (i >= visible) { el.removeChild(logo); return; }
var cell = document.createElement('div');
cell.className = 'lws-cell';
var inner = document.createElement('div');
inner.className = 'lws-in';
el.insertBefore(cell, logo);
inner.appendChild(logo);
cell.appendChild(inner);
cells.push({ inner: inner, logo: logo });
});
var s = el._lwsS = { cells: cells, pool: logos.slice(visible), last: -1, timer: 0, visible: false, busy: false };
if (reduced()) return;
var interval = +d.interval || DEFAULTS.interval;
function tick() {
if (s.visible && !document.hidden) swap(el);
s.timer = setTimeout(tick, interval);
}
new IntersectionObserver(function (en) {
s.visible = en[0].isIntersecting;
if (s.visible && !s.timer) s.timer = setTimeout(tick, interval * 0.6);
}, { threshold: 0.2 }).observe(el);
}
function swap(el) {
var s = el._lwsS, d = el.dataset;
if (!s.pool.length) return;
var i;
do { i = Math.floor(Math.random() * s.cells.length); } while (s.cells.length > 1 && i === s.last);
s.last = i;
var c = s.cells[i];
if (c.busy) return;
c.busy = true;
var dur = +d.duration || DEFAULTS.duration;
var prop = d.axis === 'y' ? 'rotateY' : 'rotateX';
var out = {}, back = {};
out[prop] = [0, 90];
back[prop] = [-90, 0];
var tl = anime.createTimeline();
tl.add(c.inner, Object.assign(out, { opacity: [1, 0.4], duration: dur * 0.42, ease: 'in(2)' }))
.call(function () {
var next = s.pool.shift();
s.pool.push(c.logo);
c.inner.replaceChild(next, c.logo);
c.logo = next;
})
.add(c.inner, Object.assign(back, { opacity: [0.4, 1], duration: dur * 0.58, ease: 'outBack(1.6)' }))
.call(function () { c.busy = false; });
}
function init() {
document.querySelectorAll('[data-logo-wall-swap]').forEach(function (el) {
if (el._lws) return;
el._lws = true;
setup(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div data-logo-wall-swap data-columns="4" data-visible="8">
<img src="logos/acme.svg" alt="Acme">
<img src="logos/globex.svg" alt="Globex">
<!-- … ten or more logos -->
</div>