| CanvasKit - Skia + WebAssembly |
| ============================== |
| |
| Skia now offers a WebAssembly build for easy deployment of our graphics APIs on |
| the web. |
| |
| CanvasKit provides a playground for testing new Canvas and SVG platform APIs, |
| enabling fast-paced development on the web platform. |
| It can also be used as a deployment mechanism for custom web apps requiring |
| cutting-edge features, like Skia's [Lottie |
| animation](https://skia.org/user/modules/skottie) support. |
| |
| |
| Features |
| -------- |
| |
| - WebGL context encapsulated as an SkSurface, allowing for direct drawing to |
| an HTML canvas |
| - Core set of Skia canvas/paint/path/text APIs available, see bindings |
| - Draws to a hardware-accelerated backend |
| - Security tested with Skia's fuzzers |
| |
| Samples |
| ------- |
| |
| <style> |
| #demo canvas { |
| border: 1px dashed #AAA; |
| margin: 2px; |
| } |
| |
| #patheffect, #ink { |
| width: 400px; |
| height: 400px; |
| } |
| |
| #sk_legos, #sk_drinks, #sk_party, #sk_onboarding { |
| width: 300px; |
| height: 300px; |
| } |
| |
| figure { |
| display: inline-block; |
| margin: 0; |
| } |
| |
| figcaption > a { |
| margin: 2px 10px; |
| } |
| |
| </style> |
| |
| <div id=demo> |
| <h3>An Interactive Path</h3> |
| <figure> |
| <canvas id=patheffect width=400 height=400></canvas> |
| <figcaption> |
| <a href="https://jsfiddle.skia.org/canvaskit/28004d8841e7e497013263598241a3c1edc21dc1cf87a679abba307f39fa5fe6" |
| target=_blank rel=noopener> |
| Star JSFiddle</a> |
| </figcaption> |
| </figure> |
| <figure> |
| <canvas id=ink width=400 height=400></canvas> |
| <figcaption> |
| <a href="https://jsfiddle.skia.org/canvaskit/43475699d6d7d3d7dad1004c29f84015752a6a6dee2bb90f2e891b53e31d45cc" |
| target=_blank rel=noopener> |
| Ink JSFiddle</a> |
| </figcaption> |
| </figure> |
| |
| <h3>Skottie (click for fiddles)</h3> |
| <a href="https://jsfiddle.skia.org/canvaskit/092690b273b41076d2f00f0d43d004893d6bb9992c387c0385efa8e6f6bc83d7" |
| target=_blank rel=noopener> |
| <canvas id=sk_legos width=300 height=300></canvas> |
| </a> |
| <a href="https://jsfiddle.skia.org/canvaskit/e7ac983d9859f89aff1b6d385190919202c2eb53d028a79992892cacceffd209" |
| target=_blank rel=noopener> |
| <canvas id=sk_drinks width=500 height=500></canvas> |
| </a> |
| <a href="https://jsfiddle.skia.org/canvaskit/0e06547181759731e7369d3e3613222a0826692f48c41b16504ed68d671583e1" |
| target=_blank rel=noopener> |
| <canvas id=sk_party width=500 height=500></canvas> |
| </a> |
| <a href="https://jsfiddle.skia.org/canvaskit/be3fc1c5c351e7f43cc2840033f80b44feb3475925264808f321bb9e2a21174a" |
| target=_blank rel=noopener> |
| <canvas id=sk_onboarding width=500 height=500></canvas> |
| </a> |
| |
| </div> |
| |
| <script type="text/javascript" charset="utf-8"> |
| (function() { |
| // Tries to load the WASM version if supported, shows error otherwise |
| let s = document.createElement('script'); |
| var locate_file = ''; |
| if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') { |
| console.log('WebAssembly is supported!'); |
| locate_file = 'https://unpkg.com/canvaskit-wasm@0.3.0/bin/'; |
| } else { |
| console.log('WebAssembly is not supported (yet) on this browser.'); |
| document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>"; |
| return; |
| } |
| s.src = locate_file + 'canvaskit.js'; |
| s.onload = () => { |
| var CanvasKit = null; |
| var legoJSON = null; |
| var drinksJSON = null; |
| var confettiJSON = null; |
| var onboardingJSON = null; |
| var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500}; |
| CanvasKitInit({ |
| locateFile: (file) => locate_file + file, |
| }).then((CK) => { |
| CanvasKit = CK; |
| DrawingExample(CanvasKit); |
| InkExample(CanvasKit); |
| // Set bounds to fix the 4:3 resolution of the legos |
| SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}); |
| // Re-size to fit |
| SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); |
| SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); |
| SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); |
| }); |
| |
| fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => { |
| resp.text().then((str) => { |
| legoJSON = str; |
| SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}); |
| }); |
| }); |
| |
| fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => { |
| resp.text().then((str) => { |
| drinksJSON = str; |
| SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); |
| }); |
| }); |
| |
| fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => { |
| resp.text().then((str) => { |
| confettiJSON = str; |
| SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); |
| }); |
| }); |
| |
| fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => { |
| resp.text().then((str) => { |
| onboardingJSON = str; |
| SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); |
| }); |
| }); |
| |
| function preventScrolling(canvas) { |
| canvas.addEventListener('touchmove', (e) => { |
| // Prevents touch events in the canvas from scrolling the canvas. |
| e.preventDefault(); |
| e.stopPropagation(); |
| }); |
| } |
| |
| function DrawingExample(CanvasKit) { |
| const surface = CanvasKit.MakeCanvasSurface('patheffect'); |
| if (!surface) { |
| console.log('Could not make surface'); |
| } |
| const context = CanvasKit.currentContext(); |
| |
| const canvas = surface.getCanvas(); |
| |
| const paint = new CanvasKit.SkPaint(); |
| |
| const textPaint = new CanvasKit.SkPaint(); |
| textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0)); |
| textPaint.setTextSize(30); |
| textPaint.setAntiAlias(true); |
| |
| let i = 0; |
| |
| let X = 200; |
| let Y = 200; |
| |
| function drawFrame() { |
| const path = starPath(CanvasKit, X, Y); |
| CanvasKit.setCurrentContext(context); |
| const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5); |
| i++; |
| |
| paint.setPathEffect(dpe); |
| paint.setStyle(CanvasKit.PaintStyle.Stroke); |
| paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30)); |
| paint.setAntiAlias(true); |
| paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)); |
| |
| canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)); |
| |
| canvas.drawPath(path, paint); |
| canvas.drawText('Try Clicking!', 10, 380, textPaint); |
| canvas.flush(); |
| dpe.delete(); |
| path.delete(); |
| window.requestAnimationFrame(drawFrame); |
| } |
| window.requestAnimationFrame(drawFrame); |
| |
| // Make animation interactive |
| let interact = (e) => { |
| if (!e.buttons) { |
| return; |
| } |
| X = e.offsetX; |
| Y = e.offsetY; |
| }; |
| document.getElementById('patheffect').addEventListener('pointermove', interact); |
| document.getElementById('patheffect').addEventListener('pointerdown', interact); |
| preventScrolling(document.getElementById('patheffect')); |
| |
| // A client would need to delete this if it didn't go on for ever. |
| //paint.delete(); |
| } |
| |
| function InkExample(CanvasKit) { |
| const surface = CanvasKit.MakeCanvasSurface('ink'); |
| if (!surface) { |
| console.log('Could not make surface'); |
| } |
| const context = CanvasKit.currentContext(); |
| |
| const canvas = surface.getCanvas(); |
| |
| let paint = new CanvasKit.SkPaint(); |
| paint.setAntiAlias(true); |
| paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); |
| paint.setStyle(CanvasKit.PaintStyle.Stroke); |
| paint.setStrokeWidth(4.0); |
| // This effect smooths out the drawn lines a bit. |
| paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50)); |
| |
| // Draw I N K |
| let path = new CanvasKit.SkPath(); |
| path.moveTo(80, 30); |
| path.lineTo(80, 80); |
| |
| path.moveTo(100, 80); |
| path.lineTo(100, 15); |
| path.lineTo(130, 95); |
| path.lineTo(130, 30); |
| |
| path.moveTo(150, 30); |
| path.lineTo(150, 80); |
| path.moveTo(170, 30); |
| path.lineTo(150, 55); |
| path.lineTo(170, 80); |
| |
| let paths = [path]; |
| let paints = [paint]; |
| |
| function drawFrame() { |
| CanvasKit.setCurrentContext(context); |
| |
| for (let i = 0; i < paints.length && i < paths.length; i++) { |
| canvas.drawPath(paths[i], paints[i]); |
| } |
| canvas.flush(); |
| |
| window.requestAnimationFrame(drawFrame); |
| } |
| |
| let hold = false; |
| let interact = (e) => { |
| let type = e.type; |
| if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) { |
| hold = false; |
| return; |
| } |
| if (hold) { |
| path.lineTo(e.offsetX, e.offsetY); |
| } else { |
| paint = paint.copy(); |
| paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2)); |
| paints.push(paint); |
| path = new CanvasKit.SkPath(); |
| paths.push(path); |
| path.moveTo(e.offsetX, e.offsetY); |
| } |
| hold = true; |
| }; |
| document.getElementById('ink').addEventListener('pointermove', interact); |
| document.getElementById('ink').addEventListener('pointerdown', interact); |
| document.getElementById('ink').addEventListener('lostpointercapture', interact); |
| document.getElementById('ink').addEventListener('pointerup', interact); |
| preventScrolling(document.getElementById('ink')); |
| window.requestAnimationFrame(drawFrame); |
| } |
| |
| function starPath(CanvasKit, X=128, Y=128, R=116) { |
| let p = new CanvasKit.SkPath(); |
| p.moveTo(X + R, Y); |
| for (let i = 1; i < 8; i++) { |
| let a = 2.6927937 * i; |
| p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a)); |
| } |
| return p; |
| } |
| |
| function SkottieExample(CanvasKit, id, jsonStr, bounds) { |
| if (!CanvasKit || !jsonStr) { |
| return; |
| } |
| const animation = CanvasKit.MakeAnimation(jsonStr); |
| const duration = animation.duration() * 1000; |
| const size = animation.size(); |
| let c = document.getElementById(id); |
| bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h}; |
| |
| const surface = CanvasKit.MakeCanvasSurface(id); |
| if (!surface) { |
| console.log('Could not make surface'); |
| } |
| const context = CanvasKit.currentContext(); |
| const canvas = surface.getCanvas(); |
| |
| let firstFrame = new Date().getTime(); |
| |
| function drawFrame() { |
| let now = new Date().getTime(); |
| let seek = ((now - firstFrame) / duration) % 1.0; |
| CanvasKit.setCurrentContext(context); |
| animation.seek(seek); |
| |
| animation.render(canvas, bounds); |
| canvas.flush(); |
| window.requestAnimationFrame(drawFrame); |
| } |
| window.requestAnimationFrame(drawFrame); |
| //animation.delete(); |
| } |
| } |
| document.head.appendChild(s); |
| })(); |
| </script> |
| |
| Lottie files courtesy of the lottiefiles.com community: |
| [Lego Loader](https://www.lottiefiles.com/410-lego-loader), |
| [I'm thirsty](https://www.lottiefiles.com/77-im-thirsty), |
| [Confetti](https://www.lottiefiles.com/1370-confetti), |
| [Onboarding](https://www.lottiefiles.com/1134-onboarding-1) |
| |
| Test server |
| ----------- |
| Test your code on our [CanvasKit Fiddle](https://jsfiddle.skia.org/canvaskit) |
| |
| Download |
| -------- |
| Get [CanvasKit on NPM](https://www.npmjs.com/package/canvaskit-wasm) |