PriceTicker
A strip of symbols, prices and changes that scrolls by itself with CSS, stops on hover and stands still for reduced motion.
Demo
- ACME$182.40+1.25%
- GLBX$48.61-0.82%
- INIT$114.74+0.31%
- UMBR$57.72-2.40%
- STRK$123.52+3.06%
- WAYN$359.620.00%
- HOOL$88.10-0.12%
- SOYL$96.03+0.77%
- BTC61,250+2.14%
- ETH2,410-1.03%
- SOL152+5.60%
- ADA1—
It stops while the pointer is on it; with reduced motion it stands still and scrolls by hand.
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/price_ticker.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/price_ticker.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/price_ticker.qwe
let price_ticker_demo_quotes = [ { symbol: "ACME", price: 182.4, change: 1.25, href: "#price_ticker" }, { symbol: "GLBX", price: 48.61, change: -0.82 }, { symbol: "INIT", price: 114.74, change: 0.31 }, { symbol: "UMBR", price: 57.72, change: -2.4 }, { symbol: "STRK", price: 123.52, change: 3.06 }, { symbol: "WAYN", price: 359.62, change: 0 }, { symbol: "HOOL", price: 88.1, change: -0.12 }, { symbol: "SOYL", price: 96.03, change: 0.77 },]view PriceTickerDemo() = <div.demo-stack> <div id="price_ticker-demo-stocks"><PriceTicker items={price_ticker_demo_quotes} prefix="$" label="Stocks"/></div> <div id="price_ticker-demo-crypto"> <PriceTicker label="Crypto" speed={18} decimals={0} items={[ { symbol: "BTC", price: 61250, change: 2.14 }, { symbol: "ETH", price: 2410, change: -1.03 }, { symbol: "SOL", price: 152, change: 5.6 }, { symbol: "ADA", price: 1, change: null }]}/> </div> <p.demo-note>It stops while the pointer is on it; with reduced motion it stands still and scrolls by hand.</p> </div>Source
ui/price_ticker.qwe
// PriceTicker: a strip of symbols with their prices and changes that scrolls along by// itself (a CSS animation, no JavaScript), as on a trading floor or a news channel.// It stops while hovered or while a link in it has focus, and stands still, as a// strip one can scroll, for those who ask for less motion.//// <PriceTicker items={[{ symbol: "ACME", price: 182.4, change: 1.25 }, { symbol: "GLBX", price: 48.61, change: -0.8 }]}/>// <PriceTicker items={quotes} prefix="$" speed={60} label="Markets"/>//// items: a list of { symbol, price, change, href }: change is the day's change in// percent (+1.25 for 1.25% up); href makes the symbol a link. prefix and suffix:// around every price ("$"); decimals: digits after the point of prices (2). speed:// seconds for the whole strip to go by once (5 per item by default). label: what// screen readers call it ("Prices"); they get the list once, without the motion.import ui/theme.qwe use ui_clslet price_ticker_css = css`.ui-pt { position: relative; display: flex; min-width: 0; overflow: hidden; contain: inline-size; border-block: 1px solid var(--ui-border); background: var(--ui-surface); font: 13px/1 var(--ui-font); color: var(--ui-fg); mask-image: linear-gradient(90deg, transparent, #000 24px, #000 calc(100% - 24px), transparent); }.ui-pt-track { display: flex; flex: none; min-width: max-content; animation: ui-pt-scroll var(--d) linear infinite; }.ui-pt:hover .ui-pt-track, .ui-pt:focus-within .ui-pt-track { animation-play-state: paused; }.ui-pt-list { display: flex; flex: none; margin: 0; padding: 0; list-style: none; }.ui-pt-item { display: inline-flex; align-items: baseline; gap: 8px; padding: 10px 22px; white-space: nowrap; border-right: 1px solid color-mix(in srgb, var(--ui-border) 70%, transparent); }.ui-pt-sym { font-weight: 650; letter-spacing: .02em; color: var(--ui-fg); text-decoration: none; }a.ui-pt-sym:hover { color: var(--ui-accent); }a.ui-pt-sym:focus-visible { outline: none; border-radius: 3px; box-shadow: var(--ui-focus); }.ui-pt-price { font-variant-numeric: tabular-nums; }.ui-pt-chg { display: inline-flex; align-items: center; gap: 3px; font-weight: 600; font-variant-numeric: tabular-nums; }.ui-pt-up .ui-pt-chg { color: var(--ui-success); } .ui-pt-down .ui-pt-chg { color: var(--ui-danger); } .ui-pt-flat .ui-pt-chg { color: var(--ui-muted); }.ui-pt-chg svg { width: 8px; height: 8px; fill: currentColor; }@keyframes ui-pt-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }@media (prefers-reduced-motion: reduce) { .ui-pt { overflow-x: auto; mask-image: none; } .ui-pt-track { animation: none; } .ui-pt-track > [aria-hidden="true"] { display: none; }}`// n (a whole number, 0 or more) with at least `width` digits.fn price_ticker_pad(n: int, width: int) -> str { let s = str(n) len(s) < width ? repeat("0", width - len(s)) + s : s}// A whole number with commas: 1234567 is "1,234,567".fn price_ticker_group(n: int) -> str = n < 1000 ? str(n) : price_ticker_group(floor(n / 1000)) + "," + price_ticker_pad(n % 1000, 3)// A value as it reads, with commas and `places` digits after the point (up to two as// needed when that is null): 48210.5 is "48,210.50" with 2. Worked out in whole// numbers, so the server and the browser write the same text.fn price_ticker_num(v: num, places: int? = null) -> str { let digits = places == null ? 2 : (places < 0 ? 0 : (places > 6 ? 6 : places)) let scale = [1, 10, 100, 1000, 10000, 100000, 1000000][digits] let units = round(abs(v) * scale) let frac = units % scale let tail = digits == 0 || (places == null && frac == 0) ? "" : (places == null && frac % 10 == 0 ? `.${floor(frac / 10)}` : `.${price_ticker_pad(frac, digits)}`) `${v < 0 && units > 0 ? "-" : ""}${price_ticker_group(floor(units / scale))}${tail}`}// A large number, short: 1500 is "1.5k", 2000000 is "2M", 0.25 is "0.25".fn price_ticker_short(v: num) -> str { let a = abs(v) let unit = a >= 1000000000 ? 1000000000 : (a >= 1000000 ? 1000000 : (a >= 1000 ? 1000 : 1)) let tenths = round(a * 10 / unit) let mark = unit == 1000000000 ? "B" : (unit == 1000000 ? "M" : "k") unit == 1 ? price_ticker_num(v) : `${v < 0 ? "-" : ""}${price_ticker_group(floor(tenths / 10))}${tenths % 10 != 0 ? `.${tenths % 10}` : ""}${mark}`}// A change in percent, signed, to two decimals: "+1.25%", "-0.40%".fn price_ticker_signed(p: num) -> str = `${p > 0 ? "+" : (p < 0 && round(abs(p) * 100) > 0 ? "-" : "")}${price_ticker_num(abs(p), 2)}%`// A coordinate out of 100 (or a length), to two decimals.fn price_ticker_pct(n: num) -> str { let r = round(n * 100) / 100 r == floor(r) ? str(floor(r)) : str(r)}view PriceTicker(items: list, prefix: str = "", suffix: str = "", decimals: int = 2, speed: num? = null, label: str = "Prices", class: str? = null) { style global price_ticker_css let seconds = speed ?? (len(items) * 5 > 10 ? len(items) * 5 : 10) let side = (c) => c == null || c == 0 ? "ui-pt-flat" : (c > 0 ? "ui-pt-up" : "ui-pt-down") let arrow = (c) => c == null || c == 0 ? null : (c > 0 ? "M0 8 4 1 8 8Z" : "M0 0 4 7 8 0Z") // A short list is said again, so each half of the strip is wider than where it is shown. let rounds = len(items) == 0 ? 1 : ceil(10 / len(items)) let row = (copy: bool) => <ul.ui-pt-list aria-hidden={copy ? "true" : null}> {for r in range(rounds) => for it in items => <li class={`ui-pt-item ${side(it.change)}`} aria-hidden={!copy && r > 0 ? "true" : null}> {it.href != null ? <a.ui-pt-sym href={it.href} tabindex={copy || r > 0 ? "-1" : null}>{it.symbol}</a> : <span.ui-pt-sym>{it.symbol}</span>} <span.ui-pt-price>{prefix}{price_ticker_num(it.price, decimals)}{suffix}</span> <span.ui-pt-chg> {arrow(it.change) != null && <svg viewBox="0 0 8 8" aria-hidden="true"><path d={arrow(it.change)}/></svg>} {it.change == null ? "—" : price_ticker_signed(it.change)} </span> </li>} </ul> <div class={ui_cls(["ui-pt", class])} role="region" aria-label={label} style={`--d: ${price_ticker_pct(seconds)}s`}> <div.ui-pt-track>{row(false)}{row(true)}</div> </div>}Related
BollingerChartPrices with Bollinger bands of a period and a number of deviations, closes outside them marked, as a line or candles.7 views · 5 downloadsCandlestickChartOHLC candles, green up and red down, with a volume pane, a date axis, moving averages and the details of each candle on hover.5 views · 7 downloadsDepthChartAn order book's bids and asks as stepped areas of the size waiting at each price or better, around the mid price, with the spread.5 views · 7 downloadsHeikinAshiChartHeikin-Ashi candles worked out from OHLC, which smooth the noise so trends show as runs of one colour, with the real close on hover.5 views · 6 downloadsLineBreakChartA three-line break chart from closing prices: lines go on while the close makes new ends, and turn only past the last three lines.5 views · 6 downloadsLiveCandlesCandles built from ticks as they come: the open candle grows, a new one starts each interval, with the last price and the time left.6 views · 6 downloads
Thread
Thread 0 posts
On PriceTicker: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.