📜 ⬆️ ⬇️

Basics of LibCanvas - Theory



Good day. One of the most common questions about LibCanvas now is “Where to start?”. I agree that the threshold for entering this library is slightly higher than in simpler canvas libraries, so in this topic I will cover the basics of LibCanvas - basic concepts and principles, drawing primitive figures, mouse events, keyboards, animation, advanced context, behaviors. I will try to describe all this with a lot of examples and the most accessible language.

I hope the article will provide answers to the questions: What is LibCanvas? Why is it needed and what are its advantages? Where to begin?
')
In this article there will be only a theory, and how to apply this knowledge in practice is revealed in the next article.

General information


LibCanvas is a framework for working with Canvas and related technologies that can be used to develop games and other interactive applications.

It is built on the basis of AtomJS - a lightweight JavaScript framework, something similar to MooTools and jQuery. There is quite good English documentation AtomJS and, if you have used MooTools before, then you will just have to master AtomJS.

The latest version of LibCanvas can be obtained in the repository , on GitHub there are also a number of relevant examples from very simple to fairly complex. Many principles can be understood by studying these examples. There is Russian documentation , but many parts of LibCanvas are not covered in it yet. Over time, it will be filled and expanded. I hope someone will help me with the translation into English)

Core


All code is stored in the LibCanvas namespace. This is good because the library does not litter the global namespace. However, there is a drawback - a rather verbose syntax in the end:
var circle = new LibCanvas.Shapes.Circle( 100, 100, 50 ); var circle = new LibCanvas.Shapes.Circle( 222, 222, 22 ); 


This can be fixed using the static method LibCanvas.extract (). It globalizes LibCanvas itself so that you can use short class names in your application:
 LibCanvas.extract(); var circle = new Circle( 100, 100, 50 ); var circle = new Circle( 222, 222, 22 ); 


Another alternative is to use aliases:
 var Circle = LibCanvas.Shapes.Circle; var circle1 = new Circle( 100, 100, 50 ); var circle2 = new Circle( 222, 222, 22 ); 


LibCanvas.Context2D


There is an embedded LibCanvas context. It is very easy to call:
 var context = document.getElementsByTagName('canvas')[0].getContext('2d-libcanvas'); // or: var context = atom.dom('canvas').first.getContext('2d-libcanvas'); 


Please note that the original "2d" context is still available and not touched, so it can be safely used in your applications:
 var context = atom.dom('canvas').first.getContext('2d'); 


The '2d-libcanvas' context is backward compatible with the original context (all code written for the '2d' context will work in the context of '2d-libcanvas'), but it has the following advantages:
1. Chainable - all methods can be called as a chain. This method has become particularly popular with the advent of jQuery:
 context .set({ fillStyle: 'black', strokeStyle: 'red' }) . fillRect(20, 20, 40, 40) .strokeRect(50, 50, 100, 100); 


2. Named arguments - now you can pass not just a set of characters, but a hash:
 context.drawImage(img, 10, 15, 40, 45, 20, 25, 50, 55); // vs context.drawImage({ image: img, crop : [10, 15, 40, 45], draw : [20, 25, 50, 50] }); 


3. Figures - you can transfer figures, not numbers. This is especially convenient when you have a large application with objects created:
 //  : context.drawImage( image, rect.from.x, rect.from.y, rect.width, rect.height ); // vs context.drawImage( image, rect ); //      : context.save(); context.fillStyle = 'red'; context.fillRect( rect.from.x, rect.from.y, rect.width, rect.height ) context.restore(); // vs: context.fill( rect, 'red' ); 


