3d example (#3647)
This PR adds a little example of using 3D transforms in tldraw. ![Kapture 2024-04-30 at 09 52 18](https://github.com/tldraw/tldraw/assets/23072548/c7620384-629b-4914-a842-5783591c9a3a) ### Change Type - [x] `docs` - [x] `improvement`
This commit is contained in:
parent
487addd2e8
commit
932bbf0b1e
5 changed files with 174 additions and 0 deletions
BIN
apps/examples/public/man.png
Normal file
BIN
apps/examples/public/man.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
apps/examples/public/shadow-man.png
Normal file
BIN
apps/examples/public/shadow-man.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
25
apps/examples/src/examples/popup-shape/PopupShapeExample.tsx
Normal file
25
apps/examples/src/examples/popup-shape/PopupShapeExample.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { Tldraw } from 'tldraw'
|
||||
import 'tldraw/tldraw.css'
|
||||
import { PopupShapeUtil } from './PopupShapeUtil'
|
||||
|
||||
const customShapeUtils = [PopupShapeUtil]
|
||||
|
||||
export default function PopupShapeExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
shapeUtils={customShapeUtils}
|
||||
onMount={(editor) => {
|
||||
for (let i = 0; i < 9; i++) {
|
||||
editor.createShape({
|
||||
type: 'my-popup-shape',
|
||||
x: (i % 3) * 220,
|
||||
y: Math.floor(i / 3) * 220,
|
||||
})
|
||||
}
|
||||
editor.zoomToContent({ duration: 0 })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
138
apps/examples/src/examples/popup-shape/PopupShapeUtil.tsx
Normal file
138
apps/examples/src/examples/popup-shape/PopupShapeUtil.tsx
Normal file
|
@ -0,0 +1,138 @@
|
|||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
BaseBoxShapeUtil,
|
||||
HTMLContainer,
|
||||
ShapeProps,
|
||||
T,
|
||||
TLBaseShape,
|
||||
stopEventPropagation,
|
||||
} from 'tldraw'
|
||||
|
||||
type IMyPopupShape = TLBaseShape<
|
||||
'my-popup-shape',
|
||||
{
|
||||
w: number
|
||||
h: number
|
||||
animal: number
|
||||
}
|
||||
>
|
||||
|
||||
export class PopupShapeUtil extends BaseBoxShapeUtil<IMyPopupShape> {
|
||||
static override type = 'my-popup-shape' as const
|
||||
static override props: ShapeProps<IMyPopupShape> = {
|
||||
w: T.number,
|
||||
h: T.number,
|
||||
animal: T.number,
|
||||
}
|
||||
|
||||
getDefaultProps(): IMyPopupShape['props'] {
|
||||
return {
|
||||
w: 200,
|
||||
h: 200,
|
||||
animal: 0,
|
||||
}
|
||||
}
|
||||
|
||||
component(shape: IMyPopupShape) {
|
||||
const [popped, setPopped] = useState(false)
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const ref2 = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const elm = ref.current
|
||||
if (!elm) return
|
||||
const elm2 = ref2.current
|
||||
if (!elm2) return
|
||||
if (popped) {
|
||||
// man
|
||||
// elm2.style.transform = `rotateX(0deg) translateY(0px) translateZ(0px)`
|
||||
// note
|
||||
elm.style.transform = `rotateX(0deg) translateY(0px) translateZ(0px)`
|
||||
} else {
|
||||
// man
|
||||
// elm.style.transform = `rotateX(-50deg) translateY(5px) translateZ(0px)`
|
||||
// elm2.style.transform = `scaleY(.8)`
|
||||
// note
|
||||
elm.style.transform = `rotateX(20deg)`
|
||||
}
|
||||
}, [popped])
|
||||
|
||||
const vpb = this.editor.getViewportPageBounds()
|
||||
const spb = this.editor.getShapePageBounds(shape)!
|
||||
const px = vpb.midX - spb.midX + spb.w / 2
|
||||
const py = vpb.midY - spb.midY + spb.h / 2
|
||||
|
||||
return (
|
||||
<HTMLContainer
|
||||
style={{
|
||||
pointerEvents: 'all',
|
||||
perspective: `${Math.max(vpb.w, vpb.h)}px`,
|
||||
perspectiveOrigin: `${px}px ${py}px`,
|
||||
}}
|
||||
onPointerDown={stopEventPropagation}
|
||||
onDoubleClick={(e) => {
|
||||
setPopped((p) => !p)
|
||||
stopEventPropagation(e)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={ref2}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
transition: `all .5s`,
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
// man
|
||||
// transformOrigin: 'bottom center',
|
||||
// backgroundImage: `url(/shadow-man.png)`,
|
||||
// note
|
||||
backgroundColor: 'rgba(0,0,0,.5)',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
ref={ref}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
transition: `all .5s`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 16,
|
||||
overflow: 'hidden',
|
||||
fontFamily: 'tldraw_draw',
|
||||
color: '#333',
|
||||
fontSize: 24,
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
transformOrigin: 'top center',
|
||||
// man
|
||||
// backgroundImage: `url(/man.png)`,
|
||||
// transformOrigin: 'bottom center',
|
||||
// transform: `rotateX(20deg) translateY(5px) translateZ(40px)`,
|
||||
// note
|
||||
background: `gold`,
|
||||
border: '1px solid goldenrod',
|
||||
}}
|
||||
>
|
||||
{/* {shape.id.slice(-1).toUpperCase()} */}
|
||||
</div>
|
||||
</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
indicator(shape: IMyPopupShape) {
|
||||
return <rect width={shape.props.w} height={shape.props.h} />
|
||||
}
|
||||
}
|
11
apps/examples/src/examples/popup-shape/README.md
Normal file
11
apps/examples/src/examples/popup-shape/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: Popup shape
|
||||
component: ./PopupShapeExample.tsx
|
||||
category: shapes/tools
|
||||
---
|
||||
|
||||
...
|
||||
|
||||
---
|
||||
|
||||
...
|
Loading…
Reference in a new issue