ScrollProgress
A thin bar along the top of the window, or of a scrolling box, that fills as it is scrolled.
Demo
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.qweimport 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.
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
// 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>}Related
Thread 0 posts
On ScrollProgress: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.