Донецкий техникум промышленной автоматики

Сонячна система на graphics2d.js

  1. Доброго {{timeOfDay}} Якось затихла тема canvas-а на Хабре ... Давайте згадаємо сонячну систему...
  2. JSFiddle з підключеним Graphics2D і плагінами .
  3. планети
  4. Орбіта
  5. Планета
  6. події
  7. Запуск!

Доброго {{timeOfDay}}

Якось затихла тема canvas-а на Хабре ...
Давайте згадаємо сонячну систему на ньому ( початок , LibCanvas , Fabric.js ) І напишемо ще одну версію? тепер на graphics2d.js .
Якось затихла тема canvas-а на Хабре

Про всяк випадок нагадаю ТЗ

Планети обертаються навколо зірки за годинниковою стрілкою. Швидкість обертання найближчій планети - 40 сек на оборот. Час на оборот кожної наступної планети на 20 секунд більше попередньої. Склад системи випадковий. При кожному оновленні планети займають випадкове місце на своїй орбіті, з якого і починають обертання і картинка для планети також випадкова.
При наведенні миші на планету вона виділяється круглої рамкою, зображення орбіти також змінюється так, як це вказано в макеті.
При кліці на планету випадає меню. При наведенні миші на планету і при кліці по ній анімація даної планети зупиняється, інші планети продовжують свій рух.
При наведенні миші на орбіту - орбіта підсвічується, планета немає.
Курсор миші при наведенні на планету або орбіту змінюється на pointer.
Повинно працювати: Opera 12+, IE9 +, актуальні версії Chrome, FF і Safari під огризок.


Примітка: спрайт і деякі магічні числа взяті з прикладу з LibCanvas, реалізація аналогічно трохи відрізняється від ТЗ.

відразу: Подивитися наживо ; Вихідні тексти (все в examples.js) .

починаємо

Нам знадобляться 2 плагіна: Layers і Sprites.

JSFiddle з підключеним Graphics2D і плагінами .

