Skills/Editor Toolkit/Pan-Zoom Element

ef-pan-zoom

Attributes

x
number

Horizontal pan offset in pixels

Default:0
y
number

Vertical pan offset in pixels

Default:0
scale
number

Zoom scale factor (0.1 to 5)

Default:1
auto-fit
boolean

Auto-fit content on first render

Default:false

Methods

screenToCanvas(screenX: number, screenY: number): { x: number, y: number }

Convert screen coordinates to canvas coordinates

Returns: Object with x, y in canvas space
canvasToScreen(canvasX: number, canvasY: number): { x: number, y: number }

Convert canvas coordinates to screen coordinates

Returns: Object with x, y in screen space
reset(): void

Reset to default transform (x=0, y=0, scale=1)

Returns: void
fitToContent(padding?: number): void

Fit content to container with optional padding (0-1)

Returns: void

Interactive pan and zoom container with mouse/trackpad gesture support.

Basic Usage

Wrap any content to make it pannable and zoomable:

<ef-pan-zoom class="w-[720px] h-[480px] border border-gray-300 rounded">
<ef-timegroup mode="contain" class="w-[1280px] h-[720px] bg-black">
<ef-video src="https://assets.editframe.com/bars-n-tone.mp4" class="size-full object-contain"></ef-video>
</ef-timegroup>
</ef-pan-zoom>

Interaction: Click and drag to pan. Hold Cmd/Ctrl and scroll to zoom. Scroll to pan horizontally/vertically.

Auto-Fit Content

Automatically fit content to the container on load:

<ef-pan-zoom auto-fit class="w-[600px] h-[400px] border border-gray-300 rounded bg-gray-50">
<ef-timegroup mode="contain" class="w-[1280px] h-[720px] bg-black">
<ef-video src="https://assets.editframe.com/bars-n-tone.mp4" class="size-full object-contain"></ef-video>
</ef-timegroup>
</ef-pan-zoom>

The auto-fit attribute centers and scales content to fit with 5% padding.

Gesture Controls

Panning

Click and drag anywhere to pan the view:

<ef-pan-zoom class="w-[500px] h-[300px] border border-gray-300 rounded">
<div class="w-[1000px] h-[600px] bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
<p class="text-white text-2xl font-bold">Drag to pan</p>
</div>
</ef-pan-zoom>

Zooming

Hold Cmd (Mac) or Ctrl (Windows/Linux) and scroll to zoom:

<ef-pan-zoom class="w-[500px] h-[300px] border border-gray-300 rounded">
<div class="w-[800px] h-[600px] bg-gradient-to-br from-green-500 to-teal-600 flex items-center justify-center">
<p class="text-white text-2xl font-bold">Cmd/Ctrl + Scroll to zoom</p>
</div>
</ef-pan-zoom>

Zoom is centered on the cursor position — the point under the cursor remains fixed while zooming.

Trackpad Gestures

  • Two-finger drag: Pan horizontally and vertically
  • Pinch: Zoom in/out (with Cmd/Ctrl)
  • Scroll: Pan without modifier keys

Programmatic Control

Set transform properties directly:

<div>
<ef-pan-zoom id="demo-panzoom" x="100" y="50" scale="1.5" class="w-[500px] h-[300px] border border-gray-300 rounded">
<div class="w-[800px] h-[600px] bg-gradient-to-br from-red-500 to-pink-600 flex items-center justify-center">
<p class="text-white text-2xl font-bold">Programmatic Transform</p>
</div>
</ef-pan-zoom>
<div class="mt-4 flex gap-2">
<button onclick="document.getElementById('demo-panzoom').reset()" class="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600">Reset</button>
<button onclick="document.getElementById('demo-panzoom').fitToContent()" class="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600">Fit Content</button>
<button onclick="document.getElementById('demo-panzoom').scale = 2" class="px-3 py-1 bg-purple-500 text-white rounded hover:bg-purple-600">Zoom 2x</button>
</div>
</div>

Methods

reset()

Reset the view to default values:

const panZoom = document.querySelector('ef-pan-zoom');
panZoom.reset(); // x=0, y=0, scale=1

fitToContent(padding)

Fit content to the container:

const panZoom = document.querySelector('ef-pan-zoom');
panZoom.fitToContent(0.1); // 10% padding on each side

Padding is a factor from 0 to 1, where 0.1 = 10% padding.

screenToCanvas(screenX, screenY)

Convert screen coordinates (e.g., mouse event) to canvas coordinates:

panZoom.addEventListener('click', (e) => {
const canvasPos = panZoom.screenToCanvas(e.clientX, e.clientY);
console.log(`Clicked at canvas position: ${canvasPos.x}, ${canvasPos.y}`);
});

Useful for hit detection and positioning elements in canvas space.

canvasToScreen(canvasX, canvasY)

Convert canvas coordinates to screen coordinates:

const screenPos = panZoom.canvasToScreen(element.x, element.y);
tooltip.style.left = `${screenPos.x}px`;
tooltip.style.top = `${screenPos.y}px`;

Useful for positioning overlays and tooltips relative to canvas elements.

Events

transform-changed

Fired when pan or zoom changes:

panZoom.addEventListener('transform-changed', (e) => {
console.log('Transform:', e.detail); // { x, y, scale }
});

Use this to sync UI controls or persist view state.

Context Provision

ef-pan-zoom provides transform data via Lit context. Child components can consume this to render overlays that match the transform:

import { consume } from '@lit/context';
import { panZoomTransformContext } from '@editframe/elements';
@consume({ context: panZoomTransformContext })
panZoomTransform?: PanZoomTransform;

Overlay components (like selection handles) use this to align with the panned/zoomed content.

Video Preview with Pan-Zoom

Create a zoomable video preview:

<ef-pan-zoom auto-fit class="w-[720px] h-[480px] border border-gray-300 rounded bg-gray-900">
<ef-timegroup mode="contain" class="w-[1920px] h-[1080px] bg-black">
<ef-video src="https://assets.editframe.com/bars-n-tone.mp4" class="size-full object-contain"></ef-video>
<div class="absolute top-8 left-8 bg-white/90 backdrop-blur px-4 py-2 rounded">
<p class="text-sm font-semibold">Zoomable Preview</p>
<p class="text-xs text-gray-600">Cmd/Ctrl + Scroll to zoom</p>
</div>
</ef-timegroup>
</ef-pan-zoom>

Styling

The element has overflow: hidden and position: relative by default. Style the container as needed:

<ef-pan-zoom class="w-full h-screen border-2 border-blue-500 rounded-lg shadow-xl">
<!-- Content -->
</ef-pan-zoom>

Scale Limits

Scale is clamped between 0.1 and 5:

  • Minimum: 0.1 (10% of original size)
  • Maximum: 5 (500% of original size)

These limits prevent unusable zoom levels while allowing meaningful magnification.

Browser Navigation Prevention

ef-pan-zoom prevents browser back/forward navigation triggered by trackpad swipes. This ensures pan gestures don't accidentally navigate away from the page.

The element uses passive event listeners in capture phase to intercept and prevent default navigation behavior while still allowing normal pan/zoom interactions.