Phoenix LiveView function component that mounts a Fresco viewer.
Renders a <div> with phx-hook="FrescoViewer". The companion JS hook
in priv/static/fresco.js lazy-loads OpenSeadragon from jsDelivr,
initializes the viewer with sensible defaults (smooth animations,
viewport clamped, Heroicons nav overlay), and publishes a handle to
window.Fresco.viewerFor(id) so peer extensions can attach.
Usage
<Fresco.viewer
id="photo"
src={~p"/uploads/photo.jpg"}
class="w-full h-[80vh] rounded"
/>Source detection
The default behavior treats src as a plain image URL. Extensions can
register source providers via window.Fresco.registerSourceProvider/2
to handle other formats (e.g., a DZI manifest URL via Tessera).
Interactions
Wheel-zoom, pinch-zoom, click-drag pan, double-click zoom, Heroicons nav buttons (zoom in / zoom out / reset / fullscreen). All work out of the box; no parent configuration needed.
Parent app setup
Import the JS hook and spread FrescoHooks into your LiveSocket
hooks:
import "../../deps/fresco/priv/static/fresco.js"
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { ...window.FrescoHooks, ...colocatedHooks }
})
Summary
Functions
Renders a Fresco viewer for the given image source(s).
Functions
Renders a Fresco viewer for the given image source(s).
Companion JS hook lazy-loads OpenSeadragon, mounts the viewer, attaches the nav overlay, and publishes the handle for peer extensions.
Attributes
id(:string) (required) - DOM id; must be unique on the page.src(:string) - URL of a single image to display — shortcut for one-image viewers. Treated as a plain image (.jpg,.png,.webp, etc.) by default; source providers registered viawindow.Fresco.registerSourceProvider/2can intercept specific URL patterns (e.g., Tessera handles.dzimanifests).Exactly one of
:srcor:sourcesis required. If both are given,:sourceswins and:srcis ignored.Defaults to
nil.sources(:list) - List of images to lay out on a shared canvas. Each entry is a map:%{ src: "/uploads/a.jpg", # required — image URL x: 0.0, # optional — horizontal offset in viewport units (default 0) y: 0.0, # optional — vertical offset in viewport units (default 0) width: 1.0 # optional — width in viewport units (default 1) }Viewport units: the first image is conventionally placed at
x: 0, y: 0withwidth: 1. Sox: 1.1puts the next image just to the right with a 10% gap. Height is derived from the image's natural aspect ratio — you don't specify it.Each entry's
srcruns through the same source-provider chain as the single-image:src, so a multi-image viewer can mix plain images with DZI tile pyramids handled by Tessera.Typically paired with
:infinite_canvasso the user can pan freely across the layout. Without it, OSD will fit-to-viewport over the bounding box of all sources at mount.Note:
handle.imageToScreen/screenToImagecurrently operate on the first source only. Multi-image coordinate disambiguation is planned but not yet implemented.Defaults to
[].class(:string) - CSS classes for the viewer container. Defaults to"w-full h-96".infinite_canvas(:boolean) - Whentrue, drops OSD's "keep the image filling the viewport" clamps so the user can pan freely beyond the image edges and zoom out until the image is a thumbnail in the middle of an empty canvas. The viewer background picks up a subtle dot-grid pattern in the void so it reads as "canvas," not "broken layout." Defaultfalsepreserves the stock single-image viewer behavior — every existing call site keeps working unchanged.Layered overlays (e.g. Etcher) can draw annotations in the void around the image because their coordinate math already supports out-of-bounds image-pixel values.
Pairs naturally with
:sourcesto lay multiple images out on the same canvas, Figma/Miro style.Defaults to
false.rotate(:boolean) - Whentrue, appends a rotation button to the nav overlay that rotates the image 90° clockwise on each click. Rotation persists across "Reset view" — it's tracked independently of zoom/pan. Defaultfalsekeeps the four-button stock nav layout. Opt-in like:infinite_canvasso existing consumers aren't surprised by an extra button.Defaults to
false.theme(:atom) - Color scheme for the viewer host background, dot grid, and nav buttons.:system(default) — follow the OS / browserprefers-color-scheme.:light— force light palette regardless of OS preference.:dark— force dark palette regardless of OS preference.
Theming is implemented as CSS custom properties on
.fresco-viewer(--fresco-bg,--fresco-grid-dot,--fresco-nav-bg,--fresco-nav-bg-hover,--fresco-nav-fg,--fresco-nav-focus). Override them in your own CSS to integrate with a parent theme system (daisyUI, custom palettes, etc.) — see the Theming section of the README.Defaults to
:system. Must be one of:system,:light, or:dark.Global attributes are accepted.