Media

Ticker

A news ticker: a labelled strip of headlines sliding past, each a link when it has one, in CSS alone.

#animation#news#media#no-js

Demo

Preview
  • Vote count at 62%
  • Turnout 71%
  • Results at 22:00
  • All systems normal
  • Last incident 41 days ago

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/ticker.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/ticker.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/ticker.qwe
let ticker_demo_news = [    { text: "Quillo 0.5 ships with live data over one WebSocket", href: "https://quillolang.org/blog/0.5" },    "Rain expected along the coast tonight",    { text: "Final: Benfica 2 – 1 Porto", href: "https://example.com/match" },    "Metro line B closed this weekend for works",    "Markets open higher after a quiet night",]view TickerDemo() =    <div.demo-stack>        <Ticker label="Breaking" items={ticker_demo_news} speed={28}/>        <Ticker label="Live" tone="danger" speed={40} items={["Vote count at 62%", "Turnout 71%", "Results at 22:00"]}/>        <Ticker label="Status" tone="success" items={["All systems normal", "Last incident 41 days ago"]} name="Service status"/>    </div>

Source

ui/ticker.qwe
// Ticker: a news ticker: a label at the left ("Breaking", "Live") and headlines// sliding past it without end, each a link when it has one. CSS alone moves it;// it pauses while the pointer or the focus is on it, and stands still (scrolling// by hand) for people who ask their system for less motion.////   <Ticker label="Breaking" items={["Markets open higher", "Rain expected tonight"]}/>//   <Ticker label="Live" tone="danger" speed={40} items={[{ text: "Final: 2–1", href: "/match/9" }, ...]}/>//// items: strings, or maps of { text, href? }. label: the text at the left;// tone: its colour, "accent" (default), "danger", "success" or "neutral".// speed: seconds for one full loop (30). name: what screen readers call the// ticker (the label by default); they read the items once.import ui/theme.qwe use ui_clslet ticker_css = css`.ui-ticker {  display: flex; align-items: stretch; overflow: hidden; width: 100%; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm);  background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.4 var(--ui-font);}.ui-ticker-label {  flex: none; display: flex; align-items: center; padding: 8px 14px; background: var(--ui-accent); color: var(--ui-accent-fg);  font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; white-space: nowrap;}.ui-ticker-danger .ui-ticker-label { background: var(--ui-danger); color: #fff; }.ui-ticker-success .ui-ticker-label { background: var(--ui-success); color: #fff; }.ui-ticker-neutral .ui-ticker-label { background: var(--ui-fg); color: var(--ui-bg); }.ui-ticker-view {  flex: 1; min-width: 0; overflow: hidden;  mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);  -webkit-mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);}.ui-ticker-track { display: flex; width: max-content; height: 100%; animation: ui-ticker var(--t, 30s) linear infinite; }.ui-ticker:hover .ui-ticker-track, .ui-ticker:focus-within .ui-ticker-track { animation-play-state: paused; }.ui-ticker-list { display: flex; flex: none; align-items: center; margin: 0; padding: 0 0 0 20px; list-style: none; }.ui-ticker-list li { display: flex; align-items: center; white-space: nowrap; }.ui-ticker-list li::after { content: ""; width: 5px; height: 5px; margin: 0 20px; border-radius: 50%; background: var(--ui-strong); }.ui-ticker-list a { color: inherit; text-decoration: none; }.ui-ticker-list a:hover { color: var(--ui-accent); text-decoration: underline; }.ui-ticker-list a:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 3px; }@keyframes ui-ticker { to { transform: translateX(-50%); } }@media (prefers-reduced-motion: reduce) {  .ui-ticker-view { overflow-x: auto; mask-image: none; -webkit-mask-image: none; }  .ui-ticker-track { animation: none; }  .ui-ticker-list[aria-hidden] { display: none; }}`fn ticker_items(items: list) -> list =    items | map((it) => type_of(it) == "map" ? { text: str(it.text), href: get(it, "href") } : { text: str(it), href: null })view Ticker(items: list = [], label: str = "News", tone: str = "accent", speed: num = 30, name: str? = null, class: str? = null) {    style global ticker_css    let its = ticker_items(items)    let row = (hidden: bool) =>        <ul.ui-ticker-list aria-hidden={hidden ? "true" : null}>            {for [i, it] in enumerate(its) => <li key={i}>                {it.href != null ? <a href={it.href} tabindex={hidden ? "-1" : null}>{it.text}</a> : it.text}            </li>}        </ul>    <div class={ui_cls(["ui-ticker", tone != "accent" && `ui-ticker-${tone}`, class])} role="marquee" aria-label={name ?? label}         style={`--t: ${speed}s`}>        <span.ui-ticker-label aria-hidden="true">{label}</span>        <div.ui-ticker-view>            <div.ui-ticker-track>                {row(false)}                {row(true)}            </div>        </div>    </div>}
Thread
69Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.