blob: 0f7ac0cbfa9184dd27e6aedef66b34bd1fe95da6 [file] [log] [blame]
/**
* @module skottie-player-sk
* @description <h2><code>skottie-player-sk</code></h2>
*
* <p>
* Displays a CanvasKit-based Skottie animation and provides various controls.
* </p>
*
*/
import 'elements-sk/icon/pause-icon-sk'
import 'elements-sk/icon/play-arrow-icon-sk'
import 'elements-sk/spinner-sk'
import { html, render } from 'lit-html'
const CanvasKitInit = require('../../build/canvaskit/canvaskit.js');
const loadingTemplate = (ele) => html`
<div class=player-loading title="Loading animation and engine."
style='width: ${ele._config.width}px; height: ${ele._config.height}px;'>
<div>Loading</div>
<spinner-sk active></spinner-sk>
</div>`;
const runningTemplate = (ele) => html`
<div class=skottie-player-wrapper>
<canvas class=skottie-canvas id=skottie
width=${ele._config.width * window.devicePixelRatio}
height=${ele._config.height * window.devicePixelRatio}
style='width: ${ele._config.width}px; height: ${ele._config.height}px;'>
Your browser does not support the canvas tag.
</canvas>
<div class=skottie-player-controls ?hidden=${!ele._config.controls}>
<play-arrow-icon-sk @click=${ele._onPlay} ?hidden=${!ele._state.paused}></play-arrow-icon-sk>
<pause-icon-sk @click=${ele._onPause} ?hidden=${ele._state.paused}></pause-icon-sk>
</div>
</div>`;
window.customElements.define('skottie-player-sk', class extends HTMLElement {
constructor() {
super();
this._config = {
width: this.hasAttribute('width') ? this.getAttribute('width') : 256,
height: this.hasAttribute('height') ? this.getAttribute('height') : 256,
controls: (new URL(document.location)).searchParams.has('controls'),
};
this._engine = {
kit: null, // CanvasKit instance
context: null, // CK context.
animation: null, // Skottie Animation instance
surface: null, // SkSurface
canvas: null, // Cached SkCanvas (surface.getCanvas()).
};
this._state = {
loading: true,
paused: this.hasAttribute('paused'),
duration: 0, // Animation duration (ms).
timeOrigin: 0, // Animation start time (ms).
seekPoint: 0, // Normalized [0..1] animation progress.
};
}
connectedCallback() {
this._render();
}
initialize(config) {
this._config.width = config.width;
this._config.height = config.height;
if (this._engine.kit) {
return new Promise((resolve, reject) => {
this._initializeSkottie(config.lottie);
resolve();
});
}
this._render();
return new Promise((resolve, reject) => {
CanvasKitInit({
locateFile: (file) => '/static/'+file,
}).then((ck) => {
this._engine.kit = ck;
this._initializeSkottie(config.lottie);
resolve();
});
});
}
duration() {
return this._state.duration;
}
seek(t) {
this._state.seekPoint = t;
this._state.timeOrigin = (Date.now() - this._state.duration * t);
if (!this.isPlaying()) {
// Force-draw a static frame when paused.
this._drawFrame();
}
}
isPlaying() {
return !this._state.paused;
}
pause() {
if (this.isPlaying()) {
this._state.paused = true;
// Save the exact/current seek point at pause time.
this._updateSeekPoint();
}
}
play() {
if (!this.isPlaying()) {
this._state.paused = false;
// Shift timeOrigin to continue from where we paused.
this.seek(this._state.seekPoint);
this._drawFrame();
}
}
_initializeSkottie(lottieJSON) {
this._state.loading = false;
// Rebuild the surface only if needed.
if (!this._engine.surface ||
this._engine.surface.width != this._config.width ||
this._engine.surface.height != this._config.height) {
this._render();
this._engine.surface && this._engine.surface.delete();
this._engine.surface = this._engine.kit.MakeCanvasSurface('skottie');
if (!this._engine.surface) {
throw new Error('Could not make SkSurface.');
}
// We don't need to call .delete() on the canvas because
// the parent surface will do that for us.
this._engine.canvas = this._engine.surface.getCanvas();
this._engine.context = this._engine.kit.currentContext();
}
this._engine.animation && this._engine.animation.delete();
this._engine.animation = this._engine.kit.MakeAnimation(JSON.stringify(lottieJSON));
if (!this._engine.surface) {
throw new Error('Could not parse Lottie JSON.');
}
this._state.duration = this._engine.animation.duration() * 1000;
this.seek(0);
this._drawFrame();
}
_updateSeekPoint() {
this._state.seekPoint = ((Date.now() - this._state.timeOrigin) / this.duration()) % 1;
}
_drawFrame() {
if (!this._engine.animation || !this._engine.canvas) {
return;
}
// When paused, the progress is fully controlled externally.
if (this.isPlaying()) {
this._updateSeekPoint();
window.requestAnimationFrame(this._drawFrame.bind(this));
}
this._engine.animation.seek(this._state.seekPoint);
this._engine.kit.setCurrentContext(this._engine.context);
this._engine.animation.render(this._engine.canvas, {
fLeft: 0,
fTop: 0,
fRight: this._config.width * window.devicePixelRatio,
fBottom: this._config.height * window.devicePixelRatio });
this._engine.surface.flush();
}
_render() {
render(this._state.loading
? loadingTemplate(this)
: runningTemplate(this),
this, {eventContext: this});
}
_onPlay() {
this.play();
this._render();
}
_onPause() {
this.pause();
this._render();
}
});