ImageHotspots
A picture with numbered points that show a note on hover or focus, with no script: a plan, a product, a photo.
Demo
- BedA 160 cm bed by the window.
- KitchenInduction hob, oven, dishwasher.
- Living roomA sofa that sleeps two, and the TV.
- BathroomWalk-in shower; towels provided.
- Balcony
Rest the pointer on a number, or Tab to it, to read the note.
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_hotspots.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/image_hotspots.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/image_hotspots.qwe
// A floor plan drawn in SVG, so the demo needs no image files.let image_hotspots_demo_plan = `data:image/svg+xml,${url_encode( `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 500" width="800" height="500">` + `<rect width="800" height="500" fill="#f4efe6"/>` + `<rect x="40" y="40" width="330" height="260" fill="#fff" stroke="#8a7f70" stroke-width="4"/>` + `<rect x="370" y="40" width="390" height="180" fill="#fff" stroke="#8a7f70" stroke-width="4"/>` + `<rect x="370" y="220" width="390" height="240" fill="#fff" stroke="#8a7f70" stroke-width="4"/>` + `<rect x="40" y="300" width="330" height="160" fill="#fff" stroke="#8a7f70" stroke-width="4"/>` + `<rect x="60" y="60" width="120" height="80" rx="6" fill="#d6e6f5"/><rect x="400" y="250" width="200" height="110" rx="8" fill="#e8dcc8"/></svg>`)}`let image_hotspots_demo_points = [ { x: 14, y: 20, title: "Bed", text: "A 160 cm bed by the window." }, { x: 60, y: 25, title: "Kitchen", text: "Induction hob, oven, dishwasher." }, { x: 62, y: 62, title: "Living room", text: "A sofa that sleeps two, and the TV." }, { x: 25, y: 76, title: "Bathroom", text: "Walk-in shower; towels provided." }, { x: 90, y: 90, title: "Balcony" },]view ImageHotspotsDemo() = <div.demo-stack> <ImageHotspots id="image-hotspots-demo" src={image_hotspots_demo_plan} alt="The flat's floor plan" points={image_hotspots_demo_points} label="Rooms"/> <p.demo-note>Rest the pointer on a number, or Tab to it, to read the note.</p> </div>Source
ui/image_hotspots.qwe
// ImageHotspots: a picture with numbered points on it that each show a note// when the pointer rests on them or they take focus: the parts of a product,// the rooms on a plan, who is in a photo. CSS shows the notes, so it needs no// script; each point is a button that screen readers read with its note.//// <ImageHotspots src="/img/bike.jpg" alt="The bike from the side" points={[// { x: 22, y: 60, title: "Front brake", text: "Hydraulic disc, 180 mm." },// { x: 71, y: 44, title: "Saddle", text: "Leather, sprung." },// ]}/>//// src, alt: the picture. points: a list of { x, y, title, text? }: x and y in// percent of the picture's width and height, from the top left; title is the// note's heading, text its body. A note opens below its point in the top part// of the picture and above it elsewhere, and leans left or right near the edges.// id: a prefix for the notes' ids ("ui-image-hotspots"); give each on a page its own.// label: what screen readers call the group of points ("Points of interest").import ui/theme.qwe use ui_clslet image_hotspots_css = css`.ui-image-hotspots { position: relative; display: inline-block; width: fit-content; max-width: 100%; overflow: visible; border-radius: var(--ui-radius); font: 13px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-image-hotspots img { display: block; max-width: 100%; height: auto; border-radius: var(--ui-radius); }.ui-image-hotspots-points { margin: 0; padding: 0; list-style: none; }.ui-image-hotspots-pin-at { position: absolute; width: 0; height: 0; }.ui-image-hotspots-pin { position: absolute; display: grid; place-items: center; width: 26px; height: 26px; padding: 0; transform: translate(-50%, -50%); border: 2px solid #fff; border-radius: 50%; background: var(--ui-accent); color: var(--ui-accent-fg); font: 700 12px/1 var(--ui-font); box-shadow: 0 0 0 4px color-mix(in srgb, var(--ui-accent) 35%, transparent), var(--ui-shadow); cursor: pointer; transition: transform .15s;}.ui-image-hotspots-pin:hover, .ui-image-hotspots-pin:focus { transform: translate(-50%, -50%) scale(1.15); }.ui-image-hotspots-pin:focus-visible { outline: none; box-shadow: 0 0 0 4px var(--ui-bg), 0 0 0 7px var(--ui-accent); }.ui-image-hotspots-pin-note { position: absolute; z-index: 2; bottom: 20px; left: 50%; width: max-content; max-width: 240px; padding: 8px 12px; transform: translateX(-50%); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); box-shadow: var(--ui-shadow-lg); text-align: left; visibility: hidden; opacity: 0; transition: opacity .15s;}.ui-image-hotspots-pin-note strong { display: block; font-weight: 600; }.ui-image-hotspots-pin-note span { color: var(--ui-muted); }.ui-image-hotspots-pin-at:hover .ui-image-hotspots-pin-note, .ui-image-hotspots-pin:focus + .ui-image-hotspots-pin-note { visibility: visible; opacity: 1; }.ui-image-hotspots-pin-below .ui-image-hotspots-pin-note { bottom: auto; top: 20px; }.ui-image-hotspots-pin-left .ui-image-hotspots-pin-note { left: auto; right: -14px; transform: none; }.ui-image-hotspots-pin-right .ui-image-hotspots-pin-note { left: -14px; transform: none; }@media (prefers-reduced-motion: reduce) { .ui-image-hotspots-pin, .ui-image-hotspots-pin-note { transition: none; } }`view ImageHotspots(src: str, alt: str = "", points: list = [], id: str = "ui-image-hotspots", label: str = "Points of interest", class: str? = null) { style global image_hotspots_css <div class={ui_cls(["ui-image-hotspots", class])}> <img src={src} alt={alt}/> <ol.ui-image-hotspots-points aria-label={label}> {for [i, p] in enumerate(points) => <li key={i} class={ui_cls(["ui-image-hotspots-pin-at", p.y < 30 && "ui-image-hotspots-pin-below", p.x > 75 && "ui-image-hotspots-pin-left", p.x < 25 && "ui-image-hotspots-pin-right"])} style={`left: ${p.x}%; top: ${p.y}%`}> <button.ui-image-hotspots-pin type="button" aria-label={`${i + 1}: ${p.title}`} aria-describedby={`${id}-${i}`}>{i + 1}</button> <div.ui-image-hotspots-pin-note id={`${id}-${i}`} role="tooltip"> <strong>{p.title}</strong> {get(p, "text") != null && <span>{get(p, "text")}</span>} </div> </li>} </ol> </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 ImageHotspots: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.