Fresco.Viewer (fresco v0.6.1)

Copy Markdown View Source

Phoenix LiveView function component that mounts a Fresco viewer.

Renders a host <div> containing a stage <div> and an <img>. The companion JS hook (FrescoViewer in priv/static/fresco.js) attaches Pointer Events for unified mouse/touch/pen gestures, applies transform: translate3d(tx, ty, 0) scale(s) on the stage element, and publishes a handle to window.Fresco.viewerFor(id) so peer extensions (Tessera, future Etcher) can attach.

The image is server-rendered inside the host so it appears immediately — the hook reads naturalWidth/Height on mount and fits the image into the viewport without any "blank box" flash.

Usage

<Fresco.viewer
  id="photo"
  src={~p"/uploads/photo.jpg"}
  class="w-full h-[80vh] rounded"
/>

Interactions

  • Pan: click/touch drag, arrow keys (after focusing the viewer)
  • Zoom: mouse wheel (centered on cursor), pinch (two-finger on touch or trackpad), double-click (2× centered on cursor), +/- keys
  • Reset: nav button, 0 key — fits the image to the viewport
  • Fullscreen: nav button, f key — toggles native browser fullscreen

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.

Functions

viewer(assigns)

Renders a Fresco viewer for the given image source.

Companion JS hook attaches gesture handlers, fits the image to the viewport, and publishes the handle for peer extensions.

Attributes

  • id (:string) (required) - DOM id; must be unique on the page.

  • src (:string) (required) - URL of the image to display.

    The default behavior treats src as a plain image URL. Extensions can register source providers via window.Fresco.registerSourceProvider/2 to intercept specific URL patterns; the bundled engine handles {type: "image"} sources and throws a clear error for anything else (Tessera-style tile sources are planned for a later release).

  • class (:string) - CSS classes for the viewer container. Defaults to "w-full h-96".

  • infinite_canvas (:boolean) - When true, drops the default "image must cover viewport" clamp 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's background dot-grid (always present) becomes visible in the void around the image so it reads as "canvas," not "broken layout." Default false keeps the stock single-image viewer behavior — pan stays location-locked inside the image, zoom-out floor is fit-to-viewport.

    Pairs naturally with future layered overlays (e.g. Etcher annotations) that need to draw shapes, callouts, or labels in the white space around the image, Figma/Miro/Excalidraw style.

    Defaults to false.

  • theme (:atom) - Color scheme for the viewer host background, dot grid, and nav buttons.

    • :system (default) — follow the OS / browser prefers-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, …).

    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).

    Defaults to :system. Must be one of :system, :light, :dark, or :inherit.

  • zoom_floor (:float) - Optional minimum zoom scale, in engine units (screen-px-per-image-px). When set, the engine clamps every zoom path — wheel, pinch, double-click, fitBounds — at this floor. nil (default) falls through to the engine's normal floor (sFit in clamped mode, sFit * 0.05 in :infinite_canvas mode).

    Consumers can also set this at runtime via handle.setZoomFloor(scale) — useful when the floor depends on state that's known only after mount (e.g. a paged reader recomputing the floor each time the user navigates to a new page).

    Defaults to nil.

  • zoom_ceiling (:float) - Optional maximum zoom scale. Symmetric to :zoom_floor. nil (default) uses the engine's default ceiling (8× natural pixel ratio capped by the 8192-px raster safety limit).

    Defaults to nil.

  • pan_locked (:boolean) - When true, single-pointer pan gestures (mouse drag, touch drag, arrow keys) are suppressed. Two-pointer pinch still works for zoom. Useful for paged readers that want the page statically centered at fit and only allow pan once the user has zoomed in.

    Toggle at runtime via handle.setPanLocked(true|false) — typically in response to a handle.on("zoom", …) subscription that flips the lock as the user crosses the fit threshold.

    Defaults to false.

  • gestures (:list) - Allowlist of enabled gestures. Atom list: [:pan, :pinch, :wheel, :double_click, :keyboard]. Default nil enables all. Omitted entries are disabled.

    Defaults to nil.

  • nav_buttons (:list) - Allowlist of enabled built-in nav buttons. Atom list: [:home, :zoom_in, :zoom_out, :rotate, :fullscreen].

    • nil (default) — every button enabled.
    • [] — every button hidden. Useful for consumers building their own chrome; wire your buttons to handle.zoomIn() / handle.zoomOut() / handle.rotateBy(90) / handle.toggleFullscreen() / handle.requestHome() to get identical behavior to the built-ins.
    • A subset list — only those buttons render.

    Defaults to nil.

  • initial_rotation (:integer) - Initial rotation in degrees, snapped to one of {0, 90, 180, 270} at mount time. Pre-0.5.7 behavior (no rotation) corresponds to 0. Consumers persisting a per-image rotation choice server- side pass it here so the first paint already shows the rotated content — no flash of unrotated → rotated.

    Runtime control via handle.setRotation(deg) / handle.rotateBy(delta); the built-in :rotate nav button cycles +90° per click.

    Defaults to 0.

  • Global attributes are accepted.