4. Expansion API - there is a whole series of amenities. First, more convenient work with paths, text, pictures, transformations, etc:
 //      ,   : // original ctx: context.save(); context.translate(this.position.x, this.position.y); context.rotate(this.angle); context.translate(-this.image.width/2, -this.image.height/2); context.drawImage(this.image, 0, 0); context.restore(); // vs context.drawImage({ image : this.image, center: this.position, angle : this.angle }); // : context.text({ text: 'Test string \n with line breaks \n is here' padding: [ 30, 50 ], size: 20, align: 'center' }) //    : context.translate( point.x, point.y); context.rotate(angle); context.translate(-point.x, -point.y); // vs: context.rotate( angle, point ); //   context.beginPath( ); context.moveTo( mt.x, mt.y ); context.lineTo( lt.x, lt.y ); context.bezierCurveTo( bc1.x, bc1.y, bc2.x, bc2.y, bc.x, bc.y ); context.quadraticCurveTo( qc1.x, qc1.y, qc.x, qc.y ); context.closePath(); // vs context .beginPath( mt ) .lineTo( lt ); .curveTo( bc, bc1, bc2 ) .curveTo( qc, qc1 ) .closePath(); //  : var circle = new Circle( 130, 120, 50 ); context.beginPath(); context.arc( circle.center.x, circle.center.y, circle.radius, 0, Math.PI * 2 ); context.closePath(); context.clip(); // vs: context.clip( circle ); //   : context.clear( 0, 0, canvas.width, canvas.height ); // vs context.clearAll(); 


And so on. I think you yourself can see the convenience of the inline context.

LibCanvas object


When constructing LibCanvas, a LibCanvas.Canvas2D object is created. The first argument is to pass a reference to the desired canvas element (css-selector, dom-object, etc). The second one can transfer additional settings - limit fps, cleaning before redrawing, preloading of pictures and others .
 var libcanvas = new LibCanvas('#my-canvas'); libcanvas instanceof LibCanvas; // true libcanvas instanceof LibCanvas.Canvas2D; // true //      : libcanvas.ctx instanceof LibCanvas.Context2D; // true 


Each frame consists of two stages. The first is data miscalculation. It is executed every time and is solely responsible for mathematical operations - the movement of objects, collisions, etc. There should be no redraw in this layer. The second stage is the render. It contains the part that is responsible for redrawing the contents of the screen and it will be executed only in case of any changes on the screen. This can be reported at the miscalculation stage by calling the libcanvas.update() method.

