/ alienkitty_canvas.html
alienkitty_canvas.html
  1  <!DOCTYPE html>
  2  <html lang="en">
  3  <head>
  4      <meta charset="utf-8">
  5      <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  6  
  7      <title>Alien Kitty — Space.js</title>
  8  
  9      <link rel="preconnect" href="https://fonts.gstatic.com">
 10      <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
 11      <link rel="stylesheet" href="assets/css/style.css">
 12  
 13      <script type="module">
 14          import { AssetLoader, Interface, clearTween, degToRad, delayedCall, headsTails, randInt, ticker, tween } from './src/index.js';
 15  
 16          const assetLoader = new AssetLoader();
 17          const loadImage = path => assetLoader.loadImage(path);
 18  
 19          class AlienKittyCanvas extends Interface {
 20              constructor() {
 21                  super(null, 'canvas');
 22  
 23                  this.width = 90;
 24                  this.height = 86;
 25                  this.needsUpdate = false;
 26                  this.isLoaded = false;
 27  
 28                  this.init();
 29                  this.initCanvas();
 30              }
 31  
 32              init() {
 33                  this.css({
 34                      opacity: 0
 35                  });
 36              }
 37  
 38              initCanvas() {
 39                  this.context = this.element.getContext('2d');
 40              }
 41  
 42              async initImages() {
 43                  const [alienkitty, eyelid] = await Promise.all([
 44                      loadImage('assets/images/alienkitty.svg'),
 45                      loadImage('assets/images/alienkitty_eyelid.svg')
 46                  ]);
 47  
 48                  this.alienkitty = this.createCanvasObject(alienkitty, 90, 86);
 49                  this.eyelid1 = this.createCanvasObject(eyelid, 24, 14, { pX: 0.5, x: 35, y: 25, scaleX: 1.5, scaleY: 0.01 });
 50                  this.eyelid2 = this.createCanvasObject(eyelid, 24, 14, { x: 53, y: 26, scaleX: 1, scaleY: 0.01 });
 51  
 52                  this.isLoaded = true;
 53  
 54                  this.update();
 55              }
 56  
 57              createCanvasObject(image, width, height, {
 58                  x = 0,
 59                  y = 0,
 60                  pX = 0,
 61                  pY = 0,
 62                  rotation = 0,
 63                  scaleX = 1,
 64                  scaleY = 1,
 65                  scale = 1,
 66                  opacity = 1
 67              } = {}) {
 68                  return {
 69                      image,
 70                      width,
 71                      height,
 72                      x,
 73                      y,
 74                      pX: width * pX,
 75                      pY: height * pY,
 76                      rotation,
 77                      scaleX: scaleX * scale,
 78                      scaleY: scaleY * scale,
 79                      opacity
 80                  };
 81              }
 82  
 83              drawImage(object) {
 84                  const context = this.context;
 85  
 86                  context.save();
 87                  context.translate(object.x + object.pX, object.y + object.pY);
 88                  context.rotate(degToRad(object.rotation));
 89                  context.scale(object.scaleX, object.scaleY);
 90                  context.globalAlpha = object.opacity;
 91                  context.drawImage(object.image, -object.pX, -object.pY, object.width, object.height);
 92                  context.restore();
 93              }
 94  
 95              addListeners() {
 96                  ticker.add(this.onUpdate);
 97              }
 98  
 99              removeListeners() {
100                  ticker.remove(this.onUpdate);
101              }
102  
103              blink() {
104                  this.timeout = delayedCall(randInt(0, 10000), headsTails(this.onBlink1, this.onBlink2));
105              }
106  
107              // Event handlers
108  
109              onUpdate = () => {
110                  if (this.needsUpdate) {
111                      this.update();
112                  }
113              };
114  
115              onBlink1 = () => {
116                  this.needsUpdate = true;
117                  tween(this.eyelid1, { scaleY: 1.5 }, 120, 'easeOutCubic', () => {
118                      tween(this.eyelid1, { scaleY: 0.01 }, 180, 'easeOutCubic');
119                  });
120                  tween(this.eyelid2, { scaleX: 1.3, scaleY: 1.3 }, 120, 'easeOutCubic', () => {
121                      tween(this.eyelid2, { scaleX: 1, scaleY: 0.01 }, 180, 'easeOutCubic', () => {
122                          this.needsUpdate = false;
123                          this.blink();
124                      });
125                  });
126              };
127  
128              onBlink2 = () => {
129                  this.needsUpdate = true;
130                  tween(this.eyelid1, { scaleY: 1.5 }, 120, 'easeOutCubic', () => {
131                      tween(this.eyelid1, { scaleY: 0.01 }, 180, 'easeOutCubic');
132                  });
133                  tween(this.eyelid2, { scaleX: 1.3, scaleY: 1.3 }, 180, 'easeOutCubic', () => {
134                      tween(this.eyelid2, { scaleX: 1, scaleY: 0.01 }, 240, 'easeOutCubic', () => {
135                          this.needsUpdate = false;
136                          this.blink();
137                      });
138                  });
139              };
140  
141              // Public methods
142  
143              resize = () => {
144                  const dpr = window.devicePixelRatio;
145  
146                  this.element.width = Math.round(this.width * dpr);
147                  this.element.height = Math.round(this.height * dpr);
148                  this.element.style.width = `${this.width}px`;
149                  this.element.style.height = `${this.height}px`;
150                  this.context.scale(dpr, dpr);
151  
152                  if (this.isLoaded) {
153                      this.update();
154                  }
155              };
156  
157              update = () => {
158                  this.context.clearRect(0, 0, this.element.width, this.element.height);
159  
160                  this.drawImage(this.alienkitty);
161                  this.drawImage(this.eyelid1);
162                  this.drawImage(this.eyelid2);
163              };
164  
165              animateIn = () => {
166                  this.addListeners();
167                  this.resize();
168                  this.blink();
169  
170                  this.tween({ opacity: 1 }, 1000, 'easeOutSine');
171              };
172  
173              animateOut = callback => {
174                  this.tween({ opacity: 0 }, 500, 'easeInOutQuad', callback);
175              };
176  
177              ready = () => this.initImages();
178  
179              destroy = () => {
180                  this.removeListeners();
181  
182                  clearTween(this.timeout);
183                  clearTween(this.eyelid1);
184                  clearTween(this.eyelid2);
185  
186                  return super.destroy();
187              };
188          }
189  
190          class App {
191              static async init() {
192                  this.initView();
193  
194                  this.addListeners();
195  
196                  await this.view.ready();
197                  this.view.animateIn();
198              }
199  
200              static initView() {
201                  this.view = new AlienKittyCanvas();
202                  this.view.css({
203                      position: 'absolute',
204                      left: '50%',
205                      top: '50%',
206                      marginLeft: -this.view.width / 2,
207                      marginTop: -this.view.height / 2 - 65,
208                      cursor: 'pointer'
209                  });
210                  document.body.appendChild(this.view.element);
211              }
212  
213              static addListeners() {
214                  this.view.element.addEventListener('click', this.onClick);
215                  ticker.start();
216              }
217  
218              // Event handlers
219  
220              static onClick = () => {
221                  this.view.element.removeEventListener('click', this.onClick);
222  
223                  this.view.animateOut(() => {
224                      this.view = this.view.destroy();
225                  });
226              };
227          }
228  
229          App.init();
230      </script>
231  </head>
232  <body>
233  </body>
234  </html>