Web Animation

2D (pixi.js)

PixiJS is a rendering library that will allow you to create rich, interactive graphics, cross platform applications, and games without having to dive into the WebGL API or deal with browser and device compatibility.

image (load by pixi.js)

前景

前景

後景

後景

z-index

前景1

後景1

主圖層 (2D 圖)

前景n

後景n

  • opacity + pointer-event
  • transition
  • animation

約 500 張

可參照:

Why not use three.js ?

2D library (pixi.js) 3D library (three.js)
色彩、光線、陰影 依照圖片 自行校正
操作自由度 (視角) 差(依照圖片) 極高(自定義)
檔案大小 極大

Compared with

  • 操作自由度:使用情境不需要
  • 檔案大小:可優化

Tools

png 壓縮神器:

500張/月 (需帳號)

圖片無損壓縮:

無限制

png to jpg

Bulk resize images

也可 Google...

500 張共 973 MB (3600x1620 png)

Result:

Resize 1/2 for desktop

Resize 1/6 for mobile

Compressed by tinypng

 desktop: 62.4 MB (1800x810 png)

mobile: 10.2 MB (600x270 png)

Setup

via npm

There is no default export. The correct way to import PixiJS is:

import * as PIXI from 'pixi.js'
npm install pixi.js

Get Started

  • Application (Root Container)
  • Container
  • Loader
  • Sprite
  • AnimatedSprite

...

<RootContainer>
  <Container>
    <Sprite />
  </Container>
  <Container>
  
  </Container>
</RootContainer>
const app = new PIXI.Application({
   // settings
});

document.body.appendChild(app.view);
// app.view = canvas

const container = new PIXI.Container();

app.stage.addChild(container);
// app.stage = root container

What is Sprite ?

The Sprite object is the base for all textured objects that are rendered to the screen

Ex: image

In 3D world, a sprite is an image that always face to camera

Further more ...

What is Sprite ?

const sprite = PIXI.Sprite.from('assets/image.png');

or

PIXI.Loader.shared.add("assets/spritesheet.json").load(setup);

function setup() {
  let sheet = PIXI.Loader.shared.resources["assets/spritesheet.json"].spritesheet;
  let sprite = new PIXI.Sprite(sheet.textures["image.png"]);
  ...
}

AnimatedSprite

const alienImages = [
  "image_sequence_01.png",
  "image_sequence_02.png",
  "image_sequence_03.png",
  "image_sequence_04.png",
];

const textureArray = alienImages.forEach(imgName => (
  PIXI.Texture.from(imgName)
));

const animatedSprite = new PIXI.AnimatedSprite(textureArray);

A texture stores the information that represents an image or part of an image.

AnimatedSprite + Loader

const alienImages = [
  "image_sequence_01.png",
  "image_sequence_02.png",
  "image_sequence_03.png",
  "image_sequence_04.png",
];

const animateLoader = new PIXI.Loader();

alienImages.forEach((imgName, idx) => {
  animateLoader
    .add(`frame${idx + 1}`, imgName);
});

/* async */
animateLoader.load((loader, resources) => {
  const resourceKeys = Object.keys(resources);

  const textures = resourceKeys.map(key => res[key].texture);
  
  const animatedSprite = new PIXI.AnimatedSprite(textures);
});

AnimatedSprite + Loader

import img1 from './static/image_sequence_01.png';
import img2 from './static/image_sequence_02.png';
import img3 from './static/image_sequence_03.png';
import img4 from './static/image_sequence_04.png';

const alienImages = [img1, img2, img3, img4];

const animateLoader = new PIXI.Loader();

alienImages.forEach((imgName, idx) => {
  animateLoader
    .add(`frame${idx + 1}`, imgName);
});

/* async */
animateLoader.load((loader, resources) => {
  const resourceKeys = Object.keys(resources);

  const textures = resourceKeys.map(key => res[key].texture);
  
  const animatedSprite = new PIXI.AnimatedSprite(textures);
});

AnimatedSprite

import img1 from './static/image_sequence_01.png';
import img2 from './static/image_sequence_02.png';
import img3 from './static/image_sequence_03.png';
import img4 from './static/image_sequence_04.png';

const alienImages = [img1, img2, img3, img4];
const textures = alienImages.map(imgName => (
  PIXI.Texture.from(imgName)
));

const animatedSprite = new PIXI.AnimatedSprite(textures);

hmmm ... ?

Files compiled by webpack with hash filename

Already cached in memories ?

AnimatedSprite

import img1 from './static/image_sequence_01.png';
import img2 from './static/image_sequence_02.png';
import img3 from './static/image_sequence_03.png';
import img4 from './static/image_sequence_04.png';

const alienImages = [img1, img2, img3, img4];
const textureFragments = alienImages.map(imgName => ({
  texture: PIXI.Texture.from(imgName),
  time: 66.6, // ms (15fps)
}));

const animatedSprite = new PIXI.AnimatedSprite(textureFragments);

Each fragment (image) can define "time"

AnimatedSprite

animatedSprite.play();
animatedSprite.gotoAndPlay(0);
animatedSprite.gotoAndStop(0);
animatedSprite.stop();

commands:

Flow control

animatedSprite.onFrameChange = () => {};
animatedSprite.onComplete = () => {};

Event emitter

Flow control

export const ANIMATION_1_KEY = 'F/ANIMATION_1';
export const ANIMTION_2_KEY = 'F/ANIMATION_2';

export const animationScenes = [{
  key: ANIMATION_1_KEY,
  tab: tabs[0],
  startFrame: 1,
  endFrame: 30,
}, {
  key: ANIMATION_2_KEY,
  tab: tabs[1],
  startFrame: 31,
  endFrame: 50,
}];
animatedSprite.onComplete = () => {
  emitter.emit(
    CURRENT_ANIMATION_COMPLETED,
    KEY_NAME,
  );
};

image1 ~ image30

image31 ~ image50

Flow control

setTimeout ?

time (s)

無法保證時間是準確的,除非這秒數不用那麼精準

主動畫

onComplete

動畫1

動畫2

動畫3

動畫4

time (s)

主動畫

動畫1

動畫2

動畫3

動畫4

t

Moreover...

Your webGL may cause "Memory Leak"

webgl: context_lost_webgl: losecontext: context lost

Then, your browser will lost webGL context, unless you refresh your browser

Optimization (mobile)

z-index

前景1

後景1

主圖層 (2D 圖)

前景n

後景n

手機版只會照順序顯示

(不能從第1幕直接跳到第n幕)

許多幕也包含了大量圖片,所以儘可能減少元素同時 render 在 DOM 上

Optimization (mobile)

Scene Controller

8

7

6

5

4

3

2

1

8

7

6

5

4

3

2

1

(scene: 1)

return null

return null

Optimization (mobile)

Scene Controller

8

7

6

5

4

3

2

1

8

7

6

5

4

3

2

1

(scene: 2)

Optimization (mobile)

Scene Controller

8

7

6

5

4

3

2

1

8

7

6

5

4

3

2

1

(scene: 3)

Thanks

Web Animation

By Travor Lee

Web Animation

2D (pixi.js)

  • 216