svelte-clipper
v0.1.2
Published
Responsive, reactive SVG clip paths for Svelte 5 — project clip shapes across elements with independent coordinate spaces.
Maintainers
Readme
svelte-clipper
Responsive, reactive SVG clip paths for Svelte 5. Define a clip shape anywhere in the DOM, apply it to anything anywhere else — across independent coordinate spaces, transforms, scroll containers, and CSS animations. Compose multiple shapes together with real boolean operations (union, difference, intersection, …).
<script lang="ts">
import { Clipper, ClippedBy } from 'svelte-clipper';
</script>
<Clipper id="hole">
<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" /></svg>
</Clipper>
<ClippedBy clipper="hole">
<img src="/cat.jpg" />
</ClippedBy>Installation
npm i svelte-clipperSvelte 5 is a peer dependency.
Why
clip-path: url(#id) works, but the clip shape has to live in a coordinate system the target understands. The moment the shape and target have different transforms, sit in different scroll containers, scale differently, or animate independently, the math gets painful fast. svelte-clipper watches both sides and continuously re-projects the shape into the target's local space, so you can put the clipper anywhere and the target anywhere and they just line up.
Core components
<Clipper>
Wraps the element(s) that define the clip shape. The shape can be any SVG graphics element (path, circle, ellipse, rect, polygon, polyline) or a container (svg, g) with any number of nested shapes.
<script lang="ts">
import { Clipper } from 'svelte-clipper';
</script>
<Clipper id="my-shape" preview live>
<svg viewBox="0 0 200 200">
<polygon points="100,10 40,198 190,78 10,78 160,198" />
</svg>
</Clipper>Clipper props
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| id | string | — | Identifier that ClippedBy uses to find this shape. |
| preview | boolean | false | Renders the shape with a dashed outline so you can see what's being used as the clipper. Otherwise the shape itself is invisible. |
| live | boolean | false | Polls the shape's screen transform every animation frame. Turn on when the shape (or an ancestor) moves via CSS animation / transitions. |
| children | Snippet | — | The SVG content. |
<ClippedBy>
Wraps the content that should be clipped. Accepts any HTML or SVG — images, divs, entire components.
<script lang="ts">
import { ClippedBy } from 'svelte-clipper';
</script>
<ClippedBy clipper="my-shape" mode="clip">
<div class="content">anything in here</div>
</ClippedBy>ClippedBy props
| Prop | Type | Default | Description |
| ---------- | ---------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| clipper | string \| string[] | — | One clipper id, or an array to combine several into a single clip. |
| mode | 'clip' \| 'subtract' | 'clip' | 'clip' keeps only what's inside the shape. 'subtract' keeps everything except the shape (punches a hole). |
| combine | CombineOp | 'concat' | How to fuse multiple clippers. 'concat' just appends them (with even-odd fill). The others run a real boolean op via path-bool: 'union', 'difference', 'intersection', 'exclusion', 'division', 'fracture'. |
| class | string | — | Forwarded to the wrapper <div>. Use this to size the clipping region. |
| children | Snippet | — | The content to clip. |
Examples
Multiple shapes as one clip
<script lang="ts">
import { Clipper, ClippedBy } from 'svelte-clipper';
</script>
<Clipper id="circle-a"><svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" /></svg></Clipper>
<Clipper id="circle-b"><svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" /></svg></Clipper>
<ClippedBy clipper={['circle-a', 'circle-b']} combine="union" mode="subtract">
<img src="/photo.jpg" />
</ClippedBy>Two independent SVGs — possibly in very different parts of the page — combined into one hole.
CSS-animated clipper
<script lang="ts">
import { Clipper } from 'svelte-clipper';
</script>
<div class="spinner">
<Clipper id="star" live>
<svg viewBox="0 0 200 200"><polygon points="..." /></svg>
</Clipper>
</div>
<style>
.spinner {
animation: spin 6s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>live polls each frame so the clip tracks the CSS transform. Without it, mutations to the shape itself are still observed, but ancestor CSS animations are not.
Complex, multi-path SVGs
The <Clipper> can wrap an entire <svg> containing nested <g> elements and many paths — all are combined into the clip shape, including multi-subpath paths with holes (via even-odd fill rule).
Boolean operations
When passing an array of clippers, combine controls how the paths are fused before the final clip is applied. This is powered by path-bool.
| Op | Meaning |
| -------------- | ----------------------------- |
| concat | Append paths (default, fast). |
| union | A ∪ B (merge overlaps). |
| difference | A − B. |
| intersection | A ∩ B. |
| exclusion | A XOR B. |
| division | Slice A using B's boundaries. |
| fracture | Emit every partition. |
Operations are folded left-to-right for more than two clippers.
How it works
Under the hood, each ClippedBy renders a hidden <svg><clipPath> and applies it via clip-path: url(#...). On every layout change (resize, scroll, mutation) or animation frame (in live mode), the library:
- Reads the clipper's current screen-space CTM.
- Reads the target's bounding rect.
- Composes them into a source → target matrix.
- Projects the shape's geometry through that matrix into a
dstring. - Optionally runs a boolean operation against other clippers.
- Writes the result to the
<clipPath>'s<path>.
Per-frame updates short-circuit when the composed matrix hasn't changed (e.g. during pure scroll where source and target move together), so the cost is effectively zero when nothing has actually shifted.
Advanced exports
The registry and projection helpers are exported for custom integrations:
import {
registerClipper,
unregisterClipper,
getClipperEl,
subscribeClipper,
notifyClipper,
shapeToPath,
SUPPORTED_SHAPE_TAGS,
observeLayout
} from 'svelte-clipper';Acknowledgments
Boolean path operations are powered by path-bool by @r-flash (MIT).
License
MIT