You can add a function to the rendering stage using the libcanvas.addRender() method, you can add a function to the render phase using the libcanvas.addRender() method. Also, at the render stage, the draw methods of the transferred objects are invoked. Approximately the code looks like this:

 libcanvas .addFunc(function () { scene.recount(); if (scene.somethingChanged()) { libcanvas.update(); } }) .addRender(function () { //      libcanvas.update(); scene.drawAll(); }); 


A lot of applications are static most of the time with redrawing only at the moments of user action. This will help to significantly reduce the unnecessary load on the processor.

In practice, addRender is rarely used, since It is very convenient to draw objects using the draw() method (see below).

Always redraw something on the screen only when changes are present. In many applications, such a basic mechanism will not be enough, but it is better than nothing.

Point


LibCanvas.Point is one of the basic objects. It is used very often, is a component of all the figures and is very convenient to use outside of them . It has methods for determining the distance between two points, the angle, the point multiplication, and also getting all the neighbors.

 //   A  60    B: var A = new Point(10, 10), B = new Point(20, 20); A.rotate( (60).degree(), B ); //        : var sum = 0 + matrix[py-1][px-1] + matrix[py-1][px] + matrix[py-1][p.x+1] + matrix[py ][px-1] + matrix[py ][p.x+1] + matrix[p.y+1][px-1] + matrix[p.y+1][px] + matrix[p.y+1][p.x+1] ; // vs var sum = point.neighbours.reduce(function(value, p) { return value + matrix[py][px]; }, 0); 


Figures


The figures are contained in the namespace LibCanvas.Shapes.* And globalize to short aliases. The most famous shapes are Rectangle , Circle , Line . When using LibCanvas, you must realize that the figures themselves do not have an appearance, they cannot have an appearance — color or shadow. An object that uses a shape, such as LibCanvas.Ui.Shaper, is responsible for the appearance, while the figures themselves contain only mathematical operations - how to pass the path, intersections, whether the point is inside the shape, etc. They are the astral, but not the physical body.

This allows you to separate the behavior from the appearance. For example, we have a board in Arkanoid. In fact, this is a picture, but we can perform all the actions as with a simple figure:

 var Unit = atom.Class({ initialize: function (rectangle, image) { this.shape = rectangle; this.image = image; }, collision: function (anotherUnit) { return this.shape.intersect( anotherUnit.shape ); }, draw: function () { this.libcanvas.ctx.drawImage( this.image, this.shape ); } }); 


Rectangle is the most important shape. It is used not only during drawing rectangles and basic mathematical operations, but also in many methods of LibCanvas. This could be, for example, the context.drawImage method, which takes arguments to cut and draw a rectangle or a tile engine, in which each element is a small Rectangle.

When a method needs a Rectangle-like argument, it can take any argument that looks like a rectangle. For example:
 context.drawImage({ image: image, crop: { from: { x: 15, y: 10 }, size: { width: 50, height: 100 } }, draw: [10,20,100,200] }); 


In this case, crop and draw will be brought inside to the Rectangle (or to another required shape), but from the point of view of performance (when repeatedly redrawing the canvas), as well as from the point of view of the application architecture, the most profitable method is to create all the objects during initialization applications. This decision was made specifically to promote good architecture.

 var Item = atom.Class({ initialize: function (image) { this.image = image; this.cropRect = new Rectangle(15, 10, 50, 100); this.drawRect = new Rectangle(10, 20, 100, 200); }, draw: function () { context.drawImage({ image: this.image, crop : this.cropRect, draw : this.drawRect }); } }); 


Other figures are similarly used:
 // : context.arc({ circle: new Circle( 100, 100, 50 ), angle : [ (45).degree(), (135).degree() ] }); //  : context.stroke( new Line([13, 13], [42, 42]), 'red' ); 


Behavior


The next part is LibCanvas.Behaviors.* . Each of them is just an admixture that adds to your class a certain functionality or behavior. For example, Animatable adds the animate method which allows you to change the properties of an object smoothly, and Drawable allows objects of your class to be added to the LibCanvas object for drawing.

By the way, Drawable is the basis for drawing in LibCanvas. A blend of Drawable and Shapes. * Allows you to draw any shape onto a canvas, and adding other behaviors will give this shape additional functionality.
 var Item = atom.Class({ Implements: [ Drawable, Draggable ], initialize: function (shape) { this.shape = shape; }, draw: function () { this.libcanvas.ctx.stroke( this.shape, 'red' ); } }); libcanvas.addElement( new Item( new Rectangle(50, 50, 100, 100) ).draggable() ); 


In fact, a similar pattern for drawing figures had to be created quite often, because Ui.Shaper already implemented:

 libcanvas.createShaper({ shape : new Rectangle(50, 50, 100, 100), stroke: 'red' }).draggable(); 


Klava and mouse


Working with the keyboard is quite simple. When initializing an application, it is enough to call the libcanvas.listenKeyboard() method and you can use the libcanvas.getKey( keyName ) method, if necessary, to find out the state of the key:

 update: function () { if( this.libcanvas.getKey('aup') ) { this.move(); } } 


Working with the mouse is worth parsing. Firstly, if you want to use the mouse in your application, be sure to call the libcanvas.listenMouse() method. In order to optimize, mouse events are not analyzed before it is called, because there are applications that do not need a mouse. After that, you can easily subscribe to mouse events by adding an element to the Mouse object:
 this.libcanvas.mouse.subscribe( element ); 


It is important that the value of the element's shape property is one of the shapes ( LibCanvas.Shapes.* ), The zIndex property is and it has implemented the class atom.Class.Events . In practice, all this is hidden behind the behaviors and when you call, for example, the draggable() method of the Draggable behavior, the object automatically subscribes to mouse events. If you only need to listen to mouse events, then it is sufficient to implement the MouseListener behavior and call the listenMouse method. However, the most important thing is still the element - the element must have the Shape property with some figure inside. When mouse events on your object are being listened to, you can subscribe to any of the following events:

 /* - click - mouseover - mousemove - mouseout - mouseup - mousedown - away:mouseover - away:mousemove - away:mouseout - away:mouseup - away:mousedown */ // : element .listenMouse() .addEvent('click', function () { alert('element clicked'); }); 


Conclusion


I described here the basics of the theoretical part of the development on LibCanvas. It does not reveal many interesting features and principles, but its goal is to explain the ideology and show the reader where to start.

The topic of the next article is a practical part of the development on LibCanvas .

Source: https://habr.com/ru/post/121046/


All Articles