Image
An image that keeps its shape while it loads, shimmering, and shows a fallback with its alt when it cannot.
Demo
0 loaded, 0 failed.
Each keeps its shape while it loads; the broken one says so and keeps its alt for screen readers.
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/image.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/image.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/image.qwe
component ImageDemoLive() { state loaded = 0 state broken = 0 <div.demo-stack> <div.demo-row style="align-items: flex-start"> <Image src="https://picsum.photos/seed/harbour/640/360" alt="Boats in a harbour" width="300px" color="#cfd9e6" on_load={() => { loaded += 1 }}/> <Image src="data:image/png;base64,bm90IGEgcGljdHVyZQ==" alt="A missing photo" width="300px" on_error={() => { broken += 1 }}/> </div> <p.demo-note id="image-demo-note">{loaded} loaded, {broken} failed.</p> </div>}view ImageDemo() = <div.demo-stack> <ImageDemoLive/> <div.demo-row id="image-demo-shapes" style="align-items: flex-start"> <Image src="https://picsum.photos/seed/forest/400/400" alt="A forest" ratio="1" width="160px"/> <Image src="https://picsum.photos/seed/lake/400/300" alt="A lake" ratio="4/3" width="160px" radius={false}/> <Image src="https://picsum.photos/seed/dunes/400/300" alt="Dunes" ratio="1" fit="contain" width="160px" color="#e8e2d6"/> <Image src="https://picsum.photos/seed/city/300/200" alt="A city" ratio={null} width="160px"/> </div> <p.demo-note>Each keeps its shape while it loads; the broken one says so and keeps its alt for screen readers.</p> </div>Source
ui/image.qwe
// Image: a picture that keeps its place while it loads (a soft block in its// shape, so the page does not jump), and shows a fallback instead of a broken// icon when it cannot be loaded.//// <Image src="/photos/hall.jpg" alt="The main hall" ratio="16/9"/>// <Image src={product.image} alt={product.name} ratio="1" fit="contain" color="#f3e8d8"/>// <Image src={url} alt="" width="240px" fallback="No picture yet" on_error={() => { broken += 1 }}/>//// src, alt: the image. ratio: its shape, "16/9", "4/3", "3/2", "1" (default "16/9");// null lets the image set its own height. fit: "cover" (default) or "contain".// width: as CSS ("240px", "50%"); it fills its container by default.// color: the placeholder's colour while it loads (a dominant colour from your// data; the theme's subtle grey by default); it shimmers while loading.// radius: rounded corners (true). lazy: loaded when scrolled near (true).// fallback: what it says when the image fails ("Image unavailable"); the alt text// is kept for screen readers.// on_load(), on_error(): inside one of your components, called when it loads or// fails. data-state on the wrapper says "loading", "loaded" or "error".import ui/theme.qwe use ui_clslet image_css = css`.ui-image { position: relative; display: block; overflow: hidden; width: 100%; background: var(--ui-image-color, var(--ui-subtle)); border-radius: var(--ui-radius); color: var(--ui-muted); font: 13px/1.4 var(--ui-font);}.ui-image-ratio { aspect-ratio: var(--r); }.ui-image-sharp { border-radius: 0; }.ui-image img { display: block; width: 100%; height: auto; object-fit: cover; opacity: 1; transition: opacity .3s; }.ui-image-ratio img { height: 100%; }.ui-image-contain img { object-fit: contain; }.ui-image[data-state="loading"] img { opacity: 0; }.ui-image[data-state="loading"]::after { content: ""; position: absolute; inset: 0; background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, .35) 50%, transparent 70%); background-size: 250% 100%; animation: ui-image-shimmer 1.4s linear infinite;}@keyframes ui-image-shimmer { from { background-position: 150% 0; } to { background-position: -100% 0; } }.ui-image[data-state="error"] img { display: none; }.ui-image-fallback { position: absolute; inset: 0; display: grid; place-items: center; align-content: center; gap: 6px; padding: 12px; text-align: center; }.ui-image-fallback svg { width: 28px; height: 28px; opacity: .6; }.ui-image:not(.ui-image-ratio)[data-state="error"] { min-height: 120px; }@media (prefers-reduced-motion: reduce) { .ui-image[data-state="loading"]::after { animation: none; } .ui-image img { transition: none; } }`component Image(src: str, alt: str = "", ratio: str? = "16/9", fit: str = "cover", width: str? = null, color: str? = null, radius: bool = true, lazy: bool = true, fallback: str = "Image unavailable", on_load = null, on_error = null, class: str? = null) { style global image_css state status = "loading" let ok = () => { status = "loaded" on_load && on_load() } let bad = () => { status = "error" on_error && on_error() } <span class={ui_cls(["ui-image", ratio != null && "ui-image-ratio", fit == "contain" && "ui-image-contain", !radius && "ui-image-sharp", class])} data-state={status} style={[ratio != null && `--r: ${ratio}`, width != null && `width: ${width}`, color != null && `--ui-image-color: ${color}`] | filter((s) => s != false && s != null) | join("; ")}> <img src={src} alt={alt} loading={lazy ? "lazy" : null} @mount={(el) => { // Loaded (or failed) before the component woke: the events are gone, so ask the element. if el.complete { if el.naturalWidth > 0 { ok() } else { bad() } } }} @load={ok} @error={bad}/> {status == "error" && <span.ui-image-fallback role="img" aria-label={alt != "" ? alt : fallback}> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2ZM8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM21 15l-5-5L5 21"/></svg> <span aria-hidden="true">{fallback}</span> </span>} </span>}Related
CameraCaptureTakes a photo with the camera into a hidden input as a data URL, with retake, and says so when there is no camera.6 views · 6 downloadsCoverImageA wide picture with a title, subtitle and actions over a gradient, in three heights.7 views · 7 downloadsFigureAn image (or anything) with a caption and a credit under it, as a figure with its figcaption.7 views · 8 downloadsImageCompareBefore and after pictures one over the other, with a divider to drag (or move with the keys) between them.6 views · 9 downloadsImageCropA crop box over a picture, dragged and resized by its corners or the keys, with the crop in hidden inputs a form sends.9 views · 6 downloadsImageGalleryA grid of thumbnails (the last can say "+N") opening a framed viewer with captions, next and previous, the keys and a strip.6 views · 7 downloads
Thread
Thread 0 posts
On Image: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.