Как стать автором
Обновить
0
Microsoft
Microsoft — мировой лидер в области ПО и ИТ-услуг

Делаем поздравительную открытку к 8 марта на HTML5 и EaselJS

Время на прочтение 12 мин
Количество просмотров 25K


(картинка кликабельна и поздравительна)

Общая идея: летающие бабочки на фоне красивой картинки и под весеннюю музыку. (Сразу признаюсь, у меня также была Silverlight-версия открытки, которую я делал год назад. Урок анимации бабочек в Silverlight.)

Для отрисовки открытки, помимо стандартных средств CSS, будем использовать HTML5 Canvas и библиотеку анимации EaselJS.

Анимация бабочек



Мы начнем с предположения, что вы уже препарировали бабочек и у вас есть отдельные крылья в виде соответствующих картинок:



Шаг 1. Отрисовка крыльев

Давайте для начала нарисуем просто пару крыльев. Для отрисовки каждого из крыльев нам понадобится класс Bitmap, объекты которого мы будем создавать на основе загруженных изображений. Чтобы собрать крылья воедино, нам понадобится класс Container. Для отрисовки сцены в EaselJS существует класс Stage.

Загрузка изображений
Для загрузки изображений нужно воспользоваться стандартным Image, повесив на него соответствующие события:

// loading left wing
leftImg = new Image();
leftImg.src = leftSrc;
leftImg.onload = onWingImageLoaded;

// loading right wing
rightImg = new Image();
rightImg.src = rightSrc;
rightImg.onload = onWingImageLoaded;


(В идеале, надо также вешать события на ошибку в загрузке и ее корректно обрабатывать.)

Сборка бабочки
После того, как оба крыла будут загружены, необходимо собрать их в одном контейнере, которым можно будет оперировать как целым:

// create a new container for butterfly wings
var butterfly = new Container();
// create wings
var lWing = new Bitmap(leftImg);
var rWing = new Bitmap(rightImg);

// assemble a batterfly
butterfly.addChild(lWing);
butterfly.addChild(rWing);


Внутри контейнера для каждого из крыльев нужно подвинуть точку регистрации, относительно которой они будут позиционироваться и далее вращаться:

// change wings registration point inside container
lWing.regX = lWing.image.width;
lWing.regY = lWing.image.height / 2;
rWing.regX = 0;
rWing.regY = rWing.image.height / 2;


Размещение бабочки
Далее добавляем бабочку на сцену, размещаем в нужном месте и запускаем отрисовку сцены:

// add butterfly to stage
stage.addChild(butterfly);

// change batterfly position
butterfly.x = canvas.width * Math.random() | 0;
butterfly.y = canvas.height * Math.random() | 0;
      
// redraw stage
stage.update();


Общий код
var leftSrc = "../images/Cypres 1.png";
var rightSrc = "../images/Cypres 2.png";
var leftImg;
var rightImg;
var loadedWings = 0;

function init() {
  // create a new stage and point it at our canvas:
  canvas = document.getElementById("canvas");
  stage = new Stage(canvas);

  // loading left wing
  leftImg = new Image();
  leftImg.src = leftSrc;
  leftImg.onload = onWingImageLoaded;

  // loading right wing
  rightImg = new Image();
  rightImg.src = rightSrc;
  rightImg.onload = onWingImageLoaded;
}

function onWingImageLoaded(event) {
  loadedWings++;
  if (loadedWings == 2)
    onButterflyReady();
}

function onButterflyReady() {
  // create a new container for butterfly wings
  var butterfly = new Container();
  // create wings
  var lWing = new Bitmap(leftImg);
  var rWing = new Bitmap(rightImg);

  // assemble a batterfly
  butterfly.addChild(lWing);
  butterfly.addChild(rWing);

  // change wings registration point inside container
  lWing.regX = lWing.image.width;
  lWing.regY = lWing.image.height / 2;
  rWing.regX = 0;
  rWing.regY = rWing.image.height / 2;

      
  // add butterfly to stage
  stage.addChild(butterfly);

  // change batterfly position
  butterfly.x = canvas.width * Math.random() | 0;
  butterfly.y = canvas.height * Math.random() | 0;
      
  // redraw stage
  stage.update();
}




Готовый пример, можно найтит тут: пример 1.

Шаг 2. Машем крыльями