На першому шарі буде фон (зірки і Сонце) - він змінюватися не буде взагалі (можна замінити на статичну картинку, але якщо вже вирішили на canvas ..).
На другому - орбіти планет.
На третьому - планети. Вони змінюються кожну секунду, так що тільки вони і будуть перемальовуватись постійно, не зачіпаючи об'єкти на інших шарах.
Підказки з іменами планет також будуть з'являтися на третьому шарі. Причому саме створюватися при наведенні (ми ж не хочемо 24 додаткових ітерації кожну мілісекунди на невидимі об'єкти?).
Починаємо: оголосимо розміри і центр canvas-а і імена планет. А також масив planetarray.
var width = 840, height = 840, center = [width / 2, height / 2], planetNames = [ "Selene", "Mimas", "Ares", "Enceladus", "Tethys", "Dione", "Zeus "," Rhea "," Titan "," Janus "," Hyperion "," Iapetus "], planetarray = [];
Створимо об'єкт app (контейнер для шарів) з div-а і 3 шари - для фону + сонця, орбіт, планет і спливаючих підказок.
// <div id = "solarsystem"> </ div> var app = Graphics2D.app ( '# solarsystem', width, height), // розміри шарів background = app.layer (0), orbits = app.layer ( 1), planets = app.layer (2);
Заповнюємо перший шар - фон і сонце.
background.image ( 'images / sky.png', 0, 0); background.image ( 'images / sun.png', center [0] -50, center [1] -50); // 50,50 - половина розмірів сонця
На першому шарі буде фон (зірки і Сонце) - він змінюватися не буде взагалі (можна замінити на статичну картинку, але якщо вже вирішили на canvas

планети

Клас планети. У нього передається радіус, час обороту і ім'я планети. І поміщаємо кожну планету в planetarray.
function Planet (options) {// властивості this.radius = options.radius; this.rotatePerMs = 360/100 / options.time; this.time = options.time; this.name = options.name; // створення планети this.createOrbit (options); this.createPlanet (options); planetarray.push (this); }
Відразу все створимо:
for (var i = 0; i <12; i ++) {new Planet ({image: i, // індекс фрейму на спрайт з планетами (докладніше - далі) radius: 90 + i * 26, // магічні числа з прикладу з LibCanvas :) time: 40 + i * 20, name: planetNames [i]}); }
На цьому моменті браузер буде лаятися на відсутність createOrbit і createPlanet :) Далі.

Орбіта

Малюємо на шарі orbits (він під шаром з планетами). Для кожної орбіти, крім її самої, малюємо обведення планети (вона з'являється при наведенні і підсвічує саму планету, а не орбіту). Обведення буде обертатися разом з планетою, з'являючись лише при наведенні (згоден, не надто економно, але зате нескладно). І так, вона буде на шарі з планетами ().
Planet.prototype.createOrbit = function (options) {var orbit = orbits.circle ({cx: center [0], cy: center [1], radius: this.radius, stroke: '1px rgba (0,192,255,0.5)' }); var stroke = planets.circle ({cx: center [0] + this.radius, // поміщаємо в координати планети cy: center [1], radius: 15, fill: 'black', // перекриває лінію орбіти (інший спосіб - orbit.clip (stroke)). stroke: '3px rgba (0,192,255,1)', visible: false // спочатку обведення невидима}); // підключаємо оброблювач ні до кола-орбіті, а до примірника класу Planet orbit.mouseover (this.overOrbit.bind (this)). Mouseout (this.out.bind (this)); this.orbit = orbit; this.stroke = stroke; // створюємо випадковий кут, зберігаємо (щоб використовувати для самої планети) this.startAngle = rand (360); // повертаємо навколо сонця (центру canvas-а) stroke.rotate (this.startAngle, center); }
Функція rand Цілком елементарно, просто щоб не виникало недомовленостей.
function rand (num) {return Math.floor (Math.random () * num); }
Виникає цікаве питання - Circle обробляє події лише всередині себе, а нам потрібно ловити лише обведення. Можна розташувати орбіти один над одним в порядку зменшення, щоб кожна перекривала події наступного, але 1) имхо, трохи костильного варіант :) 2) при наведенні на сонці буде підсвічуватися найменша орбіта, а цього в ТЗ немає.
Рішення досить просто - Graphics2D використовує функцію об'єкта isPointIn, щоб зрозуміти, чи знаходиться курсор в об'єкті. Ми можемо просто її перевизначити:
orbit.isPointIn = function (x, y) {x - = center [0]; y - = center [1]; return (x * x + y * y) <= Math.pow (this._radius + 20, 2) && ((x * x + y * y)> Math.pow (this._radius - 20, 2)); }
Малюємо на шарі orbits (він під шаром з планетами)
(якщо тимчасово закомментировать виклик createPlanet, тому що її ще немає)

Планета

Спрайт з планетами виглядає так:
Спрайт з планетами виглядає так:   Розмір кожної планети 26,26, так що ми можемо створити спрайт, автоматично розбити його на фрейми і вибрати потрібний
Розмір кожної планети 26,26, так що ми можемо створити спрайт, автоматично розбити його на фрейми і вибрати потрібний.
Функція createPlanet - передається номер фрейма, радіус, час, ім'я:
Planet.prototype.createPlanet = function (options) {// спрайт, координати // просто ставимо планету в центр і зрушуємо на радіус по x var sprite = planets.sprite ( 'images / planets.png', center [0] - 13 + options.radius, center [1] - 13); // 13,13 - половини ширини і висоти фрейма sprite.autoslice (26, 26); // розбиваємо на фрейми sprite.frame (options.image); // вибираємо потрібний фрейм // ставимо обробники подій sprite.mouseover (this.overPlanet.bind (this)). Mouseout (this.out.bind (this)); sprite.click (this.click.bind (this)); sprite.cursor ( 'pointer'); this.sprite = sprite; // повертаємо на початковий випадковий кут sprite.rotate (this.startAngle, center);}}
Обробники подій також в прототипі класу Planet, їх чотири - overOrbit, overPlanet (також показує ім'я планети), out і click (відключає / включає анімацію).

події

Planet.prototype.overOrbit = function (e) {this.stroke.show (); // підсвічування планети this.orbit.stroke ( '3px rgba (0,192,255,1)'); // підсвічування орбіти} Planet.prototype.overPlanet = function (e) {this.stroke.show (); this.orbit.stroke ( '3px rgba (0,192,255,1)'); if (this.rect) {// через анімації mouseover може спрацювати кілька разів поспіль this.rect.remove (); this.text.remove (); } This.rect = planets.rect (e.contextX, e.contextY, 70, 25, 'rgb (0,56,100)', '1px rgb (0,30,50)'); this.text = planets.text ({text: this.name, // ім'я планети font: 'Arial 11pt', x: e.contextX + 35, // 35,12 - половини розмірів фону підказки, тобто центруємо напис y: e.contextY + 12, align: 'center', baseline: 'middle', fill: "rgba (0,192,255,1)"}); } Planet.prototype.out = function () {this.stroke.hide (); this.orbit.stroke ( '1px rgba (0,192,255,0.5)'); if (this.text) {this.text.remove (); this.rect.remove (); }} Planet.prototype.click = function () {if (this.rotatePerMs) {this.rotatePerMs = 0; // найпростіший спосіб - обнуляти швидкість, тому що таймаут буде один. } Else {this.rotatePerMs = 360/100 / this.time; }}
Planet

Запуск!

Для анімації додамо класу Planet функцію, яка буде спрацьовувати раз в 1 мс для кожної планети:
Planet.prototype.update = function () {this.sprite.rotate (this.rotatePerMs, center); this.stroke.rotate (this.rotatePerMs, center); }
Поїхали! :)
window.setInterval (function () {for (var i = 0; i <12; i ++) {planetarray [i] .update ();}}, 1);
Підсумок в JSFiddle

Ми ж не хочемо 24 додаткових ітерації кожну мілісекунди на невидимі об'єкти?