Tabris.js Widgets: Canvas
This is a widget that can be used to draw graphics using a canvas context, which is a subset of the HTML5 CanvasRenderingContext2D. Just like with a <canvas> tag it allows you to create simple geometrical figures, make animations, text and load images from source.
Here is how a simple Canvas looks:
1 2 3 4 5 6 7 8 |
import { Canvas, contentView } from 'tabris'; new Canvas({ layoutData: 'stretch' }) .onResize(({ target: canvas, width, height }) => { let context = canvas.getContext("2d", width, height); context.moveTo(0, 0); // ... }).appendTo(contentView); |
Canvas class got only one method – getContext()
which returns the CanvasContext
instance, which holds all the necessary functions for drawing.
Most of the CanvasContext
tools are simple and straightforward to use. Notice at the example below that the description text for each image is a part of Canvas too instead of being TextView
instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
import { Canvas, contentView, device } from 'tabris'; contentView.append( <Canvas width={300} height={300} center onResize={(e) => drawShapes(e)} /> ); function drawShapes({ target: canvas, width, height }) { const scaleFactor = device.scaleFactor; const ctx = canvas.getContext('2d', width * scaleFactor, height * scaleFactor); ctx.scale(scaleFactor, scaleFactor); ctx.textBaseline = 'top'; ctx.textAlign = 'center'; ctx.fillStyle = '#db4437aa'; ctx.fillRect(40, 20, 80, 60); ctx.fillStyle = '#3f51b5aa'; ctx.fillRect(60, 40, 80, 60); ctx.fillStyle = '#8dbd00aa'; ctx.fillRect(20, 60, 80, 60); ctx.fillStyle = 'black'; ctx.fillText('transparency', 80, 130); ctx.lineWidth = 2; ctx.strokeStyle = '#db4437'; drawLinear(ctx, 220, 40); ctx.stroke(); ctx.strokeStyle = '#3f51b5'; drawQuadratic(ctx, 220, 70); ctx.stroke(); ctx.strokeStyle = '#8dbd00'; drawBezier(ctx, 220, 100); ctx.stroke(); ctx.fillText('curves', 220, 130); ctx.lineWidth = 2; ctx.strokeStyle = 'black'; drawPath(ctx, 80, 220, 50); ctx.stroke(); ctx.fillText('path', 80, 270); drawArc(ctx, 220, 220, 45); ctx.fillStyle = '#fed100'; ctx.fill(); ctx.fillStyle = 'black'; ctx.fillText('arc', 220, 270); } function drawLinear(ctx, x, y) { ctx.beginPath(); ctx.moveTo(x - 50, y); ctx.lineTo(x - 25, y - 15); ctx.lineTo(x + 25, y + 15); ctx.lineTo(x + 50, y); } function drawQuadratic(ctx, x, y) { ctx.beginPath(); ctx.moveTo(x - 50, y); ctx.quadraticCurveTo(x - 25, y - 25, x, y); ctx.quadraticCurveTo(x + 25, y + 25, x + 50, y); } function drawBezier(ctx, x, y) { ctx.beginPath(); ctx.moveTo(x - 50, y); ctx.bezierCurveTo(x - 50, y - 25, x, y - 25, x, y); ctx.bezierCurveTo(x, y + 25, x + 50, y + 25, x + 50, y); } function drawPath(ctx, x, y, radius) { ctx.beginPath(); const rotate = -Math.PI / 2; ctx.moveTo(x, y - radius); for (let i = 0; i <= 4 * Math.PI; i += (4 * Math.PI) / 5) { ctx.lineTo(x + radius * Math.cos(i + rotate), y + radius * Math.sin(i + rotate)); } ctx.closePath(); } function drawArc(ctx, x, y, radius) { ctx.beginPath(); ctx.moveTo(x, y); ctx.arc(x, y, radius, Math.PI / 4, -Math.PI / 4); ctx.closePath(); } |
Great use for Canvas is interactive diagrams and animations. Run the animation example using QR code below to see it in action:
And if you want to see a high-detailed image or don’t want to draw everything from scratch, you can also load an image and add it on Canvas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { Canvas, contentView, device } from 'tabris'; (async () => { const response = await fetch('resources/target_200.png'); const image = await createImageBitmap(await response.blob()); contentView.append(<Canvas stretch onResize={draw} />); /** @param {tabris.WidgetResizeEvent<Canvas>} ev */ function draw({ target: canvas, width, height }) { const scaleFactor = device.scaleFactor; const ctx = canvas.getContext('2d', width * scaleFactor, height * scaleFactor); ctx.scale(scaleFactor, scaleFactor); ctx.strokeStyle = 'rgb(78, 154, 217)'; ctx.lineWidth = 10; ctx.moveTo(20, 20); ctx.lineTo(width - 40, height - 40); ctx.stroke(); ctx.drawImage(image, 50, 50); } })(); |
This way you can use a custom background for Canvas, a grid for a diagram, for example. Or you can allow users to upload images and place text over them. And since we are talking about placing a text on Canvas – here is how it will look depending on displaying mode:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
import { Canvas, contentView, device } from 'tabris'; const ARC_RADIUS = 20; contentView.append( <Canvas center width={300} height={300} onResize={(e) => drawTexts(e)} /> ); function drawTexts({ target: canvas, width, height }) { const scaleFactor = device.scaleFactor; const ctx = canvas.getContext('2d', width * scaleFactor, height * scaleFactor); ctx.scale(scaleFactor, scaleFactor); ctx.font = '14px'; ctx.strokeStyle = 'red'; let x = 50; let y = 50; ctx.moveTo(5, y); ctx.lineTo(295, y); ctx.stroke(); ctx.textAlign = 'center'; ['top', 'bottom', 'middle'].forEach((mode) => { ctx.textBaseline = mode; ctx.fillText(mode, x, y); x += 100; }); x = 50; y = 100; ctx.moveTo(5, y); ctx.lineTo(295, y); ctx.stroke(); ['hanging', 'alphabetic', 'ideographic'].forEach((mode) => { ctx.textBaseline = mode; ctx.fillText(mode, x, y); x += 100; }); x = 150; y = 170; ctx.moveTo(x, 150); ctx.lineTo(x, 270); ctx.stroke(); ctx.textBaseline = 'middle'; ['start', 'end', 'left', 'right', 'center'].forEach((mode) => { ctx.textAlign = mode; ctx.fillText(mode, x, y); y += 20; }); } |
For more detailed information about the widget, use the Canvas documentation page.
Feedback is welcome!
Want to join the discussion?Feel free to contribute!