packages/storybook/stories/render-path.stories.tsx (131 lines of code) (raw):

import { createElement, DebugFlags, RenderPath, Size, SyntheticPointerEvent } from '@canvas-ui/core' import type { StoryObj } from '@storybook/react' import React, { useEffect, useRef } from 'react' const PATH1 = ` M28,0.5 l-25.5,0 c-0.41421,0 -0.78921,0.16789 -1.06066,0.43934 c-0.27145,0.27145 -0.43934,0.64645 -0.43934,1.06066 l0,5.3 l0,8.35 l6,-7 l18,0 l6,7 l0,-8.35 l0,-5.3 c-0.03256,-0.38255 -0.20896,-0.724 -0.47457,-0.97045 c-0.26763,-0.24834 -0.62607,-0.40013 -1.01995,-0.40013z ` const PATH2 = ` M10 80 Q 52.5 10, 95 80 T 180 80 ` export const RenderPathTest: StoryObj<React.FC> = () => { const canvasElRef = useRef<HTMLCanvasElement | null>(null) const prevFrameButtonRef = useRef<HTMLButtonElement | null>(null) const nextFrameButtonRef = useRef<HTMLButtonElement | null>(null) useEffect(() => { DebugFlags.set(DebugFlags.PathBounds) // 等待布局稳定 setTimeout(() => { if (!canvasElRef.current) { return } const canvasRect = canvasElRef.current.getBoundingClientRect() const surfaceSize = Size.fromWH(canvasRect.width, canvasRect.height) const canvas = createElement('Canvas') canvas.prepareInitialFrame() canvas.el = canvasElRef.current canvas.size = surfaceSize canvas.dpr = devicePixelRatio const originStyles = new WeakMap<RenderPath, Pick<RenderPath, 'stroke' | 'fill'>>() const handlePointerEvent = (event: SyntheticPointerEvent<RenderPath>) => { const target = event.target if (target instanceof RenderPath) { if (event.type === 'pointerover') { originStyles.set(target, { stroke: target.stroke, fill: target.fill, }) if (target.stroke) { target.stroke = '#ff0000' } if (target.fill) { target.fill = 'rgba(255, 0, 0, 0.1)' } } else if (event.type === 'pointerout') { const styles = originStyles.get(target) if (styles) { target.stroke = styles.stroke target.fill = styles.fill } } } } // // 构造如下结构 // a (Flex) // / \ \ // b c d // const a = createElement('Flex') // 否则会拉伸 a.style.alignItems = 'flex-start' a.addEventListener('pointerover', handlePointerEvent) a.addEventListener('pointerout', handlePointerEvent) canvas.child = a const b = createElement('Path') b.repaintBoundary = true a.appendChild(b) const c = createElement('Path') a.appendChild(c) const d = createElement('Path') a.appendChild(d) type FrameCallback = () => void const frameCallbacks: readonly FrameCallback[] = [ // 0: initial frame () => { b.fill = '#EFEFEF' b.style.boxShadow = '12px 12px 2px rgba(0, 0, 255, .2)' b.path = PATH1 b.style.width = 200 b.style.height = 200 c.fill = '#FEFEFE' c.stroke = '#B5BAD0' c.strokeWidth = 1 c.style.boxShadow = '12px 12px 2px rgba(0, 0, 255, .2)' c.path = PATH1 d.stroke = '#CCCCCC' d.strokeWidth = 1 d.style.boxShadow = '4px 4px 4px rgba(0, 0, 0, .2)' d.path = PATH2 d.hitTestStrokeWidth = 8 }, () => { b.style.height = undefined } ] let frame = -1 const play = (direction = 1) => { frame += direction if (frame >= frameCallbacks.length) { frame = 0 } else if (frame < 0) { frame = frameCallbacks.length - 1 } frameCallbacks[frame]() } // 绘制首帧 play() prevFrameButtonRef.current?.addEventListener('click', () => { play(-1) }) nextFrameButtonRef.current?.addEventListener('click', () => { play(1) }) }, 100) }, [canvasElRef]) return ( <> <div> <button ref={prevFrameButtonRef}>prev frame</button> <button ref={nextFrameButtonRef}>next frame</button> </div> <canvas style={{ backgroundColor: '#ffffff', width: '500px', height: '500px' }} ref={canvasElRef}></canvas> </> ) } RenderPathTest.storyName = 'RenderPath' export default { title: 'core/rendering', component: RenderPathTest, decorators: [(Story: React.ComponentType) => <div style={{ backgroundColor: '#efefef', width: '100%', height: '100vh' }}><Story /></div>], }