Таймер
Запускать анимации или последовательно происходящие события можно различными способами. В EaselJS можно подписаться на таймер обновления сцены и на каждом шаге вносить изменения. (Это похоже на цикл обновления состояния игры, в котором отрисовка сцены происходит по таймеру.)

Первым делом нужно подписаться на таймер (частоту срабатывания также можно устанавливать):

Ticker.addListener(window);


Далее нужно определить функцию tick в области видимости windows, внутри которой необходимо обновить состояние сцены и запустить отрисовку:

// update scene on each tick
function tick() {
  butterfly.move()
  stage.update();
}


Пошаговая анимация крыльев
Чтобы описать анимацию бабочки, нужно разбить все взмахи крыльев и перемещения на отдельные шаги. Но прежде, давайте зададим еще один параметр, определяющий начальное состояние бабочки, – начальный масштаб (в принципе, его можно и не задавать, если вы не планируете делать бабочек разного размера, тогда scale = 1.0):

// initial scale
butterfly.scale = butterfly.lWing.scaleX
  = butterfly.lWing.scaleY = butterfly.rWing.scaleX
  = butterfly.rWing.scaleY = 0.5 + 0.2 * Math.random();


Анимации крыльев хорошо делать через горизонтальное (по X) масшабирование, причем для периодичности можно применять тригонометрические функции.

Давайте зададим количество шагов анимации:

// animation steps
butterfly.step = butterfly.steps = 40 + 60 * Math.random();


Теперь осталось описать каждый взмах:

butterfly.move = function () {
  var wingAngle = (butterfly.steps - butterfly.step) / butterfly.steps * Math.PI;
  butterfly.lWing.scaleX = butterfly.rWing.scaleX
    = butterfly.scale * (0.4 + 0.6 * Math.abs(Math.cos(wingAngle * 4)));

  butterfly.step--;
}


Общий код
var leftSrc = "../images/Cypres 1.png";
var rightSrc = "../images/Cypres 2.png";
var leftImg;
var rightImg;
var loadedWings = 0;
var butterfly;
var butterflyLoaded = false;

function init() {
  // create a new stage and point it at our canvas:
  canvas = document.getElementById("canvas");
  stage = new Stage(canvas);

  // loading left wing
  leftImg = new Image();
  leftImg.src = leftSrc;
  leftImg.onload = onWingImageLoaded;

  // loading right wing
  rightImg = new Image();
  rightImg.src = rightSrc;
  rightImg.onload = onWingImageLoaded;

  // subscribe to Ticker
  Ticker.addListener(window);
}

function onWingImageLoaded(event) {
  loadedWings++;
  if (loadedWings == 2)
    onButterflyReady();
}

function onButterflyReady() {
  // create a new container for butterfly wings
  butterfly = new Container();
  // create wings
  butterfly.lWing = new Bitmap(leftImg);
  butterfly.rWing = new Bitmap(rightImg);

  // assemble a batterfly
  butterfly.addChild(butterfly.lWing);
  butterfly.addChild(butterfly.rWing);

  // change wings registration point inside container
  butterfly.lWing.regX = butterfly.lWing.image.width;
  butterfly.lWing.regY = butterfly.lWing.image.height / 2;
  butterfly.rWing.regX = 0;
  butterfly.rWing.regY = butterfly.rWing.image.height / 2;

      
  // add butterfly to stage
  stage.addChild(butterfly);

  // change batterfly position
  butterfly.x = canvas.width * Math.random() | 0;
  butterfly.y = canvas.height * Math.random() | 0;

  // initial rotation
  butterfly.lWing.rotation = butterfly.rWing.rotation
    = butterfly.angle = 360 * Math.random() | 0;

  // initial scale
  butterfly.scale = butterfly.lWing.scaleX
    = butterfly.lWing.scaleY = butterfly.rWing.scaleX
    = butterfly.rWing.scaleY = 0.5 + 0.2 * Math.random();

  // animation steps
  butterfly.step = butterfly.steps = 40 + 60 * Math.random();

  // move butterfly
  butterfly.move = function () {
    var wingAngle = (butterfly.steps - butterfly.step) / butterfly.steps * Math.PI;
    butterfly.lWing.scaleX = butterfly.rWing.scaleX
    = butterfly.scale * (0.4 + 0.6 * Math.abs(Math.cos(wingAngle * 4)));

    butterfly.step--;
  }

  butterflyLoaded = true;

  // redraw stage
  stage.update();
}    

