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