blob: efcc5c4bb45c4e6a90e045ede4472eac53be805f [file] [log] [blame] [view]
Joe Gregorio02f72022021-03-27 10:12:45 -04001
2---
3title: "CanvasKit - Quickstart"
4linkTitle: "CanvasKit - Quickstart"
5
6---
7
8
9CanvasKit is a wasm module that uses Skia to draw to canvas elements a more advance feature set than the canvas API.
10
11Minimal application
12-------------------
13
14This example is a minimal Canvaskit application that draws a rounded rect for one frame.
15It pulls the wasm binary from unpkg.com but you can also build and host it yourself.
16
17<!--?prettify?-->
18``` js
19<canvas id=foo width=300 height=300></canvas>
20
21<script type="text/javascript"
22 src="https://unpkg.com/canvaskit-wasm@0.19.0/bin/canvaskit.js"></script>
23<script type="text/javascript">
24 const ckLoaded = CanvasKitInit({
25 locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.19.0/bin/'+file});
26 ckLoaded.then((CanvasKit) => {
27 const surface = CanvasKit.MakeCanvasSurface('foo');
28
29 const paint = new CanvasKit.Paint();
30 paint.setColor(CanvasKit.Color4f(0.9, 0, 0, 1.0));
31 paint.setStyle(CanvasKit.PaintStyle.Stroke);
32 paint.setAntiAlias(true);
33 const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 25, 15);
34
35 function draw(canvas) {
36 canvas.clear(CanvasKit.WHITE);
37 canvas.drawRRect(rr, paint);
38 }
39 surface.drawOnce(draw);
40 });
41</script>
42```
43
44<canvas id=foo width=300 height=300></canvas>
45
46<script type="text/javascript"
47 src="https://unpkg.com/canvaskit-wasm@0.19.0/bin/canvaskit.js"></script>
48<script type="text/javascript">
49 const ckLoaded = CanvasKitInit({
50 locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.19.0/bin/'+file});
51 ckLoaded.then((CanvasKit) => {
52 const surface = CanvasKit.MakeCanvasSurface('foo');
53
54 const paint = new CanvasKit.Paint();
55 paint.setColor(CanvasKit.Color4f(0.9, 0, 0, 1.0));
56 paint.setStyle(CanvasKit.PaintStyle.Stroke);
57 paint.setAntiAlias(true);
58 const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 25, 15);
59
60 function draw(canvas) {
61 canvas.clear(CanvasKit.WHITE);
62 canvas.drawRRect(rr, paint);
63 }
64 surface.drawOnce(draw);
65 });
66</script>
67
68Let's break it down into parts and explain what they are doing:
69
70`<canvas id=foo width=300 height=300></canvas>` Creates the canvas to which CanvasKit will draw.
71This element is where we control the width and height of the drawing buffer, while it's css style
72would control any scaling applied after drawing to those pixels. Despite using a canvas element,
73CanvasKit isn't calling the HTML canvas's own draw methods. It is using this canvas element to
74get a WebGL2 context and performing most of the drawing work in C++ code compiled to WebAssembly,
75then sending commands to the GPU at the end of each frame.
76
77<!--?prettify?-->
78``` html
79<script type="text/javascript"
80 src="https://unpkg.com/canvaskit-wasm@0.19.0/bin/canvaskit.js"></script>
81```
82and
83
84<!--?prettify?-->
85``` js
86const ckLoaded = CanvasKitInit({
87 locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.19.0/bin/'+file});
88ckLoaded.then((CanvasKit) => {
89```
90are loading the canvaskit helper js and wasm binary respectively. CanvasKitInit accepts a function
91for allowing you to alter the path where it will try to find `canvaskit.wasm` and returns a promise
92that resolves with the loaded module, which we typically name `CanvasKit`.
93
94<!--?prettify?-->
95``` js
96const surface = CanvasKit.MakeCanvasSurface('foo');
97```
98Creates a Surface associated with the HTML canvas element above.
99Hardware acceleration is the default behavior, but can be overridden by calling
100`MakeSWCanvasSurface` instead. `MakeCanvasSurface` is also where alternative color spaces or gl
101attrtributes can be specified.
102
103<!--?prettify?-->
104``` js
105const paint = new CanvasKit.Paint();
106paint.setColor(CanvasKit.Color4f(0.9, 0, 0, 1.0));
107paint.setStyle(CanvasKit.PaintStyle.Stroke);
108paint.setAntiAlias(true);
109const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 25, 15);
110```
111Creates a paint, a description of how to fill or stroke rects, paths, text and other geometry in
112canvaskit. `rr` is a rounded rect, with corners having a radius of 25 in the x axis, and 15 pixels
113in the y axis.
114
115<!--?prettify?-->
116``` js
117function draw(canvas) {
118 canvas.clear(CanvasKit.WHITE);
119 canvas.drawRRect(rr, paint);
120}
121```
122Defines a function that will draw our frame. The function is provided a Canvas object on which we
123make draw calls. One to clear the entire canvas, and one to draw the rounded rect with the
124paint from above.
125
126We also delete the paint object. CanvasKit objects created with `new` or methods prefixed with
127`make` must be deleted for the wasm memory to be released. Javascript's GC will not take care of
128it automatically. `rr` is just an array, wasn't created with `new` and doesn't point to any WASM
129memory, so we don't have to call delete on it.
130
131<!--?prettify?-->
132``` js
133surface.drawOnce(draw);
134paint.delete()
135```
136Hand the drawing function to `surface.drawOnce` which makes the calls and flushes the surface.
137Upon flushing, Skia will batch and send WebGL commands, making visible changes appear onscreen.
138This example draws once and disposes of the surface. As promised, it is is a minimal
139application.
140
141Basic Draw Loop
142---------------
143
144What if we need to redraw to our canvas every frame? This example
145bounces a rounded rect around like a 90s screensaver.
146
147<!--?prettify?-->
148``` js
149ckLoaded.then((CanvasKit) => {
150 const surface = CanvasKit.MakeCanvasSurface('foo2');
151
152 const paint = new CanvasKit.Paint();
153 paint.setColor(CanvasKit.Color4f(0.9, 0, 0, 1.0));
154 paint.setStyle(CanvasKit.PaintStyle.Stroke);
155 paint.setAntiAlias(true);
156 // const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 25, 15);
157 const w = 100; // size of rect
158 const h = 60;
159 let x = 10; // initial position of top left corner.
160 let y = 60;
161 let dirX = 1; // box is always moving at a constant speed in one of the four diagonal directions
162 let dirY = 1;
163
164 function drawFrame(canvas) {
165 // boundary check
166 if (x < 0 || x+w > 300) {
167 dirX *= -1; // reverse x direction when hitting side walls
168 }
169 if (y < 0 || y+h > 300) {
170 dirY *= -1; // reverse y direction when hitting top and bottom walls
171 }
172 // move
173 x += dirX;
174 y += dirY;
175
176 canvas.clear(CanvasKit.WHITE);
177 const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(x, y, x+w, y+h), 25, 15);
178 canvas.drawRRect(rr, paint);
179 surface.requestAnimationFrame(drawFrame);
180 }
181 surface.requestAnimationFrame(drawFrame);
182});
183```
184
185<canvas id=foo2 width=300 height=300></canvas>
186
187<script type="text/javascript">
188 ckLoaded.then((CanvasKit) => {
189 const surface = CanvasKit.MakeCanvasSurface('foo2');
190
191 const paint = new CanvasKit.Paint();
192 paint.setColor(CanvasKit.Color4f(0.9, 0, 0, 1.0));
193 paint.setStyle(CanvasKit.PaintStyle.Stroke);
194 paint.setAntiAlias(true);
195 // const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 25, 15);
196 const w = 100; // size of rect
197 const h = 60;
198 let x = 10; // initial position of top left corner.
199 let y = 60;
200 // The box is always moving at a constant speed in one of the four diagonal directions
201 let dirX = 1;
202 let dirY = 1;
203
204 function drawFrame(canvas) {
205 // boundary check
206 if (x < 0 || x+w > 300) {
207 dirX *= -1; // reverse x direction when hitting side walls
208 }
209 if (y < 0 || y+h > 300) {
210 dirY *= -1; // reverse y direction when hitting top and bottom walls
211 }
212 // move
213 x += dirX;
214 y += dirY;
215
216 canvas.clear(CanvasKit.WHITE);
217 const rr = CanvasKit.RRectXY(CanvasKit.LTRBRect(x, y, x+w, y+h), 25, 15);
218 canvas.drawRRect(rr, paint);
219 surface.requestAnimationFrame(drawFrame);
220 }
221 surface.requestAnimationFrame(drawFrame);
222 });
223</script>
224
225The main difference here is that we define a function to be called before each frame is drawn and
226pass it to `surface.requestAnimationFrame(drawFrame);` That callback is handed a `canvas` and
227flushing is taken care of.
228
229<!--?prettify?-->
230``` js
231function drawFrame(canvas) {
232 canvas.clear(CanvasKit.WHITE);
233 // code to update and draw the frame goes here
234 surface.requestAnimationFrame(drawFrame);
235}
236surface.requestAnimationFrame(drawFrame);
237```
238
239Creates a function to serve as our main drawing loop. Each time a frame is about to be rendered
240(the browser will typically target 60fps), our function is called, we clear the canvas with white,
241redraw the round rect, and call `surface.requestAnimationFrame(drawFrame)` registering the
242function to be called again before the next frame.
243
244`surface.requestAnimationFrame(drawFrame)` combines window.requestAnimationFrame with
245`surface.flush()` and should be used in all the same ways. If your application would only make
246visible changes as a result of mouse events,
247don't call `surface.requestAnimationFrame` at the end of your drawFrame function. Call it only
248after handling mouse input.
249
250Text Shaping
251------------
252
253One of the biggest features that CanvasKit offers over the HTML Canvas API is paragraph shaping.
254To use text your applicatoin, supply a font file and use Promise.all to run your code when both
255CanvasKit and the font file are ready.
256
257<!--?prettify?-->
258``` js
259const loadFont = fetch('https://storage.googleapis.com/skia-cdn/misc/Roboto-Regular.ttf')
260 .then((response) => response.arrayBuffer());
261
262Promise.all([ckLoaded, loadFont]).then(([CanvasKit, robotoData]) => {
263 const surface = CanvasKit.MakeCanvasSurface('foo3');
264 const canvas = surface.getCanvas();
265 canvas.clear(CanvasKit.Color4f(0.9, 0.9, 0.9, 1.0));
266
267 const fontMgr = CanvasKit.FontMgr.FromData([robotoData]);
268 const paraStyle = new CanvasKit.ParagraphStyle({
269 textStyle: {
270 color: CanvasKit.BLACK,
271 fontFamilies: ['Roboto'],
272 fontSize: 28,
273 },
274 textAlign: CanvasKit.TextAlign.Left,
275 });
276 const text = 'Any sufficiently entrenched technology is indistinguishable from Javascript';
277 const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
278 builder.addText(text);
279 const paragraph = builder.build();
280 paragraph.layout(290); // width in pixels to use when wrapping text
281 canvas.drawParagraph(paragraph, 10, 10);
282 surface.flush();
283});
284```
285
286<canvas id=foo3 width=300 height=300></canvas>
287
288<script type="text/javascript">
289const loadFont = fetch('https://storage.googleapis.com/skia-cdn/misc/Roboto-Regular.ttf')
290 .then((response) => response.arrayBuffer());
291
292Promise.all([ckLoaded, loadFont]).then(([CanvasKit, robotoData]) => {
293 const surface = CanvasKit.MakeCanvasSurface('foo3');
294 const canvas = surface.getCanvas();
295 canvas.clear(CanvasKit.Color4f(0.9, 0.9, 0.9, 1.0));
296
297 const fontMgr = CanvasKit.FontMgr.FromData([robotoData]);
298 const paraStyle = new CanvasKit.ParagraphStyle({
299 textStyle: {
300 color: CanvasKit.BLACK,
301 fontFamilies: ['Roboto'],
302 fontSize: 28,
303 },
304 textAlign: CanvasKit.TextAlign.Left,
305 });
306 const text = 'Any sufficiently entrenched technology is indistinguishable from Javascript';
307 const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
308 builder.addText(text);
309 const paragraph = builder.build();
310 paragraph.layout(290); // width in pixels to use when wrapping text
311 canvas.drawParagraph(paragraph, 10, 10);
312 surface.flush();
313});
314</script>
315
316<!--?prettify?-->
317``` js
318const fontMgr = CanvasKit.FontMgr.FromData([robotoData]);
319```
320Creates an object that provides fonts by name to various text facilities in CanvasKit. You could
321load more than one font in this statement if needed.
322
323<!--?prettify?-->
324``` js
325const paraStyle = new CanvasKit.ParagraphStyle({
326 textStyle: {
327 color: CanvasKit.BLACK,
328 fontFamilies: ['Roboto'],
329 fontSize: 28,
330 },
331 textAlign: CanvasKit.TextAlign.Left,
332});
333```
334Specifies the style of the text. The font's name, Roboto, will be used to fetch it from the font
335manager. You can specify either (color) or (foregroundColor and backgroundColor) in order to have
336a highlight. For the full documentation of the API, check out the Typescript definitions in the
337`types/` subfolder of the npm package or in the
Eric Boren04fe2672021-09-27 10:15:39 -0400338[Skia repo](https://github.com/google/skia/tree/main/modules/canvaskit/npm_build/types).
Joe Gregorio02f72022021-03-27 10:12:45 -0400339
340<!--?prettify?-->
341``` js
342const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
343builder.addText(text);
344const paragraph = builder.build();
345```
346Next, we create a `ParagraphBuilder` with a style, add some text, and finalize it with `build()`.
347Alternatively, we could use multiple `TextStyle`s in one paragraph with
348
349<!--?prettify?-->
350``` js
351const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
352builder.addText(text1);
353const boldTextStyle = CanvasKit.TextStyle({
354 color: CanvasKit.BLACK,
355 fontFamilies: ['Roboto'],
356 fontSize: 28,
357 fontStyle: {'weight': CanvasKit.FontWeight.Bold},
358})
359builder.pushStyle(boldTextStyle);
360builder.addText(text2);
361builder.pop();
362builder.addText(text3);
363const paragraph = builder.build();
364```
365Finally, we *layout* the paragraph, meaning wrap the text to a particular width, and draw it to
366the canvas with
367
368<!--?prettify?-->
369``` js
370paragraph.layout(290); // width in pixels to use when wrapping text
371canvas.drawParagraph(paragraph, 10, 10); // (x, y) position of left top corner of paragraph.
372```
373