Custom renderer example (#3091)

This PR adds a custom renderer example. Ever wanted to see how to use an
HTML canvas with tldraw? Here's how!

![Kapture 2024-03-09 at 22 35
09](https://github.com/tldraw/tldraw/assets/23072548/9e258a8f-f99f-419a-b92a-f58b1ce93973)

### Change Type

- [x] `documentation` — Changes to the documentation only[^2]
This commit is contained in:
Steve Ruiz 2024-03-09 22:40:50 +01:00 committed by GitHub
parent eb80cf787b
commit a691c60315
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 151 additions and 0 deletions

View file

@ -0,0 +1,107 @@
import { useLayoutEffect, useRef } from 'react'
import { TLDrawShape, TLGeoShape, getDefaultColorTheme, useEditor } from 'tldraw'
export function CustomRenderer() {
const editor = useEditor()
const rCanvas = useRef<HTMLCanvasElement>(null)
useLayoutEffect(() => {
const canvas = rCanvas.current
if (!canvas) return
canvas.style.width = '100%'
canvas.style.height = '100%'
const rect = canvas.getBoundingClientRect()
canvas.width = rect.width
canvas.height = rect.height
const ctx = canvas.getContext('2d')!
let isCancelled = false
function render() {
if (isCancelled) return
if (!canvas) return
ctx.resetTransform()
ctx.clearRect(0, 0, canvas.width, canvas.height)
const camera = editor.getCamera()
ctx.scale(camera.z, camera.z)
ctx.translate(camera.x, camera.y)
const renderingShapes = editor.getRenderingShapes()
const theme = getDefaultColorTheme({ isDarkMode: editor.user.getIsDarkMode() })
const currentPageId = editor.getCurrentPageId()
for (const { shape, maskedPageBounds, opacity } of renderingShapes) {
if (!maskedPageBounds) continue
ctx.save()
if (shape.parentId !== currentPageId) {
ctx.beginPath()
ctx.rect(
maskedPageBounds.minX,
maskedPageBounds.minY,
maskedPageBounds.width,
maskedPageBounds.height
)
ctx.clip()
}
ctx.beginPath()
ctx.globalAlpha = opacity
const transform = editor.getShapePageTransform(shape.id)
ctx.transform(transform.a, transform.b, transform.c, transform.d, transform.e, transform.f)
if (editor.isShapeOfType<TLDrawShape>(shape, 'draw')) {
// Draw a freehand shape
for (const segment of shape.props.segments) {
ctx.moveTo(segment.points[0].x, segment.points[0].y)
if (segment.type === 'straight') {
ctx.lineTo(segment.points[1].x, segment.points[1].y)
} else {
for (const point of segment.points.slice(1)) {
ctx.lineTo(point.x, point.y)
}
}
}
ctx.strokeStyle = theme[shape.props.color].solid
ctx.lineWidth = 4
ctx.stroke()
if (shape.props.fill !== 'none' && shape.props.isClosed) {
ctx.fillStyle = theme[shape.props.color].semi
ctx.fill()
}
} else if (editor.isShapeOfType<TLGeoShape>(shape, 'geo')) {
// Draw a geo shape
const bounds = editor.getShapeGeometry(shape).bounds
ctx.strokeStyle = theme[shape.props.color].solid
ctx.lineWidth = 2
ctx.strokeRect(bounds.minX, bounds.minY, bounds.width, bounds.height)
} else {
// Draw any other kind of shape
const bounds = editor.getShapeGeometry(shape).bounds
ctx.strokeStyle = 'black'
ctx.lineWidth = 2
ctx.strokeRect(bounds.minX, bounds.minY, bounds.width, bounds.height)
}
ctx.restore()
}
requestAnimationFrame(render)
}
render()
return () => {
isCancelled = true
}
}, [editor])
return <canvas ref={rCanvas} />
}

View file

@ -0,0 +1,33 @@
import { useLayoutEffect } from 'react'
import { DefaultCanvas, Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'
import { CustomRenderer } from './CustomRenderer'
export default function CustomRendererExample() {
useLayoutEffect(() => {
// Hide the regular shapes layer using CSS.
const script = document.createElement('style')
if (!script) return
script.innerHTML = `.tl-shapes { display: none; }`
document.body.appendChild(script)
return () => {
script.remove()
}
})
return (
<div className="tldraw__editor">
<Tldraw
persistenceKey="example"
components={{
// We're replacing the Background component with our custom renderer
Background: CustomRenderer,
// Even though we're hiding the shapes, we'll still do a bunch of work
// in react to figure out which shapes to create. In reality, you might
// want to set the Canvas component to null and render it all yourself.
Canvas: DefaultCanvas,
}}
/>
</div>
)
}

View file

@ -0,0 +1,11 @@
---
title: Custom renderer
component: ./CustomRendererExample.tsx
category: basic
---
You can _sort of_ use a custom renderer with tldraw.
---
This example shows how you might use a custom renderer with tldraw.