Canvas mask
The canvas has a white overlay. When you have a shape selected, the shape's bounds will be used as a mask through the overlay.
import { useEffect, useRef } from 'react'
import { TLComponents, TLShape, Tldraw, createShapeId, useEditor, useQuickReactor } from 'tldraw'
import 'tldraw/tldraw.css'
import './mask-window.css'
function MaskWindow() {
const editor = useEditor()
const ref = useRef<HTMLDivElement>(null)
useQuickReactor(
'clip',
() => {
const elm = ref.current
if (!elm) return
const rotation = editor.getSelectionRotation()
const box = editor.getSelectionRotatedScreenBounds()
if (!box) {
// If there's nothing selected, clear the clip path
elm.style.clipPath = ''
return
}
// Expand the box and get the corners
const { corners } = box.clone().expandBy(20)
// Account for rotation by rotating the points of the rectangle
const [tl, tr, br, bl] = corners.map((p) => p.rotWith(box.point, rotation))
// Since there's no reliable "reverse clip path", we wind around the corners in order to turn our clip into a mask
elm.style.clipPath = `polygon(0% 0%, ${tl.x}px 0%, ${tl.x}px ${tl.y}px, ${bl.x}px ${bl.y}px, ${br.x}px ${br.y}px, ${tr.x}px ${tr.y}px, ${tl.x}px ${tl.y}px, ${tl.x}px 0%, 100% 0%, 100% 100%, 0% 100%)`
},
[editor]
)
useExtraBonusStuff()
return <div ref={ref} className="mask-fg" />
}
const components: TLComponents = {
InFrontOfTheCanvas: () => {
return <MaskWindow />
},
}
export default function MaskWindowExample() {
return (
<div className="tldraw__editor">
<Tldraw persistenceKey="mask" components={components} />
</div>
)
}
// Some extra stuff that isnt necessary but good for this demo
function useExtraBonusStuff() {
const editor = useEditor()
useEffect(() => {
if (editor.getCurrentPageShapeIds().size === 0) {
const vpb = editor.getViewportPageBounds()
// if the canvas is empty, create some shapes for the demo
for (let i = 0; i < 50; i++) {
const x = vpb.x + Math.random() * vpb.w
const y = vpb.y + Math.random() * vpb.h
editor.createShape({ type: 'geo', x, y })
}
const id = createShapeId()
const { center } = editor.getViewportPageBounds()
editor.createShape({
id,
type: 'geo',
x: center.x - 100,
y: center.y - 100,
props: {
w: 200,
h: 200,
},
})
editor.select(id)
}
// As a (fragile) treat, press J to save positions for the selected shape, then press Ctrl+J to animate between positions
const positions: TLShape[] = []
const handleKeydown = (e: any) => {
if (e.key === 'j') {
e.preventDefault()
e.stopPropagation()
if (e.metaKey || e.ctrlKey) {
const shape = positions.shift()
if (!shape) return
editor.animateShape(shape, { animation: { duration: 1000 } })
} else {
const shape = editor.getOnlySelectedShape()
if (!shape) return
positions.push(shape)
}
}
}
document.addEventListener('keydown', handleKeydown)
return () => {
document.removeEventListener('keydown', handleKeydown)
}
}, [editor])
}
Is this page helpful?
Prev
... with private contentNext
Fog of war