ZoomImage
A picture that zooms in where the pointer is; a click holds it, and the keyboard zooms and moves around too.
Demo
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/zoom_image.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/zoom_image.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/zoom_image.qwe
// A woven pattern drawn in SVG, fine enough to be worth zooming, so the demo needs no image files.fn zoom_image_demo_weave(a: str, b: str) -> str { let cells = join(for i in range(0, 20) => join(for j in range(0, 15) => `<rect x="${i * 40}" y="${j * 40}" width="40" height="40" fill="${(i + j) % 2 == 0 ? a : b}"/><circle cx="${i * 40 + 20}" cy="${j * 40 + 20}" r="4" fill="#fff" opacity=".6"/>`, ""), "") let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" width="800" height="600">${cells}` + `<text x="400" y="320" font-family="Georgia, serif" font-size="14" text-anchor="middle" fill="#fff">Made in Porto · 100% linen</text></svg>` `data:image/svg+xml,${url_encode(svg)}`}view ZoomImageDemo() = <div.demo-row style="align-items: flex-start"> <div style="width: 360px; max-width: 100%"> <ZoomImage src={zoom_image_demo_weave("#5b4bdb", "#3f8f8a")} alt="Linen fabric with a small label"/> </div> <div style="width: 240px; max-width: 100%"> <ZoomImage src={zoom_image_demo_weave("#c62a2f", "#a35200")} alt="Red fabric, square" zoom={4} ratio="1" large="https://picsum.photos/seed/weave/1200/1200" hint="Zoom ×4"/> </div> </div>Source
ui/zoom_image.qwe
// ZoomImage: a picture that zooms in where the pointer is while it rests on it,// to see the weave of a fabric or the print on a label. A click (or a tap) holds// the zoom on and off; with the keyboard, Enter zooms, the arrow keys move// around and Escape zooms out.//// <ZoomImage src="/img/sofa.jpg" alt="The sofa's fabric"/>// <ZoomImage src={product.image} alt={product.name} zoom={3} ratio="1"/>//// src, alt: the picture. zoom: how much it grows (2.5). ratio: the frame's shape// ("4/3"); the picture fills it. large: a bigger picture to show while zoomed// (src by default), loaded only when it is first wanted. hint: the line shown// in a corner ("Hover to zoom"); null hides it.import ui/theme.qwe use ui_clslet zoom_image_css = css`.ui-zoom-image { position: relative; display: block; overflow: hidden; width: 100%; aspect-ratio: var(--r, 4/3); border-radius: var(--ui-radius); background: var(--ui-subtle); cursor: zoom-in; outline: none; font: 12px/1 var(--ui-font);}.ui-zoom-image:focus-visible { box-shadow: var(--ui-focus); }.ui-zoom-image img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transform-origin: var(--x, 50%) var(--y, 50%); transition: transform .2s ease-out; pointer-events: none; -webkit-user-drag: none;}.ui-zoom-image-on { cursor: zoom-out; }.ui-zoom-image-on img { transform: scale(var(--z, 2.5)); transition: transform .2s ease-out, transform-origin .05s linear; }.ui-zoom-image-hint { position: absolute; right: 10px; bottom: 10px; display: inline-flex; align-items: center; gap: 6px; padding: 6px 9px; border-radius: 999px; background: rgba(0, 0, 0, .55); color: #fff; pointer-events: none; transition: opacity .2s;}.ui-zoom-image-hint svg { width: 14px; height: 14px; }.ui-zoom-image-on .ui-zoom-image-hint { opacity: 0; }@media (prefers-reduced-motion: reduce) { .ui-zoom-image img, .ui-zoom-image-on img, .ui-zoom-image-hint { transition: none; } }`component ZoomImage(src: str, alt: str = "", zoom: num = 2.5, ratio: str = "4/3", large: str? = null, hint: str? = "Hover to zoom", class: str? = null) { style global zoom_image_css state on = false state held = false state x = 50 state y = 50 state wanted = false let shown = wanted && large != null ? large : src let clamp = (v: num) => v < 0 ? 0 : v > 100 ? 100 : v let follow = (e) => { let r = e.target.getBoundingClientRect() x = clamp(round((e.clientX - r.left) * 100 / r.width, 2)) y = clamp(round((e.clientY - r.top) * 100 / r.height, 2)) } <div class={ui_cls(["ui-zoom-image", on && "ui-zoom-image-on", class])} style={`--r: ${ratio}; --z: ${zoom}; --x: ${x}%; --y: ${y}%`} role="img" aria-label={alt} tabindex="0" aria-description={on ? `Zoomed ${zoom} times. Arrow keys move, Escape zooms out.` : "Press Enter to zoom."} @pointerenter={(e) => { if e.pointerType == "mouse" { wanted = true; on = true; follow(e) } }} @pointermove={(e) => { if on { follow(e) } }} @pointerleave={(e) => { if e.pointerType == "mouse" && !held { on = false } }} @click={(e) => { wanted = true held = !held || !on on = held if on { follow(e) } }} @keydown={(e) => { let step = 10 let done = match e.key { "Enter" | " " => { wanted = true; on = !on; held = on; x = 50; y = 50; true } "Escape" => { if on { on = false; held = false; true } else { false } } "ArrowLeft" => { if on { x = clamp(x - step) }; on } "ArrowRight" => { if on { x = clamp(x + step) }; on } "ArrowUp" => { if on { y = clamp(y - step) }; on } "ArrowDown" => { if on { y = clamp(y + step) }; on } _ => false } if done { e.preventDefault() } }}> <img src={shown} alt="" draggable="false"/> {hint != null && <span.ui-zoom-image-hint aria-hidden="true"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM21 21l-4.35-4.35M11 8v6M8 11h6"/></svg> {hint} </span>} </div>}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.8 views · 9 downloadsImageAn image that keeps its shape while it loads, shimmering, and shows a fallback with its alt when it cannot.6 views · 9 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 downloads
Thread
Thread 0 posts
On ZoomImage: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.