1CanvasKit - Skia + WebAssembly 2============================== 3 4Skia now offers a WebAssembly build for easy deployment of our graphics APIs on 5the web. 6 7CanvasKit provides a playground for testing new Canvas and SVG platform APIs, 8enabling fast-paced development on the web platform. 9It can also be used as a deployment mechanism for custom web apps requiring 10cutting-edge features, like Skia's [Lottie 11animation](https://skia.org/user/modules/skottie) support. 12 13 14Features 15-------- 16 17 - WebGL context encapsulated as an SkSurface, allowing for direct drawing to 18 an HTML canvas 19 - Core set of Skia canvas/paint/path/text APIs available, see bindings 20 - Draws to a hardware-accelerated backend 21 - Security tested with Skia's fuzzers 22 23Samples 24------- 25 26<style> 27 #demo canvas { 28 border: 1px dashed #AAA; 29 margin: 2px; 30 } 31 32 #patheffect, #ink { 33 width: 400px; 34 height: 400px; 35 } 36 37 #sk_legos, #sk_drinks, #sk_party, #sk_onboarding { 38 width: 300px; 39 height: 300px; 40 } 41 42 figure { 43 display: inline-block; 44 margin: 0; 45 } 46 47 figcaption > a { 48 margin: 2px 10px; 49 } 50 51</style> 52 53<div id=demo> 54 <h3>Go beyond the HTML Canvas2D</h3> 55 <figure> 56 <canvas id=patheffect width=400 height=400></canvas> 57 <figcaption> 58 <a href="https://jsfiddle.skia.org/canvaskit/ea89749ae8c90bce807ea2e7e34fb7b09b950cee70d9db0a9cdfd2d67bd48ef0" 59 target=_blank rel=noopener> 60 Star JSFiddle</a> 61 </figcaption> 62 </figure> 63 <figure> 64 <canvas id=ink width=400 height=400></canvas> 65 <figcaption> 66 <a href="https://jsfiddle.skia.org/canvaskit/43475699d6d7d3d7dad1004c29f84015752a6a6dee2bb90f2e891b53e31d45cc" 67 target=_blank rel=noopener> 68 Ink JSFiddle</a> 69 </figcaption> 70 </figure> 71 72 <h3>Skottie (click for fiddles)</h3> 73 <a href="https://jsfiddle.skia.org/canvaskit/092690b273b41076d2f00f0d43d004893d6bb9992c387c0385efa8e6f6bc83d7" 74 target=_blank rel=noopener> 75 <canvas id=sk_legos width=300 height=300></canvas> 76 </a> 77 <a href="https://jsfiddle.skia.org/canvaskit/e7ac983d9859f89aff1b6d385190919202c2eb53d028a79992892cacceffd209" 78 target=_blank rel=noopener> 79 <canvas id=sk_drinks width=500 height=500></canvas> 80 </a> 81 <a href="https://jsfiddle.skia.org/canvaskit/0e06547181759731e7369d3e3613222a0826692f48c41b16504ed68d671583e1" 82 target=_blank rel=noopener> 83 <canvas id=sk_party width=500 height=500></canvas> 84 </a> 85 <a href="https://jsfiddle.skia.org/canvaskit/be3fc1c5c351e7f43cc2840033f80b44feb3475925264808f321bb9e2a21174a" 86 target=_blank rel=noopener> 87 <canvas id=sk_onboarding width=500 height=500></canvas> 88 </a> 89 90 <h3>Text Shaping using ICU and Harfbuzz</h3> 91 <figure> 92 <canvas id=shaping width=400 height=400></canvas> 93 <figcaption> 94 <a href="https://jsfiddle.skia.org/canvaskit/d5c61a106d57ff4ada119a46ddc3bfa479d343330d0c9e50da21f4ef113d0dee" 95 target=_blank rel=noopener> 96 Text Shaping JSFiddle</a> 97 </figcaption> 98 </figure> 99 100</div> 101 102<script type="text/javascript" charset="utf-8"> 103(function() { 104 // Tries to load the WASM version if supported, shows error otherwise 105 let s = document.createElement('script'); 106 var locate_file = ''; 107 if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') { 108 console.log('WebAssembly is supported!'); 109 locate_file = 'https://unpkg.com/canvaskit-wasm@0.4.0/bin/'; 110 } else { 111 console.log('WebAssembly is not supported (yet) on this browser.'); 112 document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>"; 113 return; 114 } 115 s.src = locate_file + 'canvaskit.js'; 116 s.onload = () => { 117 var CanvasKit = null; 118 var legoJSON = null; 119 var drinksJSON = null; 120 var confettiJSON = null; 121 var onboardingJSON = null; 122 var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500}; 123 CanvasKitInit({ 124 locateFile: (file) => locate_file + file, 125 }).ready().then((CK) => { 126 CanvasKit = CK; 127 DrawingExample(CanvasKit); 128 InkExample(CanvasKit); 129 ShapingExample(CanvasKit); 130 // Set bounds to fix the 4:3 resolution of the legos 131 SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}); 132 // Re-size to fit 133 SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); 134 SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); 135 SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); 136 }); 137 138 fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => { 139 resp.text().then((str) => { 140 legoJSON = str; 141 SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}); 142 }); 143 }); 144 145 fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => { 146 resp.text().then((str) => { 147 drinksJSON = str; 148 SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); 149 }); 150 }); 151 152 fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => { 153 resp.text().then((str) => { 154 confettiJSON = str; 155 SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); 156 }); 157 }); 158 159 fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => { 160 resp.text().then((str) => { 161 onboardingJSON = str; 162 SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); 163 }); 164 }); 165 166 function preventScrolling(canvas) { 167 canvas.addEventListener('touchmove', (e) => { 168 // Prevents touch events in the canvas from scrolling the canvas. 169 e.preventDefault(); 170 e.stopPropagation(); 171 }); 172 } 173 174 function DrawingExample(CanvasKit) { 175 const surface = CanvasKit.MakeCanvasSurface('patheffect'); 176 if (!surface) { 177 console.log('Could not make surface'); 178 } 179 const context = CanvasKit.currentContext(); 180 181 const canvas = surface.getCanvas(); 182 183 const paint = new CanvasKit.SkPaint(); 184 185 const textPaint = new CanvasKit.SkPaint(); 186 textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0)); 187 textPaint.setAntiAlias(true); 188 189 const textFont = new CanvasKit.SkFont(null, 30); 190 191 let i = 0; 192 193 let X = 200; 194 let Y = 200; 195 196 function drawFrame() { 197 const path = starPath(CanvasKit, X, Y); 198 CanvasKit.setCurrentContext(context); 199 const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5); 200 i++; 201 202 paint.setPathEffect(dpe); 203 paint.setStyle(CanvasKit.PaintStyle.Stroke); 204 paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30)); 205 paint.setAntiAlias(true); 206 paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)); 207 208 canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)); 209 210 canvas.drawPath(path, paint); 211 canvas.drawText('Try Clicking!', 10, 380, textPaint, textFont); 212 canvas.flush(); 213 dpe.delete(); 214 path.delete(); 215 window.requestAnimationFrame(drawFrame); 216 } 217 window.requestAnimationFrame(drawFrame); 218 219 // Make animation interactive 220 let interact = (e) => { 221 if (!e.buttons) { 222 return; 223 } 224 X = e.offsetX; 225 Y = e.offsetY; 226 }; 227 document.getElementById('patheffect').addEventListener('pointermove', interact); 228 document.getElementById('patheffect').addEventListener('pointerdown', interact); 229 preventScrolling(document.getElementById('patheffect')); 230 231 // A client would need to delete this if it didn't go on forever. 232 // font.delete(); 233 // paint.delete(); 234 } 235 236 function InkExample(CanvasKit) { 237 const surface = CanvasKit.MakeCanvasSurface('ink'); 238 if (!surface) { 239 console.log('Could not make surface'); 240 } 241 const context = CanvasKit.currentContext(); 242 243 const canvas = surface.getCanvas(); 244 245 let paint = new CanvasKit.SkPaint(); 246 paint.setAntiAlias(true); 247 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 248 paint.setStyle(CanvasKit.PaintStyle.Stroke); 249 paint.setStrokeWidth(4.0); 250 // This effect smooths out the drawn lines a bit. 251 paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50)); 252 253 // Draw I N K 254 let path = new CanvasKit.SkPath(); 255 path.moveTo(80, 30); 256 path.lineTo(80, 80); 257 258 path.moveTo(100, 80); 259 path.lineTo(100, 15); 260 path.lineTo(130, 95); 261 path.lineTo(130, 30); 262 263 path.moveTo(150, 30); 264 path.lineTo(150, 80); 265 path.moveTo(170, 30); 266 path.lineTo(150, 55); 267 path.lineTo(170, 80); 268 269 let paths = [path]; 270 let paints = [paint]; 271 272 function drawFrame() { 273 CanvasKit.setCurrentContext(context); 274 275 for (let i = 0; i < paints.length && i < paths.length; i++) { 276 canvas.drawPath(paths[i], paints[i]); 277 } 278 canvas.flush(); 279 280 window.requestAnimationFrame(drawFrame); 281 } 282 283 let hold = false; 284 let interact = (e) => { 285 let type = e.type; 286 if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) { 287 hold = false; 288 return; 289 } 290 if (hold) { 291 path.lineTo(e.offsetX, e.offsetY); 292 } else { 293 paint = paint.copy(); 294 paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2)); 295 paints.push(paint); 296 path = new CanvasKit.SkPath(); 297 paths.push(path); 298 path.moveTo(e.offsetX, e.offsetY); 299 } 300 hold = true; 301 }; 302 document.getElementById('ink').addEventListener('pointermove', interact); 303 document.getElementById('ink').addEventListener('pointerdown', interact); 304 document.getElementById('ink').addEventListener('lostpointercapture', interact); 305 document.getElementById('ink').addEventListener('pointerup', interact); 306 preventScrolling(document.getElementById('ink')); 307 window.requestAnimationFrame(drawFrame); 308 } 309 310 function ShapingExample(CanvasKit) { 311 const surface = CanvasKit.MakeCanvasSurface('shaping'); 312 if (!surface) { 313 console.log('Could not make surface'); 314 return; 315 } 316 const context = CanvasKit.currentContext(); 317 const skcanvas = surface.getCanvas(); 318 const paint = new CanvasKit.SkPaint(); 319 paint.setColor(CanvasKit.BLUE); 320 paint.setStyle(CanvasKit.PaintStyle.Stroke); 321 322 const textPaint = new CanvasKit.SkPaint(); 323 const bigFont = new CanvasKit.SkFont(null, 30); 324 const smallFont = new CanvasKit.SkFont(null, 14); 325 326 const TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ac leo vitae ipsum hendrerit euismod quis rutrum nibh. Quisque non suscipit urna. Donec enim urna, facilisis vitae volutpat in, mattis at elit. Sed quis augue et dolor dignissim fringilla. Sed non massa eu neque tristique malesuada. '; 327 328 let X = 280; 329 let Y = 190; 330 331 function drawFrame() { 332 CanvasKit.setCurrentContext(context); 333 skcanvas.clear(CanvasKit.TRANSPARENT); 334 335 const shapedText = new CanvasKit.ShapedText({ 336 font: smallFont, 337 leftToRight: true, 338 text: TEXT, 339 width: X - 10, 340 }); 341 342 skcanvas.drawRect(CanvasKit.LTRBRect(10, 10, X, Y), paint); 343 skcanvas.drawText(shapedText, 10, 10, textPaint, smallFont); 344 skcanvas.drawText('Try dragging the box!', 10, 380, textPaint, bigFont); 345 346 surface.flush(); 347 348 shapedText.delete(); 349 350 window.requestAnimationFrame(drawFrame); 351 } 352 window.requestAnimationFrame(drawFrame); 353 354 // Make animation interactive 355 let interact = (e) => { 356 if (!e.pressure) { 357 return; 358 } 359 X = e.offsetX; 360 Y = e.offsetY; 361 }; 362 document.getElementById('shaping').addEventListener('pointermove', interact); 363 document.getElementById('shaping').addEventListener('pointerdown', interact); 364 document.getElementById('shaping').addEventListener('lostpointercapture', interact); 365 document.getElementById('shaping').addEventListener('pointerup', interact); 366 preventScrolling(document.getElementById('shaping')); 367 window.requestAnimationFrame(drawFrame); 368 } 369 370 function starPath(CanvasKit, X=128, Y=128, R=116) { 371 let p = new CanvasKit.SkPath(); 372 p.moveTo(X + R, Y); 373 for (let i = 1; i < 8; i++) { 374 let a = 2.6927937 * i; 375 p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a)); 376 } 377 return p; 378 } 379 380 function SkottieExample(CanvasKit, id, jsonStr, bounds) { 381 if (!CanvasKit || !jsonStr) { 382 return; 383 } 384 const animation = CanvasKit.MakeAnimation(jsonStr); 385 const duration = animation.duration() * 1000; 386 const size = animation.size(); 387 let c = document.getElementById(id); 388 bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h}; 389 390 const surface = CanvasKit.MakeCanvasSurface(id); 391 if (!surface) { 392 console.log('Could not make surface'); 393 } 394 const context = CanvasKit.currentContext(); 395 const canvas = surface.getCanvas(); 396 397 let firstFrame = new Date().getTime(); 398 399 function drawFrame() { 400 let now = new Date().getTime(); 401 let seek = ((now - firstFrame) / duration) % 1.0; 402 CanvasKit.setCurrentContext(context); 403 animation.seek(seek); 404 405 animation.render(canvas, bounds); 406 canvas.flush(); 407 window.requestAnimationFrame(drawFrame); 408 } 409 window.requestAnimationFrame(drawFrame); 410 //animation.delete(); 411 } 412 } 413 document.head.appendChild(s); 414})(); 415</script> 416 417Lottie files courtesy of the lottiefiles.com community: 418[Lego Loader](https://www.lottiefiles.com/410-lego-loader), 419[I'm thirsty](https://www.lottiefiles.com/77-im-thirsty), 420[Confetti](https://www.lottiefiles.com/1370-confetti), 421[Onboarding](https://www.lottiefiles.com/1134-onboarding-1) 422 423Test server 424----------- 425Test your code on our [CanvasKit Fiddle](https://jsfiddle.skia.org/canvaskit) 426 427Download 428-------- 429Get [CanvasKit on NPM](https://www.npmjs.com/package/canvaskit-wasm) 430