A Svelte library for panning and zooming in SVG, Canvas and HTML.
pnpm add panera
Peer requirement: svelte ^4 || ^5
<script>
import { Panera, Svg } from 'panera';
let width = 800,
height = 300;
let pan;
function reset() {
pan.reset();
}
function to() {
pan.to({ x: 80, y: 40, width: 200, height: 120 }, { duration: 600 });
}
</script>
<button on:click={reset}>Reset</button> <button on:click={to}>Zoom to rect</button>
<Panera bind:this={pan} {width} {height}>
<Svg>
<rect fill="tomato" x="80" y="40" width="200" height="120" />
</Svg>
</Panera>
<Panera />
<Svg />
, <Canvas />
, <Html />
bind:this
: to()
, reset()
, interpolate()
, getView()
, getBox()
<Panera />
propswidth?: number | null
— container width in pixels (required for fits)height?: number | null
- container height in pixels (required for fits)duration?: number
— default 1000 ms (used by to()
& reset()
)easing?: (t:number)=>number
— default cubicInOut
bound?: boolean
— clamp view inside container (default true
)debug?: boolean
— overlay a non-scaling red debug rect (default false
)bind:this
)reset(opts?)
opts = { duration?: number, easing?: (t)=>number, debug?: boolean }
Reset the view back to k=1, x=0, y=0.
to(rect, opts?)
rect = {x, y, width, height}
opts = { duration?, easing?, debug?, bound? }
Tweens to the fitted view of the rectangle.
interpolate(a, b, opts?)
a = { x, y, width, height }, b = { x, y, width, height }
opts = { t?: number, easing?: (t)=>number, bound?: boolean, debug?: boolean }
Instant scrub (no tween). Fits both a and b, then blends view using t (after easing).
getView(): Readable<{ k:number, x:number, y:number }>
A Svelte store reflecting the current view.
getBox(): Readable<{ x:number, y:number, width:number, height: number }>
A Svelte store reflecting the current bounding box.
<Svg>
Renders a <svg>
with viewBox
and a <g>
that applies:
transform="scale(k) translate(x, y)"
.
Debug rect uses vector-effect="non-scaling-stroke"
.
<Canvas>
DPR-aware canvas; applies scale(k)
then translate(x, y)
.
Prop:
render(ctx, {k, x, y}, {width, height, dpr}) => void
<Html>
Absolutely-positioned HTML that follows the same transform via CSS:
transform: scale(k) translate(xpx, ypx); transform-origin: 0 0;
Place children with position:absolute; left/top
in object coordinates.
window
/devicePixelRatio
.scale(k)
then translate(x, y)
— so coordinates match.bound: true
, content is clamped so edges don’t show outside the container.pnpm format # prettier
pnpm lint # prettier check + eslint
pnpm test # vitest unit tests
pnpm prepack # build library to dist/ (svelte-package + publint)
pnpm pack # output a tarball
Core math is tested in src/lib/core.spec.js
:
fitRectToContainer
(aspect fit + bounds)lerp
MIT