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>An Interactive Path</h3> 55 <figure> 56 <canvas id=patheffect width=400 height=400></canvas> 57 <figcaption> 58 <a href="https://jsfiddle.skia.org/canvaskit/28004d8841e7e497013263598241a3c1edc21dc1cf87a679abba307f39fa5fe6" 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</div> 91 92<script type="text/javascript" charset="utf-8"> 93(function() { 94 // Tries to load the WASM version if supported, shows error otherwise 95 let s = document.createElement('script'); 96 var locate_file = ''; 97 if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') { 98 console.log('WebAssembly is supported!'); 99 locate_file = 'https://unpkg.com/canvaskit-wasm@0.3.0/bin/'; 100 } else { 101 console.log('WebAssembly is not supported (yet) on this browser.'); 102 document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>"; 103 return; 104 } 105 s.src = locate_file + 'canvaskit.js'; 106 s.onload = () => { 107 var CanvasKit = null; 108 var legoJSON = null; 109 var drinksJSON = null; 110 var confettiJSON = null; 111 var onboardingJSON = null; 112 var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500}; 113 CanvasKitInit({ 114 locateFile: (file) => locate_file + file, 115 }).then((CK) => { 116 CanvasKit = CK; 117 DrawingExample(CanvasKit); 118 InkExample(CanvasKit); 119 // Set bounds to fix the 4:3 resolution of the legos 120 SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}); 121 // Re-size to fit 122 SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); 123 SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); 124 SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); 125 }); 126 127 fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => { 128 resp.text().then((str) => { 129 legoJSON = str; 130 SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}); 131 }); 132 }); 133 134 fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => { 135 resp.text().then((str) => { 136 drinksJSON = str; 137 SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); 138 }); 139 }); 140 141 fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => { 142 resp.text().then((str) => { 143 confettiJSON = str; 144 SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); 145 }); 146 }); 147 148 fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => { 149 resp.text().then((str) => { 150 onboardingJSON = str; 151 SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); 152 }); 153 }); 154 155 function preventScrolling(canvas) { 156 canvas.addEventListener('touchmove', (e) => { 157 // Prevents touch events in the canvas from scrolling the canvas. 158 e.preventDefault(); 159 e.stopPropagation(); 160 }); 161 } 162 163 function DrawingExample(CanvasKit) { 164 const surface = CanvasKit.MakeCanvasSurface('patheffect'); 165 if (!surface) { 166 console.log('Could not make surface'); 167 } 168 const context = CanvasKit.currentContext(); 169 170 const canvas = surface.getCanvas(); 171 172 const paint = new CanvasKit.SkPaint(); 173 174 const textPaint = new CanvasKit.SkPaint(); 175 textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0)); 176 textPaint.setTextSize(30); 177 textPaint.setAntiAlias(true); 178 179 let i = 0; 180 181 let X = 200; 182 let Y = 200; 183 184 function drawFrame() { 185 const path = starPath(CanvasKit, X, Y); 186 CanvasKit.setCurrentContext(context); 187 const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5); 188 i++; 189 190 paint.setPathEffect(dpe); 191 paint.setStyle(CanvasKit.PaintStyle.Stroke); 192 paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30)); 193 paint.setAntiAlias(true); 194 paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)); 195 196 canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)); 197 198 canvas.drawPath(path, paint); 199 canvas.drawText('Try Clicking!', 10, 380, textPaint); 200 canvas.flush(); 201 dpe.delete(); 202 path.delete(); 203 window.requestAnimationFrame(drawFrame); 204 } 205 window.requestAnimationFrame(drawFrame); 206 207 // Make animation interactive 208 let interact = (e) => { 209 if (!e.buttons) { 210 return; 211 } 212 X = e.offsetX; 213 Y = e.offsetY; 214 }; 215 document.getElementById('patheffect').addEventListener('pointermove', interact); 216 document.getElementById('patheffect').addEventListener('pointerdown', interact); 217 preventScrolling(document.getElementById('patheffect')); 218 219 // A client would need to delete this if it didn't go on for ever. 220 //paint.delete(); 221 } 222 223 function InkExample(CanvasKit) { 224 const surface = CanvasKit.MakeCanvasSurface('ink'); 225 if (!surface) { 226 console.log('Could not make surface'); 227 } 228 const context = CanvasKit.currentContext(); 229 230 const canvas = surface.getCanvas(); 231 232 let paint = new CanvasKit.SkPaint(); 233 paint.setAntiAlias(true); 234 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 235 paint.setStyle(CanvasKit.PaintStyle.Stroke); 236 paint.setStrokeWidth(4.0); 237 // This effect smooths out the drawn lines a bit. 238 paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50)); 239 240 // Draw I N K 241 let path = new CanvasKit.SkPath(); 242 path.moveTo(80, 30); 243 path.lineTo(80, 80); 244 245 path.moveTo(100, 80); 246 path.lineTo(100, 15); 247 path.lineTo(130, 95); 248 path.lineTo(130, 30); 249 250 path.moveTo(150, 30); 251 path.lineTo(150, 80); 252 path.moveTo(170, 30); 253 path.lineTo(150, 55); 254 path.lineTo(170, 80); 255 256 let paths = [path]; 257 let paints = [paint]; 258 259 function drawFrame() { 260 CanvasKit.setCurrentContext(context); 261 262 for (let i = 0; i < paints.length && i < paths.length; i++) { 263 canvas.drawPath(paths[i], paints[i]); 264 } 265 canvas.flush(); 266 267 window.requestAnimationFrame(drawFrame); 268 } 269 270 let hold = false; 271 let interact = (e) => { 272 let type = e.type; 273 if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) { 274 hold = false; 275 return; 276 } 277 if (hold) { 278 path.lineTo(e.offsetX, e.offsetY); 279 } else { 280 paint = paint.copy(); 281 paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2)); 282 paints.push(paint); 283 path = new CanvasKit.SkPath(); 284 paths.push(path); 285 path.moveTo(e.offsetX, e.offsetY); 286 } 287 hold = true; 288 }; 289 document.getElementById('ink').addEventListener('pointermove', interact); 290 document.getElementById('ink').addEventListener('pointerdown', interact); 291 document.getElementById('ink').addEventListener('lostpointercapture', interact); 292 document.getElementById('ink').addEventListener('pointerup', interact); 293 preventScrolling(document.getElementById('ink')); 294 window.requestAnimationFrame(drawFrame); 295 } 296 297 function starPath(CanvasKit, X=128, Y=128, R=116) { 298 let p = new CanvasKit.SkPath(); 299 p.moveTo(X + R, Y); 300 for (let i = 1; i < 8; i++) { 301 let a = 2.6927937 * i; 302 p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a)); 303 } 304 return p; 305 } 306 307 function SkottieExample(CanvasKit, id, jsonStr, bounds) { 308 if (!CanvasKit || !jsonStr) { 309 return; 310 } 311 const animation = CanvasKit.MakeAnimation(jsonStr); 312 const duration = animation.duration() * 1000; 313 const size = animation.size(); 314 let c = document.getElementById(id); 315 bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h}; 316 317 const surface = CanvasKit.MakeCanvasSurface(id); 318 if (!surface) { 319 console.log('Could not make surface'); 320 } 321 const context = CanvasKit.currentContext(); 322 const canvas = surface.getCanvas(); 323 324 let firstFrame = new Date().getTime(); 325 326 function drawFrame() { 327 let now = new Date().getTime(); 328 let seek = ((now - firstFrame) / duration) % 1.0; 329 CanvasKit.setCurrentContext(context); 330 animation.seek(seek); 331 332 animation.render(canvas, bounds); 333 canvas.flush(); 334 window.requestAnimationFrame(drawFrame); 335 } 336 window.requestAnimationFrame(drawFrame); 337 //animation.delete(); 338 } 339 } 340 document.head.appendChild(s); 341})(); 342</script> 343 344Lottie files courtesy of the lottiefiles.com community: 345[Lego Loader](https://www.lottiefiles.com/410-lego-loader), 346[I'm thirsty](https://www.lottiefiles.com/77-im-thirsty), 347[Confetti](https://www.lottiefiles.com/1370-confetti), 348[Onboarding](https://www.lottiefiles.com/1134-onboarding-1) 349 350Test server 351----------- 352Test your code on our [CanvasKit Fiddle](https://jsfiddle.skia.org/canvaskit) 353 354Download 355-------- 356Get [CanvasKit on NPM](https://www.npmjs.com/package/canvaskit-wasm) 357