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.:inherit— emit only the host structure; the parent app's CSS supplies the six--fresco-*custom properties. Use this to wire Fresco to a parent theme system (daisyUI, custom palettes, …) so its background, grid, and nav follow the parent theme. The variables flip automatically as the parent theme changes.
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). With:system/:light/:dark, Fresco supplies the values. With:inherit, the parent app does — see the Theming section of the README for the daisyUI mapping example.Defaults to
:system. Must be one of:system,:light,:dark, or:inherit.Global attributes are accepted.