Utilities

ScrollProgress

A thin bar along the top of the window, or of a scrolling box, that fills as it is scrolled.

#utility#progress#page#interactive

Demo

Preview

Paragraph 1 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 2 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 3 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 4 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 5 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 6 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 7 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 8 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 9 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 10 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 11 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 12 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 13 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 14 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Paragraph 15 of a long article. Scroll this box: the green bar on its top edge fills as you go.

Read 0% of the box.

The page has one too: the thin bar along the top of the window, filling as this page is scrolled.

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/scroll_progress.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/scroll_progress.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/scroll_progress.qwe
component ScrollProgressDemoLive() {    state at = 0    <div.demo-stack>        <div id="scroll-progress-demo-box" tabindex="0" aria-label="A long article"             style="height: 220px; overflow: auto; border: 1px solid var(--ui-border); border-radius: var(--ui-radius)">            <ScrollProgress scroller="scroll-progress-demo-box" height={4} tone="success" label="Article progress" on_change={(p) => { at = p }}/>            <div style="padding: 4px 18px">                {for i in range(1, 16) => <p key={i}>Paragraph {i} of a long article. Scroll this box: the green bar on its top edge fills as you go.</p>}            </div>        </div>        <p.demo-note>Read {at}% of the box.</p>    </div>}view ScrollProgressDemo() =    <div.demo-stack>        <ScrollProgressDemoLive/>        <ScrollProgress/>        <p.demo-note>The page has one too: the thin bar along the top of the window, filling as this page is scrolled.</p>    </div>

Source

ui/scroll_progress.qwe
// ScrollProgress: a thin bar along the top of the window that fills as the page// is scrolled, so a reader sees how far along an article they are. It can// follow a scrolling box instead of the page.////   <ScrollProgress/>//   <ScrollProgress tone="success" height={4} position="bottom"/>//   <div id="log" style="height: 300px; overflow: auto"><ScrollProgress scroller="log"/> ...</div>//// position: "top" (default) or "bottom" of the window. scroller: the id of a// scrolling element to follow instead; put the ScrollProgress first inside it,// and the bar sticks to that box's top. height: the bar's thickness in pixels// (3). tone: "accent" (default), "success", "info", "warning", "danger" or// "neutral". label: what screen readers call it ("Reading progress"); it is a// progressbar, and they hear the percent. A page too short to scroll shows a// full bar. on_change(percent): inside one of your components, called as it moves.import ui/theme.qwe use ui_clslet scroll_progress_css = css`.ui-scrollprog { position: fixed; left: 0; right: 0; top: 0; z-index: 70; height: var(--h, 3px); background: transparent; pointer-events: none;  --t: var(--ui-accent); }.ui-scrollprog-bottom { top: auto; bottom: 0; }.ui-scrollprog-inline { position: sticky; top: 0; left: auto; right: auto; z-index: 3; margin-bottom: calc(-1 * var(--h, 3px)); }.ui-scrollprog-fill { height: 100%; width: 0; background: var(--t); transition: width .1s linear; }.ui-scrollprog-success { --t: var(--ui-success); }.ui-scrollprog-info { --t: var(--ui-info); }.ui-scrollprog-warning { --t: var(--ui-warning); }.ui-scrollprog-danger { --t: var(--ui-danger); }.ui-scrollprog-neutral { --t: var(--ui-muted); }@media (prefers-reduced-motion: reduce) { .ui-scrollprog-fill { transition: none; } }`component ScrollProgress(position: str = "top", scroller: str? = null, height: int = 3, tone: str = "accent",                         label: str = "Reading progress", on_change = null, class: str? = null) {    style global scroll_progress_css    state pct = 0    <div class={ui_cls(["ui-scrollprog", scroller != null ? "ui-scrollprog-inline" : position == "bottom" && "ui-scrollprog-bottom",                        tone != "accent" && `ui-scrollprog-${tone}`, class])}         style={`--h: ${height}px`} role="progressbar" aria-label={label} aria-valuemin="0" aria-valuemax="100"         aria-valuenow={round(pct)} aria-valuetext={`${round(pct)}%`}         @mount={(_el) => {             let box = scroller != null ? js("document.getElementById", scroller) : null             let from = box ?? js("window")             let measure = (_e = null) => {                 let top = box != null ? box.scrollTop : js("scrollY")                 let room = box != null ? box.scrollHeight - box.clientHeight                     : js("document.documentElement").scrollHeight - js("innerHeight")                 let p = room > 0 ? max([0, min([100, top * 100 / room])]) : 100                 if round(p) != round(pct) {                     pct = p                     on_change && on_change(round(p))                 }             }             measure()             from.addEventListener("scroll", measure, { passive: true })             js("window").addEventListener("resize", measure, { passive: true })             () => {                 from.removeEventListener("scroll", measure)                 js("window").removeEventListener("resize", measure)             }         }}>        <div.ui-scrollprog-fill style={`width: ${pct}%`}></div>    </div>}
Thread
77Useful? Like it, or add to its thread below.

Thread 0 posts

On ScrollProgress: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.