// update scene on each tick
function tick() {
  if (butterflyLoaded && butterfly.step >= 0) {
    butterfly.move()
  }
  stage.update();
}




Готовый пример, можно найтит тут: пример 2.

Шаг 3. Перемещаем бабочку

Теперь, когда мы научили бабочку махать крыльями, давайте заставим ее перемещаться. Для этого нам нужно будем учитывать угол поворота и направление и скорость движения:

// initial rotation
butterfly.lWing.rotation = butterfly.rWing.rotation
  = butterfly.angle = 360 * Math.random() | 0;
// movement direction
butterfly.direction = 8 * Math.random() - 4.0;
// movement speed
butterfly.speed = 5 * Math.random() + 5;


Внутри функции butterfly.move осталось добавить изменение угла повората:

butterfly.angle += butterfly.direction + (5.0 * Math.random() - 2.5);
butterfly.lWing.rotation = butterfly.rWing.rotation = butterfly.angle;


и прирост перемещения:

butterfly.x += (butterfly.speed * Math.sin((butterfly.angle) / 180.0 * Math.PI));
butterfly.y -= (butterfly.speed * Math.cos((butterfly.angle) / 180.0 * Math.PI));


Бабочка должна полететь.



Готовый пример, можно найтит тут: пример 3.

Шаг 4. Оформляем код

Так как дальше нам нужно будет обрабатывать целую коллекцию бабочек, то логично выделить код с описанием работы бабочки в отдельный блок (синим также выделена добавленная реакция на мышь):

(function (window) {

  Butterfly = function (leftImg, rightImg) {
    this.initialize();
    this.initButterfly(leftImg, rightImg);
  }

  var p = Butterfly.prototype = new Container();

  p.lWing = null;
  p.rWing = null;
  p.angle = 0;
  p.direction = 0;
  p.speed = 0;

  p.steps = 0;
  p.step = -1;

  p.initButterfly = function (leftImg, rightImg) {
    // create wings
    this.lWing = new Bitmap(leftImg);
    this.rWing = new Bitmap(rightImg);

    // change wings registration point inside container
    this.lWing.regX = this.lWing.image.width;
    this.lWing.regY = this.lWing.image.height / 2;
    this.rWing.regX = 0;
    this.rWing.regY = this.rWing.image.height / 2;

    // initial rotation
    this.lWing.rotation = this.rWing.rotation = this.angle = 360 * Math.random() | 0;
        
    // initial scale
    this.scale = this.lWing.scaleX = this.lWing.scaleY = this.rWing.scaleX = this.rWing.scaleY = 0.5 + 0.2 * Math.random();

    // assembling batterfly
    this.addChild(this.lWing);
    this.addChild(this.rWing);

    this.mouseEnabled = true;
    this.onMouseOver = function (e) { this.reset(); };
  };

  p.reset = function () {
    // animation steps
    this.step = this.steps = 40 + 60 * Math.random();
    // movement direction
    this.direction = 8 * Math.random() - 4.0;
    // movement speed
    this.speed = 5 * Math.random() + 5;
  };

  p.move = function () {
    // update rotation
    this.angle += this.direction + (5.0 * Math.random() - 2.5);
    this.lWing.rotation = this.rWing.rotation = this.angle;

    // update wings
    var wingAngle = (this.steps - this.step) / this.steps * Math.PI;
    this.lWing.scaleX = this.rWing.scaleX = this.scale * (0.4 + 0.6 * Math.abs(Math.cos(wingAngle * 4)));

    // update position
    this.x += (this.speed * Math.sin((this.angle) / 180.0 * Math.PI));
    this.y -= (this.speed * Math.cos((this.angle) / 180.0 * Math.PI));
    this.step--;
  };

  p.isActive = function () {
    return this.step >= 0;
  };

  window.Butterfly = Butterfly;
} (window));


Оставшийся кусок кода немного упрощается, также нужно разрешить отслеживание событий от мыши:

var leftSrc = "../images/Cypres 1.png";
var rightSrc = "../images/Cypres 2.png";
var leftImg;
var rightImg;
var loadedWings = 0;
var butterfly;
var butterflyLoaded = false;

