Flow

@glitchr/flowjs 1.6.1 one library, one module per feature, no build step required

Pages that load in place.
Sections the scroll rests on, scenes it plays.

Flow is the scroll and the navigation of server-rendered pages: links and forms that load the next page in the background, a website inside the website, a header that steps aside, scroll stops, scenes, sortable lists and trees, named AJAX channels, the cookies and the visitor's consent. Everything on this site runs on it.

npm install @glitchr/flowjs Read the manual API reference

This page is a line of stations: scroll and let go, or press ↓, Space, Page Down - and watch the address follow you.

Sticky · StickyStops · StickyScenes manual · API

Scrolling: headers, stops and scenes

What the scroll does on a page: a header that steps aside and an address that follows the reading, places the scroll comes to rest on, scenes it plays.

Sticky manual · API

Sticky headers, and an address that follows the reading

  • Look at the address bar: it reads #sticky now - the panel being read - and changes as you scroll, without a history entry.
  • Scroll down a little: the header leaves; up a little, it is back (.sticky-top).
  • Also: magnets, a bottom bar, widgets, fading blocks, scroll-percent triggers, swipes, autoscroll.

The markup
<header class="site-head sticky-top">…</header>
<section id="scrolling" class="sticky-headlines">
    <div id="sticky">…</div>
</section>
site.js
Sticky.ready({ scrollhide: true, replacehash: true });
button.addEventListener('click', function () { Sticky.scrollTo(0); });

StickyStops manual · API

Scroll stops

Places a scroll settles on once it comes to rest - momentum included; the arrows go from one to the next, a tall one read a screen at a time.

A scroller of its ownstop 1 of 5
1 A short stop
2 A tall stop, read a screen at a time with the keys (screens)… its second screen… its end
3 A short stop
4 A stop barely taller than the scroller: its top alone (shortEnd)
5 The last stop
A row, sideways (data-sticky-axis="x")
Navigation
Sticky
Stops
Scenes
Sortable
Ajaxer
Cookie
stops.js
StickyStops.attach(scroller, {
    keys: true,        // the arrows, Page Up / Down, Space go stop to stop
    screens: true,     // a tall stop: a screen at a time, top to end
    duration: 600,     // a glide of its own, along the easing curve
    reach: 200         // px: only a rest near a stop is completed
});
// The page itself: its sections are stops too.
StickyStops.attach(window, { keys: true, screens: true, up: false, reach: '40vh' });

StickyScenes · new in 1.6 manual · API

Scenes the scroll plays - here, sideways

Scroll the scene to the side - a trackpad, Shift and the wheel, a finger, or ← → once it has the focus: the laptop turns and opens, step by step. The page's own scroll stays free.

1. Closed The scene at its start: --scene-progress is 0.
2. It turns --scene-progress goes 0 → 1 as the track scrolls: the device turns to face you.
3. It opens Each panel is a step (data-scene-panel, scroll-snap): the lid lifts.
progress 0.00 · step 1 of 3
The markup
<section class="sticky-scene" data-scene-axis="x">
    <div class="sticky-scene-stage">
        <div class="laptop">…</div>
        <div data-scene-track aria-label="The laptop scene">
            <article data-scene-panel>Closed</article>
            <article data-scene-panel>It turns</article>
            <article data-scene-panel>It opens</article>
        </div>
        <span data-scene-hint>→</span>
    </div>
</section>
The scene is CSS
.laptop {
    transform: rotateY(calc(-42deg + var(--scene-progress) * 42deg));
}
.laptop-lid {   /* open over the second half */
    --open: clamp(0, calc((var(--scene-progress) - .5) * 2), 1);
    transform: rotateX(calc(-90deg + var(--open) * 100deg));
}

A vertical scene pins its stage while the page scrolls through it (data-scene-length), and the arrows, Page Down and Space go from step to step. With prefers-reduced-motion a scene shows its end, still.

StickySortable manual · API

Sortable lists, and trees

Drag-and-drop with pointer events - mouse, pen and touch on one code path (a long press on a phone); the arrows on a handle for the keyboard. A list, or a tree whose items move between levels.

A list

  1. Navigation
  2. Scrolling
  3. Sortable
  4. Ajaxer
  5. Cookie

The order: Navigation, Scrolling, Sortable, Ajaxer, Cookie

A tree: chapters and their sections

  1. Getting started
    1. Install
    2. First page
  2. Navigation
    1. Links and forms
    2. The progress bar
    3. Frames
  3. Scrolling
    1. Headers
    2. Stops
  4. Appendixtakes no section

Drag a section into another chapter, or out to be a chapter; on a handle, ↑ ↓ move, → nests under the item above, ← takes out. Two levels at most.


            
A list
new StickySortable(list, {
    handle: '.grip',
    onChange: function (order) {
        output.textContent = order.map(function (item) {
            return item.textContent.trim();
        }).join(', ');
    }
});
A tree
var tree = new StickySortable.Tree(book, {
    handle: '.grip',
    maxDepth: 1                 // chapters and sections, no deeper
});
book.addEventListener('sortable:change', function (e) {
    // e.detail: item, parent, index, depth, tree
    out.textContent = JSON.stringify(e.detail.tree, null, 1);
});

Ajaxer manual · API

Named AJAX channels

A channel is a name with its address, method, throttle, debounce, loader and retries: press quickly, one request leaves.

0 asked · 0 answered

Press the button.

    See the response
    Nothing asked yet.

    Each press asks one of 64 static files at random (/demo/names/<n>.json): a real request, a different answer. Press several times quickly: the channel's throttle lets one request a second leave - the counters show it.

    ajaxer.js
    Ajaxer.setMethod('names', 'GET');  // a static host: GET
    Ajaxer.setThrottle('names', '1s'); // one a second at most
    Ajaxer.setTarget('names', box);    // its loader, its classes
    button.addEventListener('click', function () {
        var n = 1 + Math.floor(Math.random() * 64);
        Ajaxer.setUrl('names', '/demo/names/' + n + '.json');
        Ajaxer.send('names', {}, function (data) {
            name.textContent = data.name;  // "Brave Otter"
        });
    });

    Take what a page needs

    With a bundler
    import '@glitchr/flowjs';              // everything, with the stylesheets
    import '@glitchr/flowjs/navigation';   // Transparent and FlowFrame (jQuery)
    import '@glitchr/flowjs/stops';        // or one module: StickyStops
    import '@glitchr/flowjs/scenes';       // StickyScenes
    Without one
    <script src="flowjs/src/js/core.js"></script>
    <script src="flowjs/src/js/stops.js"></script>
    <script src="flowjs/src/js/scenes.js"></script>