ImageCompare
Before and after pictures one over the other, with a divider to drag (or move with the keys) between them.
Demo
The divider is at 50%.
Drag anywhere on a picture, or Tab to it and use the arrow keys.
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_compare.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/image_compare.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/image_compare.qwe
// The same scene drawn twice in SVG, by day and by night, so the demo needs no image files.fn image_compare_demo_scene(sky: str, sun: str, hill: str, lit: bool) -> str { let windows = lit ? `<rect x="540" y="330" width="24" height="24" fill="#ffd36b"/><rect x="590" y="330" width="24" height="24" fill="#ffd36b"/>` : "" let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 675" width="1200" height="675">` + `<rect width="1200" height="675" fill="${sky}"/><circle cx="900" cy="170" r="70" fill="${sun}"/>` + `<path d="M0 470 Q 300 330 600 450 T 1200 420 V 675 H 0 Z" fill="${hill}"/>` + `<rect x="520" y="300" width="120" height="110" fill="#8a5a3c"/><path d="M505 305 L580 240 L655 305 Z" fill="#5b3a2a"/>${windows}</svg>` `data:image/svg+xml,${url_encode(svg)}`}component ImageCompareDemoLive() { state split = 50 <div.demo-stack> <ImageCompare before={image_compare_demo_scene("#8fc8f2", "#ffd36b", "#4f8f4a", false)} after={image_compare_demo_scene("#16203f", "#f5f0d0", "#1f3326", true)} alt="A cottage on a hill" before_label="Day" after_label="Night" on_change={(p) => { split = p }}/> <p.demo-note id="image-compare-demo-note">The divider is at {round(split)}%.</p> </div>}view ImageCompareDemo() = <div.demo-stack> <ImageCompareDemoLive/> <div style="max-width: 360px"> <ImageCompare before={image_compare_demo_scene("#f3a46b", "#ff6b4a", "#7a5a3a", false)} after={image_compare_demo_scene("#f3a46b", "#ff6b4a", "#3f7a3a", false)} alt="The hill, before and after the rain" at={25} ratio="4/3" before_label={null} after_label={null}/> </div> <p.demo-note>Drag anywhere on a picture, or Tab to it and use the arrow keys.</p> </div>Source
ui/image_compare.qwe
// ImageCompare: two pictures of the same thing, before and after, one over the// other, with a divider to drag across to show more of one or the other. The// divider is a real range input stretched over the pictures, so a click or a// drag anywhere moves it, and the arrow keys, Home and End do too.//// <ImageCompare before="/img/old-kitchen.jpg" after="/img/new-kitchen.jpg" alt="The kitchen"/>// <ImageCompare before={raw_photo} after={edited} before_label="Original" after_label="Edited"// at={30} ratio="4/3" on_change={(pct) => { split = pct }}/>//// before, after: the two pictures (the same size and framing); before shows at// the left of the divider, after at the right. alt: what they show; each// picture's alt is it with its label. before_label, after_label: the tags in// the corners ("Before", "After"); null hides them. at: where the divider// starts, in percent from the left (50). ratio: the pictures' shape ("16/9").// label: what screen readers call the divider ("Divider between before and after").// on_change(pct): inside one of your components, called with the divider's place.import ui/theme.qwe use ui_clslet image_compare_css = css`.ui-image-compare { position: relative; overflow: hidden; width: 100%; aspect-ratio: var(--r, 16/9); border-radius: var(--ui-radius); background: var(--ui-subtle); font: 600 12px/1 var(--ui-font); user-select: none; touch-action: pan-y;}.ui-image-compare img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; pointer-events: none; -webkit-user-drag: none; }.ui-image-compare-before { clip-path: inset(0 calc(100% - var(--at, 50%)) 0 0); }.ui-image-compare-line { position: absolute; top: 0; bottom: 0; left: var(--at, 50%); width: 2px; margin-left: -1px; background: #fff; box-shadow: 0 0 4px rgba(0, 0, 0, .4); pointer-events: none; }.ui-image-compare-knob { position: absolute; top: 50%; left: 50%; display: grid; place-items: center; width: 40px; height: 40px; transform: translate(-50%, -50%); border: 2px solid #fff; border-radius: 50%; background: rgba(0, 0, 0, .45); color: #fff; box-shadow: var(--ui-shadow);}.ui-image-compare-knob svg { width: 22px; height: 22px; }.ui-image-compare-tag { position: absolute; top: 12px; padding: 5px 9px; border-radius: 999px; background: rgba(0, 0, 0, .55); color: #fff; letter-spacing: .04em; text-transform: uppercase; pointer-events: none; }.ui-image-compare-tag-before { left: 12px; }.ui-image-compare-tag-after { right: 12px; }.ui-image-compare input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: ew-resize; appearance: none; }.ui-image-compare input::-webkit-slider-thumb { appearance: none; width: 44px; height: 100vh; }.ui-image-compare:has(input:focus-visible) .ui-image-compare-knob { box-shadow: 0 0 0 3px #fff, 0 0 0 6px var(--ui-accent); }`component ImageCompare(before: str, after: str, alt: str = "", before_label: str? = "Before", after_label: str? = "After", at: num = 50, ratio: str = "16/9", label: str = "Divider between before and after", on_change = null, class: str? = null) { style global image_compare_css state pos = at < 0 ? 0 : at > 100 ? 100 : at let name = (tag: str?, fallback: str) => alt != "" ? `${alt} (${tag ?? fallback})` : "" <div class={ui_cls(["ui-image-compare", class])} style={`--r: ${ratio}; --at: ${pos}%`}> <img.ui-image-compare-after src={after} alt={name(after_label, "after")} draggable="false"/> <img.ui-image-compare-before src={before} alt={name(before_label, "before")} draggable="false"/> {before_label != null && <span.ui-image-compare-tag.ui-image-compare-tag-before aria-hidden="true">{before_label}</span>} {after_label != null && <span.ui-image-compare-tag.ui-image-compare-tag-after aria-hidden="true">{after_label}</span>} <div.ui-image-compare-line aria-hidden="true"> <span.ui-image-compare-knob> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 6-6 6 6 6M15 6l6 6-6 6"/></svg> </span> </div> <input type="range" min="0" max="100" step="0.5" value={pos} aria-label={label} aria-valuetext={`${round(pos)}% ${before_label ?? "before"}, ${round(100 - pos)}% ${after_label ?? "after"}`} @input={(e) => { pos = float(e.value) on_change && on_change(pos) }}/> </div>}Related
ImageCropA 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 downloadsCameraCaptureTakes 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 · 8 downloadsImageAn image that keeps its shape while it loads, shimmering, and shows a fallback with its alt when it cannot.6 views · 8 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 ImageCompare: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.