function init() {
  // create a new stage and point it at our canvas:
  canvas = document.getElementById("canvas");
  stage = new Stage(canvas);
  stage.enableMouseOver(10);

  // loading left wing
  leftImg = new Image();
  leftImg.src = leftSrc;
  leftImg.onload = onWingImageLoaded;

  // loading right wing
  rightImg = new Image();
  rightImg.src = rightSrc;
  rightImg.onload = onWingImageLoaded;

  // subscribe to Ticker
  Ticker.addListener(window);
}

function onWingImageLoaded(event) {
  loadedWings++;
  if (loadedWings == 2)
    onButterflyImagesReady();
}

function onButterflyImagesReady() {
  // create a new container for butterfly wings
  var butterfly = new Butterfly(leftImg, rightImg);
      
  // add butterfly to stage
  stage.addChild(butterfly);

  // change batterfly position
  butterfly.x = canvas.width * Math.random() | 0;
  butterfly.y = canvas.height * Math.random() | 0;
      
  butterfly.reset();
  butterflyLoaded = true;

  // redraw stage
  stage.update();
}    

// update scene on each tick
function tick() {
  if (butterflyLoaded && butterfly.step >= 0) {
    butterfly.move()
  }
  stage.update();
}


Готовый пример, можно найтит тут: пример 4.

Много-много бабочек


Теперь давайте добавим побольше бабочек. Нам понадобится массив адресов изображений крыльев:

var bfimgsrc = [{ left: "../images/Didius 1.png", right: "../images/Didius 2.png" },
{ left: "../images/Amphitrion 1.png", right: "../images/Amphitrion 2.png" },
{ left: "../images/Catenarius 1.png", right: "../images/Catenarius 2.png" },
{ left: "../images/Cyanides 1.png", right: "../images/Cyanides 2.png" },
{ left: "../images/Cypres 1.png", right: "../images/Cypres 2.png" },
{ left: "../images/Diana 1.png", right: "../images/Diana 2.png" },
{ left: "../images/Hecuba 1.png", right: "../images/Hecuba 2.png" },
{ left: "../images/Peleides 1.png", right: "../images/Peleides 2.png" },
{ left: "../images/Polyphemus 1.png", right: "../images/Polyphemus 2.png" },
{ left: "../images/Sulkowski 1.png", right: "../images/Sulkowski 2.png"}];


Создание бабочек вынесем в отдельную функцию loadButterflyis и будем запускать еще бабочек по клику мыши:

function init() {
  // create a new stage and point it at our canvas:
  canvas = document.getElementById("canvas");
  stage = new Stage(canvas);
  stage.enableMouseOver(10);

  loadButterflyis(18);

  canvas.onclick = function () {
    loadButterflyis(5);
  };

  // subscribe to Ticker
  Ticker.addListener(window);
}
    
function loadButterflyis(count) {
  for (var k = 0; k < count; k++) {
    var i = Math.floor(bfimgsrc.length * Math.random());

    var bfimages = {
      left: new Image(),
      right: new Image(),
      loadedWings: 0,
      onready: onButterflyImagesReady
    };

    // loading left wing
    bfimages.left.src = bfimgsrc[i].left;
    bfimages.left.onload = onWingImageLoaded;
    bfimages.left.butterfly = bfimages;

    // loading right wing
    bfimages.right.src = bfimgsrc[i].right;
    bfimages.right.onload = onWingImageLoaded;
    bfimages.right.butterfly = bfimages;
  }
}


Обновленный tick:

// update scene on each tick
function tick() {
  for (var i = 0; i < bfs.length; i++) {
    var butterfly = bfs[i];
    if (butterfly.isActive()) {
      butterfly.move();
    }
    else if (Math.random() > 0.999) {
      butterfly.reset();
    }
  }
  stage.update();
}




Готовый пример, можно найтит тут:пример 5.


Спецэффекты


Дополнительные эффекты будут довольно простыми. Тематичный фон:



И аудио на фоне:

<audio src="audio/ArrivalForest.mp3" autoplay loop/>

(Энтузиасты могул легко добавить поддержку других форматов аудио.)

Финальная версия также доступна.

Upd.


Решаю проблему с хостингом, выложил временно на тестовом паркинге и отдельно: промежуточные примеры и финальная версия одним файлом — narod.ru/disk/6923480001/March8.zip.html

Source code was highlighted with Source Code Highlighter.
Теги:
Хабы:
+48
Комментарии 47
Комментарии Комментарии 47

Публикации

Информация

Сайт
www.microsoft.com
Дата регистрации
Дата основания
Численность
Неизвестно
Местоположение
США

Истории