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

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

Общая идея: летающие бабочки на фоне красивой картинки и под весеннюю музыку. (Сразу признаюсь, у меня также была 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/>

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

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

Промежуточные примеры и финальная версия одним файлом -- https://narod.ru/disk/6923480001/March8.zip.html

 Source code was highlighted with Source Code Highlighter.