Pattern

Scroll-up way back

On phones the way back appears after a short scroll up, and gets out of the way when the reader reads on.

01

Watch it

An article at 390: reading down, a short scroll up, reading on. Recorded from the live code.
02

Rules

Appears
After 200px of scroll and a 40px move up from the lowest point since the last scroll down.
Hides
On any scroll down, and above 200px.
Where
Every page but the home. Phones always; tablets only without a rail; never on desktop.
What
The band’s own trail, rebuilt from its links: “← Index” or “← Index / Parent”.
Motion
200ms opacity and 8px translate; instant under reduced motion.
Without JavaScript
Does not exist; the band carries the way back.
03

Code

From site/assets/js/site.js, lines 48 to 101.

// ------------------------------------------------------- scroll-up way back
const from = document.querySelector('main .crumbs ol, main a.band-back');
if (from) {
    const up = document.createElement('nav');
    up.className = 'up-index';
    up.setAttribute('aria-label', 'Way back');
    if (document.querySelector('main .rail')) up.classList.add('with-rail');
    (from.matches('a') ? [from] : [...from.querySelectorAll('a')]).forEach((a, i) => {
        if (i) {
            const sep = document.createElement('span');
            sep.className = 'up-index-sep';
            sep.setAttribute('aria-hidden', 'true');
            sep.textContent = '/';
            up.append(sep);
        }
        const link = document.createElement('a');
        link.href = a.getAttribute('href');
        if (link.getAttribute('href') === '/') {
            const arrow = document.createElement('span');
            arrow.setAttribute('aria-hidden', 'true');
            arrow.textContent = '\u2190\u00a0\u00a0';
            link.append(arrow);
        }
        link.append(a.textContent.replace(/^\u2190\s*/, '').trim());
        up.append(link);
    });
    (document.querySelector('.skip') || document.body.firstChild).after(up);
    let last = scrollY;
    let low = scrollY; // the lowest point since the reader last scrolled down
    let on = false;
    const show = (v) => {
        if (v === on) return;
        on = v;
        up.classList.toggle('is-on', v);
    };
    addEventListener(
        'scroll',
        () => {
            const y = scrollY;
            if (y <= 200) {
                show(false);
                low = y;
            } else if (y > last) {
                show(false);
                low = y;
            } else if (low - y >= 40) {
                show(true);
            }
            last = y;
        },
        { passive: true }
    );
}