Barn Door Wipe
A page transition: when a link is clicked, two panels slide in from the sides and meet in the middle with a soft bump, and on the next page they part again to show it. This page uses it, so try any link.
How to use it
- Add the snippet to every page of your site.
- Add
data-barn-door-wipeto each page’s<body>.
Options
Add any of these attributes to the same element.
data-barn-door-wipe="#hex" | Panel colour. Default #1e1b4b. |
data-direction="vertical" | Panels close from the top and bottom instead. |
data-duration | Time for each close or open. Default 650 ms. |
data-interval | Contained mode only: time each slide stays. Default 2200 ms. |
Links to other sites, new tabs, downloads and #anchors are left alone, and if anything goes wrong the link still opens. Coming back with the Back button opens the doors again. Put data-barn-door-wipe on any other element with two or more children and it becomes a slideshow.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Barn Door Wipe: a page transition. When a link is clicked two panels
// slide in from the sides and meet in the middle with a soft bump; on the
// next page they part again to reveal it.
// Usage: <body data-barn-door-wipe> (add the snippet to every page)
// Optional: data-barn-door-wipe="#hex" (panel colour, default #1e1b4b),
// data-direction="vertical" (panels come from the top and bottom),
// data-duration (ms for each close or open, default 650).
// Contained mode: put data-barn-door-wipe on any other element with two or
// more children and the doors close and part between them every
// data-interval ms (default 2200).
var DEFAULTS = { color: '#1e1b4b', duration: 650, interval: 2200 };
var css =
'.bdw-layer{position:fixed;inset:0;z-index:2147483000;pointer-events:none;visibility:hidden;overflow:hidden}' +
'.bdw-door{position:absolute}' +
// A bright seam along each meeting edge and a shadow cast on the page.
'.bdw-a{box-shadow:inset -2px 0 0 rgba(255,255,255,.18),8px 0 24px rgba(0,0,0,.25)}' +
'.bdw-b{box-shadow:inset 2px 0 0 rgba(255,255,255,.18),-8px 0 24px rgba(0,0,0,.25)}' +
'.bdw-v .bdw-a{box-shadow:inset 0 -2px 0 rgba(255,255,255,.18),0 8px 24px rgba(0,0,0,.25)}' +
'.bdw-v .bdw-b{box-shadow:inset 0 2px 0 rgba(255,255,255,.18),0 -8px 24px rgba(0,0,0,.25)}' +
'.bdw-contained{position:relative;overflow:hidden;display:grid}' +
'.bdw-contained>*:not(.bdw-layer){grid-area:1/1}' +
'.bdw-contained>.bdw-layer{position:absolute}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function layer(parent, color, vertical) {
var l = document.createElement('div');
l.className = 'bdw-layer' + (vertical ? ' bdw-v' : '');
l.setAttribute('aria-hidden', 'true');
['bdw-a', 'bdw-b'].forEach(function (cls, n) {
var d = document.createElement('div');
d.className = 'bdw-door ' + cls;
// Each door reaches a touch past the middle, so they overlap with no
// seam, and runs on past its outer edge, so the bump as they meet never
// opens a gap at the sides.
d.style.cssText = vertical
? 'left:0;right:0;height:calc(65% + 1px);' + (n ? 'bottom:-15%;' : 'top:-15%;')
: 'top:0;bottom:0;width:calc(65% + 1px);' + (n ? 'right:-15%;' : 'left:-15%;');
// A faint sheen, lighter towards the meeting edge.
d.style.background = 'linear-gradient(' + (vertical ? (n ? 'to top' : 'to bottom') : (n ? 'to left' : 'to right')) +
', rgba(0,0,0,.18), rgba(255,255,255,.06)), ' + color;
l.appendChild(d);
});
parent.appendChild(l);
l._bdwDoors = Array.from(l.children);
l._bdwProp = vertical ? 'y' : 'x';
return l;
}
// Close: doors slide in from off-screen. Open: they slide back out.
function doors(l, close, duration, done) {
if (l._bdw) l._bdw.pause();
var d = l._bdwDoors, p = l._bdwProp, a = {}, b = {};
a[p] = close ? ['-100%', '0%'] : ['0%', '-100%'];
b[p] = close ? ['100%', '0%'] : ['0%', '100%'];
a.duration = b.duration = duration;
a.ease = b.ease = close ? 'outBack(1.15)' : 'inOut(3)';
l.style.visibility = 'visible';
var tl = anime.createTimeline({
onComplete: function () { if (!close) l.style.visibility = 'hidden'; if (done) done(); }
});
tl.add(d[0], a, 0).add(d[1], b, 0);
l._bdw = tl;
}
function linkFor(e) {
var a = e.target.closest && e.target.closest('a[href]');
if (!a || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return null;
if ((a.target && a.target !== '_self') || a.hasAttribute('download')) return null;
var url = new URL(a.href, location.href);
if (url.origin !== location.origin) return null;
if (url.pathname === location.pathname && url.search === location.search && url.hash) return null;
return url;
}
function options(el) {
return [el.getAttribute('data-barn-door-wipe') || DEFAULTS.color, el.dataset.direction === 'vertical'];
}
function pageMode(body) {
var duration = +body.dataset.duration || DEFAULTS.duration;
var o = options(body), l = layer(body, o[0], o[1]);
var trip = 0;
doors(l, false, duration);
document.addEventListener('click', function (e) {
var url = linkFor(e);
if (!url) return;
e.preventDefault();
var t = ++trip, gone = false;
var go = function () { if (gone || t !== trip) return; gone = true; location.href = url.href; };
try {
doors(l, true, duration, go);
setTimeout(go, duration + 400);
} catch (err) { go(); }
});
window.addEventListener('pageshow', function (e) {
if (!e.persisted) return;
trip++;
doors(l, false, duration);
});
}
function containedMode(el, reduced) {
el.classList.add('bdw-contained');
var slides = Array.from(el.children);
var duration = +el.dataset.duration || DEFAULTS.duration;
var o = options(el), l = layer(el, o[0], o[1]);
var i = 0;
slides.forEach(function (s, n) { s.style.visibility = n ? 'hidden' : 'visible'; });
if (reduced) return;
var timer = anime.createTimer({
duration: (+el.dataset.interval || DEFAULTS.interval) + duration * 2 + 200,
loop: true,
autoplay: false,
onLoop: function () {
if (el.closest('[hidden]')) return;
doors(l, true, duration, function () {
slides[i].style.visibility = 'hidden';
i = (i + 1) % slides.length;
slides[i].style.visibility = 'visible';
setTimeout(function () { doors(l, false, duration); }, 200);
});
}
});
new IntersectionObserver(function (entries) {
if (entries[0].isIntersecting) timer.play(); else timer.pause();
}).observe(el);
}
function init() {
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
document.querySelectorAll('[data-barn-door-wipe]').forEach(function (el) {
if (el._bdw) return;
el._bdw = true;
if (el === document.body) { if (!reduced) pageMode(el); }
else if (el.children.length > 1) containedMode(el, reduced);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><body data-barn-door-wipe>
…
</body>