• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1
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
338[Skia repo](https://github.com/google/skia/tree/main/modules/canvaskit/npm_build/types).
339
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
374