@blinkorb/rcx 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +460 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,460 @@
1
+ # RCX
2
+
3
+ **Reactive JSX-based library for creating HTML5 canvas applications**
4
+
5
+ ## Preamble
6
+
7
+ This library is in early development, and so the interfaces you interact with may change. We'll setup full documentation when the API stabilizes. For now the readme should give you enough info to get started.
8
+
9
+ ## About
10
+
11
+ RCX closely resembles other JSX-based view libraries such as React/Vue, but allows you to render to canvas. It can even be used in conjunction with other view libraries (see [Integrating With React](#integrating-with-react) example).
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm i @blinkorb/rcx -P
17
+ ```
18
+
19
+ ## The Basics
20
+
21
+ ### TypeScript Config
22
+
23
+ In order to use RCX with TypeScript (if you are not already using another JSX-based library), you should set the following `compilerOptions` in your `tsconfig.json`:
24
+
25
+ ```json
26
+ {
27
+ "compilerOptions": {
28
+ "jsx": "react-jsx",
29
+ "jsxImportSource": "@blinkorb/rcx"
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### Entry Point
35
+
36
+ All components in RCX are function components. To get started you should create an `App` component that renders the `Canvas` component.
37
+
38
+ The canvas component allows you to control things like the size and pixel ratio of your canvas, and provides context to other components to inform them of its size, pixel ratio, etc.
39
+
40
+ A `pixelRatio` of 2 and `width` of `100` will actually render a canvas that is `200` in width, but scale your drawings so you don't have to manually scale everything - allows for crisper drawings on high density/retina displays. You can use the `getRecommendedPixelRatio` util to use our recommendation (`2` for any devices with a `devicePixelRatio` greater than or equal to 2, and `1` for every other device).
41
+
42
+ If you set the `width` and or `height` to `"auto"` then the canvas' pixels will match the actual size of the canvas on the screen (scaled by pixel ratio). This way you can have your canvas automatically scale to fill its parent (using CSS) for example.
43
+
44
+ ```tsx
45
+ import { Canvas } from '@blinkorb/rcx';
46
+
47
+ const App = () => {
48
+ return (
49
+ <Canvas pixelRatio={getRecommendedPixelRatio()}>
50
+ {/* Your component here */}
51
+ </Canvas>
52
+ );
53
+ };
54
+ ```
55
+
56
+ You can then render this component using the `render` function. The render function's second argument is the DOM node where you would like the canvas to appear. If the node is already a canvas RCX will render to that canvas, otherwise it will add a canvas within that node.
57
+
58
+ ```tsx
59
+ import { render } from '@blinkorb/rcx';
60
+
61
+ render(<App />, document.body);
62
+ ```
63
+
64
+ ### Basic Components
65
+
66
+ #### Transform Components
67
+
68
+ We provide `Translate`, `Scale`, and `Rotate` components that will transform any of their children.
69
+
70
+ In the below example the `Offset` component will be offset by 10 pixels in both the `x` and `y` axis. The `NoOffset` component will not be affected by the transform.
71
+
72
+ ```tsx
73
+ <>
74
+ <Translate x={10} y={10}>
75
+ <Offset />
76
+ </Translate>
77
+ <NoOffset />
78
+ </>
79
+ ```
80
+
81
+ #### Shape Components
82
+
83
+ We provide `Circle`, `Ellipse`, and `Rectangle` components for rendering some basic shapes. Each of these can receive a style prop to apply a stroke/border, and fill. You can also define (for some of these components) if the shape should continue from any existing drawings, or begin a new path by setting the `beginPath` prop. You can also choose to close these shapes by setting the `closePath` prop.
84
+
85
+ ```tsx
86
+ <>
87
+ <Circle
88
+ x={50}
89
+ y={50}
90
+ radius={50}
91
+ beginPath
92
+ closePath
93
+ style={{
94
+ strokeWidth: 1,
95
+ stroke: 'black',
96
+ }}
97
+ />
98
+ <Ellipse
99
+ x={50}
100
+ y={50}
101
+ radiusX={20}
102
+ radiusY={50}
103
+ beginPath
104
+ closePath
105
+ style={{
106
+ strokeWidth: 1,
107
+ stroke: 'black',
108
+ fill: 'red',
109
+ }}
110
+ />
111
+ <Rectangle
112
+ x={0}
113
+ y={0}
114
+ width={100}
115
+ height={50}
116
+ beginPath
117
+ style={{ fill: 'blue' }}
118
+ />
119
+ </>
120
+ ```
121
+
122
+ #### Path Components
123
+
124
+ We provide a selection of components for drawing paths. These components can be combined to draw more complex shapes.
125
+
126
+ All path plotting components can have stroke styles. `Path` and `ArcTo` components can also have fill styles (fills are excluded from `Line` as it is more performant to use `Path`).
127
+
128
+ ```tsx
129
+ <>
130
+ {/* Plot a single line */}
131
+ <Line
132
+ startX={0}
133
+ startY={0}
134
+ endX={10}
135
+ endY={10}
136
+ beginPath
137
+ style={{
138
+ strokeWidth: 2,
139
+ stroke: 'black',
140
+ }}
141
+ />
142
+ {/* Plot an arc */}
143
+ <ArcTo
144
+ startControlX={0}
145
+ startControlY={0}
146
+ endControlX={10}
147
+ endControlY={10}
148
+ radius={10}
149
+ style={{
150
+ strokeWidth: 2,
151
+ stroke: 'black',
152
+ }}
153
+ />
154
+ {/* Plot a path from an array of points */}
155
+ <Path
156
+ points={[
157
+ {
158
+ x: 0,
159
+ y: 0,
160
+ },
161
+ {
162
+ x: 10,
163
+ y: 10,
164
+ },
165
+ ]}
166
+ beginPath
167
+ style={{
168
+ strokeWidth: 2,
169
+ stroke: 'black',
170
+ }}
171
+ />
172
+ {/* Plot a path from an array of points using the Point component */}
173
+ <Path
174
+ beginPath
175
+ style={{
176
+ strokeWidth: 2,
177
+ stroke: 'black',
178
+ }}
179
+ >
180
+ {points.map((point, index) => (
181
+ <Point $key={index} x={point.x} x={point.y} lineTo={index > 0} />
182
+ ))}
183
+ </Path>
184
+ {/* Plot a path using manually specified Points */}
185
+ <Path
186
+ beginPath
187
+ style={{
188
+ strokeWidth: 2,
189
+ stroke: 'black',
190
+ }}
191
+ >
192
+ <Point x={0} x={0} lineTo={false} />
193
+ <Point x={10} x={10} lineTo={true} />
194
+ </Path>
195
+ </>
196
+ ```
197
+
198
+ In addition to the path plotting components we provide, we also have a `Clip` component that can be used to apply a clipping mask to future drawings.
199
+
200
+ ```tsx
201
+ <>
202
+ <Circle x={50} y={50} radius={50}>
203
+ <Clip>
204
+ <ComponentWillOnlyDrawInsideCircle />
205
+ </Clip>
206
+ </Circle>
207
+ </>
208
+ ```
209
+
210
+ #### Text Components
211
+
212
+ We currently only provide a single `Text` component that will render a single line of raw text. We hope to add multi-line and rich text components in the future. You can also render components that contain text or number within a `Text` component.
213
+
214
+ ```tsx
215
+ <Text x={10} y={10} style={{
216
+ fill: 'black',
217
+ align: 'center,
218
+ }}>
219
+ The count is {count}
220
+ <ContainsSomeText />
221
+ </Text>
222
+ ```
223
+
224
+ ## Custom Components
225
+
226
+ You can define your own components with complex drawing logic directly applied via canvas context using the `useRenderBeforeChildren` and `useRenderAfterChildren` hooks.
227
+
228
+ It is highly recommended to `.save()` the canvas state before beginning drawing in `useRenderBeforeChildren` and to `.restore()` the canvas state after drawing in the `useRenderAfterChildren`.
229
+
230
+ We also provide some utils for resolving and applying styles as styles can be provided as an array, and all fills and strokes are always applied in the same way.
231
+
232
+ Here's an example that draws a rectangle with rounded corners.
233
+
234
+ ```tsx
235
+ interface RoundedRectangleProps extends RectangleProps {
236
+ radius: number;
237
+ closePath?: boolean;
238
+ }
239
+
240
+ const RoundedRectangle: RCXComponent<RoundedRectangleProps> = (props) => {
241
+ useRenderBeforeChildren((renderingContext) => {
242
+ const { x, y, width, height, radius, beginPath = true, closePath } = props;
243
+
244
+ renderingContext.ctx2d.save();
245
+
246
+ if (beginPath) {
247
+ renderingContext.ctx2d.beginPath();
248
+ }
249
+
250
+ renderingContext.ctx2d.moveTo(x + radius, y);
251
+ renderingContext.ctx2d.lineTo(x + width - radius, y);
252
+ renderingContext.ctx2d.arcTo(x + width, y, x + width, y + radius, radius);
253
+ renderingContext.ctx2d.lineTo(x + width, y + height - radius);
254
+ renderingContext.ctx2d.arcTo(
255
+ x + width,
256
+ y + height,
257
+ x + width - radius,
258
+ y + height,
259
+ radius
260
+ );
261
+ renderingContext.ctx2d.lineTo(x + radius, y + height);
262
+ renderingContext.ctx2d.arcTo(x, y + height, x, y + height - radius, radius);
263
+ renderingContext.ctx2d.lineTo(x, y + radius);
264
+ renderingContext.ctx2d.arcTo(x, y, x + radius, y, radius);
265
+
266
+ if (closePath) {
267
+ renderingContext.ctx2d.closePath();
268
+ }
269
+ });
270
+
271
+ useRenderAfterChildren((renderingContext) => {
272
+ applyFillAndStrokeStyles(renderingContext, resolveStyles(props.style));
273
+
274
+ renderingContext.ctx2d.restore();
275
+ });
276
+
277
+ return props.children;
278
+ };
279
+ ```
280
+
281
+ ## Hooks
282
+
283
+ ### useCanvasContext
284
+
285
+ Provides the context from the current canvas including its `pixelRatio`, `width` and `height` (scaled by `pixelRatio`), and actual width/height (e.g. with a `pixelRatio` of `2` and `width` of `100` the `actualWidth` of the canvas will be `200` - you should generally avoid using the actual sizes and rely on the scaled `width` and `height` values).
286
+
287
+ ### useRenderBeforeChildren
288
+
289
+ Used for creating custom components with complex rendering logic. Takes a callback that receives the current canvas rendering context to allow manually drawing with the canvas context. The callback is called before any children are rendered. See [Custom Components](#custom-components) for a full example.
290
+
291
+ ### useRenderAfterChildren
292
+
293
+ Used for creating custom components with complex rendering logic. Takes a callback that receives the current canvas rendering context to allow manually drawing with the canvas context. The callback is called after any children are rendered. See [Custom Components](#custom-components) for a full example.
294
+
295
+ ### useLinearGradient
296
+
297
+ Can be used to create a linear gradient that can then be applied as a fill/stroke style.
298
+
299
+ ```tsx
300
+ const stroke = useLinearGradient({
301
+ startX: 0,
302
+ startY: 0,
303
+ endX: 10,
304
+ endY: 10,
305
+ stops: [
306
+ {
307
+ offset: 0,
308
+ color: '#f00',
309
+ },
310
+ {
311
+ offset: 1,
312
+ color: '#000',
313
+ },
314
+ ],
315
+ });
316
+ ```
317
+
318
+ ### useRadialGradient
319
+
320
+ Can be used to create a radial gradient that can then be applied as a fill/stroke style.
321
+
322
+ ```tsx
323
+ const fill = useRadialGradient({
324
+ startX: 10,
325
+ startY: 10,
326
+ startRadius: 0,
327
+ endX: 0,
328
+ endY: 0,
329
+ endRadius: 10,
330
+ stops: [
331
+ {
332
+ offset: 0,
333
+ color: '#000',
334
+ },
335
+ {
336
+ offset: 1,
337
+ color: '#00f',
338
+ },
339
+ ],
340
+ });
341
+ ```
342
+
343
+ ### useLoop
344
+
345
+ Takes a callback and calls this in an infinite `requestAnimationFrame` loop.
346
+
347
+ ```tsx
348
+ useLoop(() => {
349
+ // Your logic here
350
+ });
351
+ ```
352
+
353
+ ### useOnMount
354
+
355
+ Takes a callback that is executed when the component mounts. This callback can return another callback that is executed when a component unmounts e.g. to cleanup listeners.
356
+
357
+ ```tsx
358
+ useOnMount(() => {
359
+ // Logic on mount
360
+
361
+ return () => {
362
+ // Logic on unmount
363
+ };
364
+ });
365
+ ```
366
+
367
+ ### useOnUnmount
368
+
369
+ Takes a callback that is executed when the component is mounted.
370
+
371
+ ```tsx
372
+ useOnUnmount(() => {
373
+ // Logic on unmount
374
+ });
375
+ ```
376
+
377
+ ### useReactive
378
+
379
+ Receives an object that will be wrapped in a JavaScript proxy. Any mutations to this object will cause a re-render.
380
+
381
+ Note: you must use either a `useReactive` or `useUnreactive` for any state values you want to persist. When a component re-renders any state that is defined as basic variables will be recreated.
382
+
383
+ ```tsx
384
+ const state = useReactive({ count: 0 });
385
+
386
+ // This will reset to zero every render
387
+ const notState = { count: 0 };
388
+ ```
389
+
390
+ ### useUnreactive
391
+
392
+ Receives an object that will be available until the component is unmounted. Unlike `useReactive` any mutations on this object will not cause a re-render.
393
+
394
+ Note: you must use either a `useReactive` or `useUnreactive` for any state values you want to persist. When a component re-renders any state that is defined as basic variables will be recreated.
395
+
396
+ ```tsx
397
+ const state = useUnreactive({ count: 0 });
398
+
399
+ // This will reset to zero every render
400
+ const notState = { count: 0 };
401
+ ```
402
+
403
+ ### useWindowSize
404
+
405
+ Returns the current window size. This will update when the window is resized.
406
+
407
+ ## Integrating With React
408
+
409
+ ### TypeScript Config With React
410
+
411
+ If you're already using JSX with another view library, such as React, you'll likely already have your `tsconfig.json` setup to handle JSX for React e.g.
412
+
413
+ ```json
414
+ {
415
+ "compilerOptions": {
416
+ "jsx": "react-jsx",
417
+ "jsxImportSource": "react"
418
+ }
419
+ }
420
+ ```
421
+
422
+ You can tell TypeScript to treat your RCX components differently (using the JSX types from RCX itself) by adding the following to the top of any RCX component files:
423
+
424
+ ```tsx
425
+ /** @jsxImportSource @blinkorb/rcx */
426
+ ```
427
+
428
+ ### Rendering RCX Components Within React
429
+
430
+ You can render an RCX component within a React app by getting a `ref` to a canvas node and rendering/unmounting the RCX tree at this node within a `useEffect`.
431
+
432
+ Make sure you're importing the correct `jsx` function from RCX.
433
+
434
+ ```tsx
435
+ import { render } from '@blinkorb/rcx';
436
+ import { jsx } from '@blinkorb/rcx/jsx-runtime';
437
+ import App from './your-canvas-app-component';
438
+
439
+ const ReactComponent = () => {
440
+ const [canvas, setCanvas] = useState<HTMLCanvasElement | null>(null);
441
+
442
+ useEffect(() => {
443
+ const unmountOrError = canvas ? render(jsx(App, {}), canvas) : null;
444
+
445
+ // Check if we have an error
446
+ if (unmountOrError && 'error' in unmountOrError) {
447
+ console.error(unmountOrError.error);
448
+ }
449
+
450
+ return () => {
451
+ // Ensure we have the unmount function (no errors)
452
+ if (unmountOrError && !('error' in unmountOrError)) {
453
+ unmountOrError();
454
+ }
455
+ };
456
+ }, [canvas]);
457
+
458
+ return <canvas ref={setCanvas} />;
459
+ };
460
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkorb/rcx",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Reactive JSX-based library for creating HTML5 canvas applications",
5
5
  "publishConfig": {
6
6
  "access": "public"