preface
Canvas does not provide a method to add event listening for its internal elements, so if you want to make the elements in canvas respond to events, you need to implement it yourself. The implementation method is also very simple. First, obtain the coordinates of the mouse on the canvas, calculate which elements the current coordinates are inside, and then operate the elements accordingly. With custom events, we can add event listening effect for elements in canvas.
Custom event
In order to realize the custom events of javascript objects, we can create an object to manage events, which contains an internal object (used as a map, the event name as the attribute name, and the event handling function as the attribute value. Because there may be multiple event handling functions, we use the array to store the event handling function) to store related events. Then provide a function to trigger the event, and call the previously bound function by using the call method. Here is a code example:
(function () { cce.EventTarget = function () { this._listeners = {}; this.inBounds = false; }; cce.EventTarget.prototype = { constructor: cce.EventTarget, // Check whether an event is monitored hasListener: function (type) { if (this._listeners.hasOwnProperty(type)) { return true; } else { return false; } }, // Add listener function for event addListener: function (type, listener) { if (!this._listeners.hasOwnProperty(type)) { this._listeners[type] = []; } this._listeners[type].push(listener); cce.EventManager.addTarget(type, this); }, // Trigger event fire: function (type, event) { if (event == null || event.type == null) { return; } if (this._listeners[event.type] instanceof Array) { var listeners = this._listeners[event.type]; for (var i = 0, len = listeners.length; i < len; i++) { listeners[i].call(this, event); } } }, // If listener is null, all event listeners under the current event will be cleared removeListener: function (type, listener) { if (listener == null) { if (this._listeners.hasOwnProperty(type)) { this._listeners[type] = []; cce.EventManager.removeTarget(type, this); } } if (this._listeners[type] instanceof Array) { var listeners = this._listeners[type]; for (var i = 0, len = listeners.length; i < len; i++) { if (listeners[i] === listener) { listeners.splice(i, 1); if (listeners.length == 0) cce.EventManager.removeTarget(type, this); break; } } } } }; }());
In the above code, the EventManager is used to store all objects bound to event listening, so as to judge whether the mouse is inside an object later. If a custom object needs to add event listener, it only needs to inherit EventTarget.
Ordered array
When judging the element that triggers an event, you need to traverse all the elements bound to the event to judge whether the mouse position is inside the element. In order to reduce unnecessary comparisons, an ordered array is used here, which uses the minimum x value of the element area as the comparison value and is arranged in ascending order. If the minimum x value of an element area is greater than the x value of the mouse, there is no need to compare the elements after the element in the array. The specific implementation can be seen SortArray.js
Element parent
An abstract class is designed here as the parent class of all element objects. This class inherits EventTarget and defines three functions. All subclasses should implement these three functions. The specific code is as follows:
(function () { // Abstract class, which inherits the event handling class. All element objects should inherit this class // In order to realize object comparison, compareTo, comparePointX and hasPoint methods should be implemented when inheriting this class. cce.DisplayObject = function () { cce.EventTarget.call(this); this.canvas = null; this.context = null; }; cce.DisplayObject.prototype = Object.create(cce.EventTarget.prototype); cce.DisplayObject.prototype.constructor = cce.DisplayObject; // In the ordered array, the objects will be sorted according to the returned results of this method cce.DisplayObject.prototype.compareTo = function (target) { return null; }; // Compare the x value of the target point with the minimum x value of the current area, and use it in combination with the ordered array. If the x value of the point is less than the minimum x value of the current area, then the remaining x value in the ordered array // The minimum x value of the element will also be greater than the x value of the target point, and the comparison can be stopped. When judging events, first use this function to filter. cce.DisplayObject.prototype.comparePointX = function (point) { return null; }; // Judge whether the target point is in the current area cce.DisplayObject.prototype.hasPoint = function (point) { return false; }; }());
Event judgment
Taking mouse events as an example, here we implement three mouse events: mouseover, MouseMove and mouseout. First, add a mouseover event to the canvas. When the mouse moves on the canvas, it will always compare the current mouse position with the position of the element bound to the above three events. If the trigger conditions are met, call the fire method of the element to trigger the corresponding event. Here is the sample code:
_handleMouseMove: function (event, container) { // The container is passed in here for use_ windowToCanvas function var point = container._windowToCanvas(event.clientX, event.clientY); // Get the element object bound with mouseover, MouseMove and mouseout events var array = cce.EventManager.getTargets("mouse"); if (array != null) { array.search(point); // The element where the mouse is located var selectedElements = array.selectedElements; // Elements without mouse var unSelectedElements = array.unSelectedElements; selectedElements.forEach(function (ele) { if (ele.hasListener("mousemove")) { var event = new cce.Event(point.x, point.y, "mousemove", ele); ele.fire("mousemove", event); } // It was not in the area before, but now it is, indicating that the mouse has entered if (!ele.inBounds) { ele.inBounds = true; if (ele.hasListener("mouseover")) { var event = new cce.Event(point.x, point.y, "mouseover", ele); ele.fire("mouseover", event); } } }); unSelectedElements.forEach(function (ele) { // It was in the area before, but now it's gone, indicating that the mouse has left if (ele.inBounds) { ele.inBounds = false; if (ele.hasListener("mouseout")) { var event = new cce.Event(point.x, point.y, "mouseout", ele); ele.fire("mouseout", event); } } }); } }
other
Execute function now
Functions such as the following form are called immediate execution functions.
(function() { // code }());
The advantage of using the immediate execution function is that it limits the scope of variables, so that the variables defined in the immediate execution function will not pollute other scopes. For more detailed explanation, please see here
apply, call, bind
The use of these three functions is similar to the method in java reflection Invoke: the method, as a body, passes the object executing the method into the method as a parameter. The functions of apply and call are the same. They will be executed immediately after the call, but the forms of accepting parameters are different.
func.call(this, arg1, arg2); func.apply(this, [arg1, arg2])
bind will return the corresponding function, and it will not be executed immediately, so it will be easy to call later. Take the following example:
function aa() { console.log(111); console.log(this); } var bb = aa.bind(Math); bb();
For more detailed explanation, please see here
addEventListener parameter
If you need to pass parameters when adding event listening to an element, you can use the following method
var i = 1; aa.addEventListener("click", function() { bb(i); }, false);
Call the constructor of the parent class
Just use call
Child = function() { Parent.call(this); }
Object detection
Determine whether the object is null or undefined
Determine whether an object has a property
isPointInPath
Whether the point is inside a path in canvas can be used for polygon detection. However, the path used by isPointInPath is the last graph drawn. If there are multiple graphs that need to be judged, the previous graph path needs to be saved. When judging, the path needs to be reconstructed, but it does not need to be drawn, as shown below
this.context.save(); this.context.beginPath(); //console.log(this.points); this.context.moveTo(this.points[0].x, this.points[0].y); for (var i = 1; i < this.points.length; i++) { this.context.lineTo(this.points[i].x, this.points[i].y); } if (this.context.isPointInPath(target.x, target.y)) { isIn = true; } this.context.closePath(); this.context.restore();
Reference article: