<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>LibCanvas :: Solar</title> <link href="/files/styles.css" rel="stylesheet" /> <style> html, body { background: url(im/sky.png) } </style> <script src="/files/js/atom.js"></script> <script src="/files/js/libcanvas.js"></script> </head> <body> <p><a href="/">Return to index</a></p> <script> new function () { LibCanvas.extract(); atom.dom(function () { new Solar.Controller(); }); }; </script> <script src="js/controller.js"></script> </body> </html>
/** @class Solar.Controller */ atom.declare('Solar.Controller', { initialize: function () { this.size = new Size(840, 840); this.app = new App({ size: this.size }); atom.ImagePreloader.run({ planets: 'im/planets.png', sun : 'im/sun.png' }, this.start.bind(this)); }, start: function (images) { // images ready this.app.resources.set( 'images', images ); } });
intersection: all
so that they all redrawn and invoke: true
so that each of them onUpdate
method
/** @class Solar.Controller */ // .. initialize: function () { // .. this.geoLayer = this.app.createLayer({ name: 'geo', invoke: true, intersection: 'all', zIndex: 2 }); // ..
App.Element
so that you can add to the application:
/** @class Solar.Sun */ atom.declare('Solar.Sun', App.Element, { renderTo: function (ctx, resources) { ctx.drawImage({ image : resources.get('images').get('sun'), center: this.shape.center }); } });
/** @class Solar.Controller */ // .. start: function (images) { // .. this.sun = new Solar.Sun(this.geoLayer, { shape: new Circle(this.app.rectangle.center, 50) }); // ..
/** @class Solar.Controller */ atom.declare('Solar.Controller', { names : 'Selene Mimas Ares Enceladus Tethys Dione Zeus Rhea Titan Janus Hyperion Iapetus' .split(' '), // .. start: function (images) { // .. for (var i = 12; i--;) { var planet = new Solar.Planet(this.geoLayer, { sun : this.sun, radius: 90 + i * 26, time : 40 + i * 20, image : i, zIndex: 0, name : this.names[i] }); } // ..
this.center = this.solarCenter.clone(); this.center.move([ this.radius, 0 ]);
'time'
in seconds or 360/1000 in time in milliseconds:
this.rotatePerMs = (360).degree() / 1000 / this.settings.get('time');
getImagePart: function () { var x = this.settings.get('image'); return this.layer.app.resources.get('images') .get('planets') .sprite(new Rectangle([ x*this.size.width,0 ],this.size)); },
LibCanvas.Point.rotate
method. normalizeAngle
needed so that the angle is always between 0 and 360 degrees.
rotate: function (angle) { if (angle == null) angle = Number.random(0, 360).degree(); this.angle = (this.angle + angle).normalizeAngle(); this.center.rotate(angle, this.solarCenter); return this; },
onUpdate
add interactivity - every call to the onUpdate
method onUpdate
planet, not forgetting to make an amendment for the time that has passed since the previous call. onUpdate
, like onUpdate
, are renderTo
built into the LibCanvas framework that can be overridden and changed behavior.
onUpdate: function (time) { this.rotate(time * this.rotatePerMs); this.redraw(); },
renderTo: function (ctx) { ctx.drawImage({ image : this.image, center: this.center, angle : this.angle }); }
/** @class Solar.Planet */ atom.declare('Solar.Planet', App.Element, { angle: 0, configure: function () { this.size = new Size(26, 26); this.center = this.solarCenter.clone(); this.center.move([ this.radius, 0 ]); this.rotatePerMs = (360).degree() / 1000 / this.settings.get('time'); this.shape = new Circle(this.center, this.size.width/2); this.image = this.getImagePart(); this.rotate(); }, getImagePart: function () { var x = this.settings.get('image'); return this.layer.app.resources.get('images') .get('planets') .sprite(new Rectangle([x*this.size.width,0],this.size)); }, get radius () { return this.settings.get('radius'); }, get solarCenter () { return this.settings.get('sun').shape.center; }, rotate: function (angle) { if (angle == null) angle = Number.random(0, 360).degree(); this.angle = (this.angle + angle).normalizeAngle(); this.center.rotate(angle, this.solarCenter); return this; }, onUpdate: function (time) { this.rotate(time * this.rotatePerMs); this.redraw(); }, renderTo: function (ctx) { ctx.drawImage({ image : this.image, center: this.center, angle : this.angle }); } });
onUpdate
call here, and we will manage the intersections manually. All the same, these intersections of objects with each other, as we see, especially and no. In addition, in order not to return, immediately add a method to create an orbit on the planet
/** @class Solar.Controller */ // .. initialize: function () { // .. this.orbitLayer = this.app.createLayer({ name: 'orbit', intersection: 'manual', zIndex: 1 }); // .. start: function (images) { // .. for (var i = 12; i--;) { var planet = new Solar.Planet(this.geoLayer, { // .. }); planet.createOrbit(this.orbitLayer, i); // <=== // .. } // ..
/** @class Solar.Planet */ // .. createOrbit: function (layer, z) { return this.orbit = new Solar.Orbit(layer, { planet: this, zIndex: z }); }, // ..
Circle
, which will be the basis of our drawing, in the renderTo
method renderTo
simply stroke this circle. The only thing that has now been added is the clearPrevious
method, which changes the principle of cleaning the layer from this object - we do not roughly clear the contents of the BoundingRectangle, but carefully draw the circle stroke using the inverted ctx.clear(this.shape, true)
:
/** @class Solar.Orbit */ atom.declare('Solar.Orbit', App.Element, { configure: function () { this.shape = new Circle(this.planet.solarCenter, this.planet.radius); }, get planet () { return this.settings.get('planet'); }, clearPrevious: function (ctx) { ctx.clear(this.shape, true); }, renderTo: function (ctx, resources) { ctx.stroke(this.shape, 'rgba(0,192,255,0.5)'); } });
LibCanvas.Mouse
object that catches mouse events of a specific dom element and a LibCanvas.App.MouseHandler
object that will process these events and redirect to the corresponding application element.
/** @class Solar.Controller */ // .. start: function (images) { var mouse, mouseHandler; mouse = new Mouse(this.app.container.bounds); mouseHandler = new App.MouseHandler({ mouse: mouse, app: this.app }); this.app.resources.set({ images: images, mouse : mouse, mouseHandler: mouseHandler }); // .. for (var i = 12; i--;) { var planet = new Solar.Planet(this.geoLayer, { // .. }); planet.createOrbit(this.orbitLayer, i); mouseHandler.subscribe( planet ); mouseHandler.subscribe( planet.orbit ); } } // ..
isTriggerPoint
method. Now mouse events will fire into orbit only within 13 pixels from it.
/** @class Solar.Orbit */ // .. isTriggerPoint: function (point) { var distance = this.planet.solarCenter.distanceTo(point); return (this.planet.radius - distance).abs() < 13; }, // ..
Clickable
behavior and slightly modify the rendering method:
/** @class Solar.Orbit */ // .. configure: function () { // .. new App.Clickable( this, this.redraw ).start(); }, // .. renderTo: function (ctx, resources) { if (this.hover) { ctx.stroke(this.shape, 'rgba(255,64,64,0.8)'); } else { ctx.stroke(this.shape, 'rgba(0,192,255,0.5)'); } } // ..
/** @class Solar.Orbit */ // .. isHover: function () { return this.hover || this.planet.hover; }, renderTo: function (ctx, resources) { if (this.isHover()) { // .. } // ..
redraw
to onUpdate
/** @class Solar.Planet */ // .. configure: function () { // .. new App.Clickable( this, this.redraw ).start(); }, // .. onUpdate: function (time) { // .. if (this.orbit.isHover()) this.orbit.redraw(); }, // ..
/** @class Solar.Orbit */ // .. renderTo: function (ctx, resources) { if (this.isHover()) { ctx.save(); ctx.set({ strokeStyle: 'rgb(0,192,255)', lineWidth: 3 }); ctx.stroke(this.shape); ctx.clear(this.planet.shape); ctx.stroke(this.planet.shape); ctx.restore(); } else { ctx.stroke(this.shape, 'rgba(0,192,255,0.5)'); } }
saveCurrentBoundingShape
, which will save exactly where the hernia was last, if any, at all. It will also be called when necessary automatically:
/** @class Solar.Orbit */ // .. saveCurrentBoundingShape: function () { if (this.isHover()) { this.previousBoundingShape = this.planet.shape.clone().grow(6); } else { this.previousBoundingShape = null; } return this; },
/** @class Solar.Orbit */ // .. clearPrevious: function (ctx) { if (this.previousBoundingShape) { ctx.save(); ctx.set({ lineWidth: 4 }); ctx.clear(this.previousBoundingShape); ctx.clear(this.shape, true); ctx.restore(); } else { ctx.clear(this.shape, true); } },
mouseout
on Solar.Planet
, but they only work when the mouse is moving, i.e. if the planet leaves the stationary mouse, the mouseout
will not work. Therefore, we take the point of the mouse and every frame we check if it is located above our planet.
/** @class Solar.Planet */ // .. configure: function () { // .. this.mousePoint = this.layer.app.resources.get('mouse').point; this.info = new Solar.Info(this.layer, { planet: this, zIndex: 1 }); }, checkStatus: function (visible) { if (this.info.isVisible() != visible) { this.info[visible ? 'show' : 'hide'](); } }, onUpdate: function (time) { // .. this.checkStatus(this.isTriggerPoint(this.mousePoint)); // if (this.info.isVisible()) this.info.updateShape(this.shape.center); }, // ..
settings: { hidden: true }
is one of the LibCanvas settings. This element will not participate in drawing in any way, but it still catches mouse events if it is signed. Therefore, we create a simple and logical class Solar.Info
using this feature.
/** @class Solar.Info */ atom.declare('Solar.Info', App.Element, { settings: { hidden: true }, get planet () { return this.settings.get('planet'); }, configure: function () { this.shape = new Rectangle(0,0,100,30); }, updateShape: function (from) { this.shape.moveTo(from).move([20,10]) }, show: function () { this.settings.set({ hidden: false }); this.redraw(); }, hide: function () { this.settings.set({ hidden: true }); this.redraw(); }, renderTo: function (ctx) { ctx.fill(this.shape, '#002244'); ctx.text({ to : this.shape, text : this.planet.settings.get('name'), color: '#0ff', align: 'center', optimize: false, padding: 3 }) } });
/** @class Solar.Planet */ // .. checkStatus: function (visible) { if (this.info.isVisible() != visible) { this.info[visible ? 'show' : 'hide'](); this.layer.dom.element.css('cursor', visible ? 'pointer' : 'default'); } }, // ..
(program)
- the better. On an empty application, it reaches 100%.
Source: https://habr.com/ru/post/163893/