Control multiple canvas animations - javascript

Is it possible to hook into a Canvas animation that has already been drawn?
I'm trying to create 3 seperate tracks that must be controlled by a "Start" and "Stop" button, but when pressing the Start button of the first canvas , it makes the last canvas run.
See current codepen progress
This is what I have so far.
class Animation{
constructor(){
this.options = {
left : 0,
animate : false
}
let tracks = $('.line');
let self = this;
tracks.each(function(){
self.startbtn = $(this).find('button.start');
self.stopbtn = $(this).find('button.stop');
self.canvas = $(this).find('canvas').get(0);
self.canvas.width = 1000;
self.canvas.height = 30;
self.ctx = self.canvas.getContext('2d');
self.draw(self.canvas,self.ctx);
self.startbtn.bind('click',() => {
self.options.animate = true;
self.draw(self.canvas,self.ctx);
})
});
}
draw(c,ctx){
ctx.clearRect(0,0,c.height,c.width);
ctx.fillRect(this.options.left,0,1,30);
if(this.options.animate){
requestAnimationFrame(() => {
console.log('done');
this.options.left += 1;
console.log(this.options.left)
this.draw(c,ctx);
});
}
}
}
new Animation();

Your code has scope issues and you had to insure that each track is operating separate from its constructor.
This is where i'd made sure information is entirely independent for the given track/parent element.
this.options = *set*;//left and animate separate for own process
this.node = *set*;//nodes for each track
this.ctx = *set*;//draw command context set at the parent
this.draw = *set*;//draw function moved to track
Here is complete edit to your code:
class Animation{
constructor(){
var tracks = $('.line');
var self = this;
tracks.each(function () {
this.options = {//options seperate to parent track node
left: 0,
animate: false
};
this.node = {//store child nodes for access
startbtn: $(this).find('button.start'),
stopbtn: $(this).find('button.stop'),
canvas: $(this).find('canvas').get(0)
};
this.node.canvas.width = 1000;
this.node.canvas.height = 30;
this.ctx = this.node.canvas.getContext('2d');
this.node.startbtn.bind('click',() => {// () => parentNode instantiation
this.options.animate = true;
this.draw(this.node.canvas, this.ctx);
});
this.draw = self.draw;
});
}
draw(c,ctx){
parent = c.parentNode;
ctx.clearRect(0,0,c.height,c.width);
ctx.fillRect(parent.options.left,0,1,30);
if(parent.options.animate){
requestAnimationFrame(() => {
console.log('done');
parent.options.left += 1;
console.log(parent.options.left)
parent.draw(c,ctx);
});
}
}
}
new Animation();
Scope and object handling needed to be done.
http://codepen.io/anon/pen/JKzBLK

Related

How to invoke a function when a class is instantiated

I have two classes, one of which is a subclass of the other. What I want to do is have one of the functions, step, invoked when the subclass is instantiated. This will cause the subclass to blink. I can see how to do it with functional instantiation but not with pseudoclassical. I've attached the jquery and javascript. Basically, when the button is clicked, I want the blinky dancer to blink, this should happen via the step function. Maybe I'm calling it incorrectly in the dancer superclass?
$(document).ready(function() {
window.dancers = [];
$('.addDancerButton').on('click', function(event) {
var dancerMakerFunctionName = $(this).data('dancer-maker-function-name');
console.log(dancerMakerFunctionName);
var dancerMakerFunction = window[dancerMakerFunctionName];
var dancer = new dancerMakerFunction(
$("body").height() * Math.random(),
$("body").width() * Math.random(),
Math.random() * 1000
);
$('body').append(dancer.$node);
});
});
var makeBlinkyDancer = function(top, left, timeBetweenSteps) {
makeDancer.call(this, top, left, timeBetweenSteps);
};
makeBlinkyDancer.prototype = Object.create(makeDancer.prototype);
makeBlinkyDancer.prototype.constructor = makeBlinkyDancer;
var oldStep = makeBlinkyDancer.prototype.step;
makeBlinkyDancer.prototype.step = function() {
oldStep();
this.$node.toggle();
};
var makeDancer = function(top, left, timeBetweenSteps) {
this.$node = $('<span class="dancer"></span>');
};
makeDancer.prototype.step = function() {
setTimeout(step.bind(this), timeBetweenSteps);
};
makeDancer.prototype.setPosition = function (top, left) {
var styleSettings = {
top: top,
left: left
};
this.$node.css(styleSettings);
};
// makeDancer.prototype.setPosition();
You can call the method when the subclass is instantiated by calling init in the constructor. In the example and demo I called this method makeBlink rather than step or oldStep. Hope that makes it clear.
// the subclass BlinkyDancer
var BlinkyDancer = function (top, left, height, width, timeBetweenSteps) {
// ... get props from parent class
// make blink when sublcass instantiated
this.init = this.makeBlink();
};
Full Demo/Solution
Demo: https://codesandbox.io/s/call-method-when-class-instantiated-mnesxt
HTML
<body>
<div id="app">
<button id="make-blink">Make Blink</button>
<button id="stop-blink">Stop Blink</button>
</div>
</body>
JS
import $ from "jquery";
var Dancer = function (top, left, height, width) {
this.$node = $('<div class="dancer"></div>');
this.top = top;
this.left = left;
this.height = height;
this.width = width;
this.bg = "blue";
};
Dancer.prototype.putDancerOnScreen = function () {
this.$node.height(this.height).width(this.width);
this.$node.css("background-color", "blue");
this.$node.css("position", "absolute");
$("body").append(this.$node);
};
Dancer.prototype.setPosition = function () {
this.$node.css("top", this.top);
this.$node.css("left", this.left);
};
var myDancer = new Dancer(
Math.floor(Math.random() * 500),
Math.floor(Math.random() * 500),
50,
50
);
myDancer.putDancerOnScreen();
myDancer.setPosition();
// the subclass BlinkyDancer
var BlinkyDancer = function (top, left, height, width, timeBetweenSteps) {
Dancer.call(this, top, left, height, width); // get the props set up by Dancer
this.timeBetweenSteps = timeBetweenSteps;
this.blinkerId = null;
// make blink when sublcass instantiated
this.init = this.makeBlink();
};
// set inheritance and Dancer constructor function
BlinkyDancer.prototype = Object.create(Dancer.prototype);
BlinkyDancer.prototype.constructor = Dancer;
// add subclass methods
BlinkyDancer.prototype.makeBlink = function () {
if (this.blinkerId !== null) {
return;
}
let count = 0;
let blinkerId = setInterval(() => {
// do whatever thing you want to indicate blinking/dancing
if (count % 2 === 0) {
this.$node.css("background-color", "red");
} else {
this.$node.css("background-color", "blue");
}
count++;
}, this.timeBetweenSteps);
this.blinkerId = blinkerId;
console.log("blinkder id set: ", this.blinkerId);
};
BlinkyDancer.prototype.stopBlink = function () {
if (this.blinkerId === null) {
// already blinking
return;
}
if (this.blinkerId !== null) {
clearInterval(this.blinkerId);
console.log("blink id cleared: ", this.blinkerId);
this.blinkerId = null;
}
};
// instantiate a new subclass
let myBlinkyDancer = new BlinkyDancer(
Math.floor(Math.random() * 500),
Math.floor(Math.random() * 500),
50,
50,
25
);
// use parent class methods to put the element on screen
myBlinkyDancer.putDancerOnScreen();
myBlinkyDancer.setPosition();
const makeBlinkButton = document.getElementById("make-blink");
const stopBlinkButton = document.getElementById("stop-blink");
makeBlinkButton.addEventListener("click", function () {
myBlinkyDancer.makeBlink();
});
stopBlinkButton.addEventListener("click", function () {
myBlinkyDancer.stopBlink();
});
Explanation
The Dancer class takes some props related to positioning and dimensions. It gets a blue background and has two methods for adding the element to the DOM and setting the position on screen.
BlinkyDancer is a subclass that inherits all the props of Dancer as well as two new props and two new methods.
What I want to do is have one of the functions, step, invoked when the
subclass is instantiated.
When a new BlinkyDancer is instantiated, we call makeBlink right away with init so the element starts blinking.
// make blink when sublcass instantiated
this.init = this.makeBlink();
I used an alternating background color to demonstrate the blinking effect.
The makeBlink and stopBlink methods work in tandem with the new props timeBetweenSteps and blinkerId.
when the button is clicked, I want the blinky dancer to blink, this
should happen via the step function.
Buttons are wired up to trigger starting and stopping the blinking effect. I've replaced step in your example with simple and declarative methods for making the blink and stopping it.
makeBlinkButton.addEventListener("click", function () {
myBlinkyDancer.makeBlink();
});
The blinking effect uses setInterval to toggle the background color using the timeBetweenSteps prop as the interval duration. startBlink starts the interval and sets blinkerId to the corresponding interval ID. To stop the blinking effect, clearInterval is passed the blinkerId to clear it (stopping the blinking effect) before resetting it to null.
BlinkyDancer.prototype.makeBlink = function () {
if (this.blinkerId !== null) {
return;
}
let count = 0;
let blinkerId = setInterval(() => {
// do whatever thing you want to indicate blinking/dancing
if (count % 2 === 0) {
this.$node.css("background-color", "red");
} else {
this.$node.css("background-color", "blue");
}
count++;
}, this.timeBetweenSteps);
this.blinkerId = blinkerId;
console.log("blinkder id set: ", this.blinkerId);
};
BlinkyDancer.prototype.stopBlink = function () {
if (this.blinkerId === null) {
// already blinking
return;
}
if (this.blinkerId !== null) {
clearInterval(this.blinkerId);
console.log("blink id cleared", this.blinkerId);
this.blinkerId = null;
}
};
Maybe I'm calling [step] incorrectly in the dancer superclass?
It looks like you were on the right track but missing parens to call the method. In your example code, rather than:
makeBlinkyDancer.prototype.step;
You would do:
makeBlinkyDancer.prototype.step();

JS Class instance communication

I'm new with js classes and I think I'm not doing it right.
I want to achieve a simple thing here. On one card click I want to hide all other cards. But how can I reach other cards if event is triggered from inside of one of the cards?
class Cards {
constructor(args){
this.list = [];
this.amm = args.amm;
this.createCards();
}
createCards(){
for(var i=0; i<this.amm; i++){
this.list.push( new Card( {id: i} ) );
}
}
}
class Card {
constructor(args){
this.id = args.id;
this.el = null;
this.createCard();
this.addEvents();
}
createCard(){
this.el = document.createElement("div");
this.el.style.width = "60px";
this.el.style.height = "100px";
this.el.style.backgroundColor = "red";
this.el.style.margin = "5px";
this.el.style.float = "left";
document.body.appendChild(this.el);
}
addEvents(){
let _this = this;
this.el.onclick = function(){
_this.el.style.opacity = 0.7;
_this.hideOtherCards(_this.id);
};
}
hideOtherCards(id){
// how to hide other cards?
}
}
var myCards = new Cards({amm: 5});
It's good practice (encapsulation) to keep the scope of any component limited to itself. That is, a card shouldn't know that, or how many, other cards exist. To keep the cards decoupled, a common way to achieve that is to make use of custom events.
Imagine it like this: A card that is clicked shouts into the room "I was clicked" and relies upon someone hearing that and reacting to that and for that instance to know what to do. If noone reacts, your code still won't throw an error.
For this to work in your scenario, you'd need a host element for the cards as events bubble up the DOM, but don't bubble to siblings.
Long story short, this is what I'd do:
Edit: Actually, the myCards class should be responsible for creating the host element, and listening to card-clicked.
class Cards {
constructor(args){
this.list = [];
this.el = null;
this.amm = args.amm;
this.createCardHost();
}
createCardHost() {
this.el = document.createElement('div');
this.createCards();
this.el.addEventListener('card-clicked', (e) => {
this.list.forEach(card => {card.id === e.detail.id ? card.el.style.opacity = 0.7 : card.el.style.opacity = 0.1})
})
for (const card of this.list) {
this.el.appendChild(card.el)
}
document.body.appendChild(this.el);
}
createCards(){
for(var i=0; i<this.amm; i++){
this.list.push( new Card( {id: i} ) );
}
}
}
class Card {
constructor(args){
this.id = args.id;
this.el = null;
this.createCard();
this.addEvents();
}
createCard(){
this.el = document.createElement("div");
this.el.style.width = "60px";
this.el.style.height = "100px";
this.el.style.backgroundColor = "red";
this.el.style.margin = "5px";
this.el.style.float = "left";
}
addEvents(){
this.el.addEventListener('click', () => {
this.el.style.opacity = 0.7;
// throw a 'card-clicked' event here
const cardClicked = new CustomEvent('card-clicked', { bubbles: true, cancelable: true, detail: { id: this.id }});
this.el.dispatchEvent(cardClicked);
});
}
}
var myCards = new Cards({amm: 5});

Give id to a canvas

I have it so that when you (the PaintBrush) hit the finish everything clears and a button appears. When the button is clicked it starts level two, here it creates a new canvas. I've added some code so that when the button is clicked the old canvas is deleted, then the new one is made. However, this requires the canvas to have an id. how do i get this code:
canvas: document.createElement("canvas"),
To also include an id for it? I cannot have it say var canvas = blah blah and then have
canvas.id = "canvas";
because of the way i have it set up.
P.S the remove code is this if you need to know:
Element.prototype.remove = function() {
this.parentElement.removeChild(this);
}
NodeList.prototype.remove = HTMLCollection.prototype.remove = function() {
for(var i = this.length - 1; i >= 0; i--) {
if(this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
}
and in the button "click" function area i put this for the remove:
document.getElementById("canvas").remove();
Please help! Thanks in Advance!
EDIT: Here is some extra code to help, it is what occurs when the PaintBrush hits the finish:
else if (PaintBrush.crashWith(Finish)) {
PaintBrush.y = 50;
var button = document.createElement("button");
button.innerHTML = "Level 3";
button.id = "button-2"; // add the id to the button
document.body.appendChild(button); // append to body
GameArena2.clear();
GameArena2.stop();
button.addEventListener ("click", function() {
startGame2();
document.getElement("canvas").remove();
document.getElementById("button-2").remove();
});
EDIT 2: Game canvas code:
var GameArena2 = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 1280;
this.canvas.height = 480;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea2, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
},
drawGameOver: function() {
ctx2 = GameArena2.context;
ctx2.fillStyle = "rgba(0,0,0,"+fader +")";
ctx2.fillRect(0,0,GameArena2.width,GameArena2.height);
drawStatic(fader/2);
fader += .1 * 1/fps
ctx2.fillStyle = "rgba(255,255,255," + fader + ")";
ctx2.font = "72px sans-serif";
ctx2.fillText("GAME OVER",GameArena2.width/2 - 220,GameArena2.height/2);
}
}
function everyinterval(n) {
if ((GameArena.frameNo / n) % 1 == 0) {
return true;
}
else if ((GameArena.frameNo / n) % 1 == 0) {
return true;
}
return false;
}
I think the best way is to create a static function like this:
function createElementOnDom (type,id) {
var element=document.createElement(type);
element.id=id;
return element;
}
console.log(createElementOnDom('CANVAS','myId'));
So you can simply create the canvas adding it his specific id using a simple single row function call like you need.
I only know a way to do direcly this on jQuery, that you wouldn't like to use.
simply you need to do that:
function createElementOnDom (type,id) {
var element=document.createElement(type);
element.id=id;
return element;
}
var GameArena2 = {
canvas: createElementOnDom('CANVAS','myId'),
Simple! Just assign it immediately after the GameArena2 object is created.
var GameArena2 = {
canvas: document.createElement("canvas"),
start: function() {
...
...
}
// give id to a canvas
GameArena2.canvas.id = "GameArena2";
function everyinterval(n) {
if ((GameArena.frameNo / n) % 1 == 0) {
...
...
}

ReactJS: Uncaught RangeError after setState

I'm trying to create a simple drawing application using ReactJS and Flux. However, I came across a bug [Uncaught RangeError: Maximum call stack size exceeded] (it's more specifically a ReactJS problem) I've been trying figure out for a while now. It occurs when I'm trying to do setState() in a component. Below is part of my code to better illustrate my problem.
DrawingCanvas.react.js
var React = require('react');
var MouseInteractionsMixin = require('../utils/MouseInteractionsMixin');
var StrokeActionCreators = require('../actions/StrokeActionCreators');
var StrokeStore = require('../stores/StrokeStore');
function getStateFromStores() {
return {
currentStroke: StrokeStore.getCurrentStroke()
};
}
var DrawingCanvas = React.createClass({
mixins: [MouseInteractionsMixin],
styles: {
canvas: {
border: '1px solid black'
}
},
getInitialState: function() {
return getStateFromStores();
},
componentWillMount: function() {
StrokeStore.addChangeListener(this._update);
},
componentDidMount: function() {
this._onChange();
},
componentDidUpdate: function() {
this._onChange();
},
_onChange: function() {
if(this.state.dragging) {
StrokeActionCreators.beginStroke(this.state.mouse.point);
} else {
if(this.state.mouse.event == 'mouseup') {
StrokeActionCreators.endStroke(this.state.currentStroke);
}
}
},
_update: function() {
this.setState(getStateFromStores()); // where the problem occurs
context = this.getDOMNode().getContext('2d');
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
this._draw(context);
},
_draw: function(context) {
context.strokeStyle = '#000000';
context.lineJoin = 'round';
context.lineWidth = 5;
// Draw current stroke (incompleted)
for(var index = 1; index < this.state.currentStroke.points.length; index++) {
context.beginPath();
context.moveTo(this.state.currentStroke.points[index - 1].x, this.state.currentStroke.points[index - 1].y);
context.lineTo(this.state.currentStroke.points[index].x, this.state.currentStroke.points[index].y);
context.closePath();
context.stroke();
}
/*// Draw the other completed strokes
for(var strokeIndex = 0; strokeIndex < this.state.strokes.length; strokeIndex++) {
var stroke = this.state.strokes[strokeIndex];
for(var currentPointIndex = 1; currentPointIndex < stroke.points.length; currentPointIndex++) {
var previousPointIndex = currentPointIndex - 1;
context.beginPath();
context.moveTo(stroke.points[previousPointIndex].x, stroke.points[previousPointIndex].y);
context.lineTo(stroke.points[currentPointIndex].x, stroke.points[currentPointIndex].y);
context.closePath();
context.stroke();
}
}*/
},
render: function() {
return (
<canvas style={this.styles.canvas} width={this.props.width} height={this.props.height} />
);
}
});
module.exports = DrawingCanvas;
Basically, what I'm trying to do is trigger an action when the mouse is clicked and starts moving on the canvas, which sends the mouse position data to the StrokeStore. The StrokeStore stores all the mouse position data, so that eventually the DrawingCanvas component requests for the data (hence the getStateFromStores() function) and draws on the canvas, but when I try to set the state in the _update() function and try drawing on the canvas, there would be a large amount of mouse position data coming in (even after moving the mouse a tiny bit then stopping it) and an error "Uncaught RangeError: Maximum call stack size exceeded" would come up.
I've tried not storing the mouse position data as a state of the component, but rather separate variables outside the component which I use inside the component, and it works perfectly fine, so I think it's a problem with the setState().
I'd really like to get it working as states (or any other way so that I can contain the data in the stores).
EDIT:
StrokeStore.js
var AppDispatcher = require('../dispatchers/AppDispatcher');
var AppConstants = require('../constants/AppConstants');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var ActionTypes = AppConstants.ActionTypes;
var CHANGE_EVENT = 'change';
var _currentStroke = {
// Points array contains point objects with x and y variables
points: []
};
// Strokes array contains stroke objects with similar structure to currentStroke
var _strokes = [];
function _addPointToCurrentStroke(point) {
_currentStroke.points.push(point);
}
function _addStrokeToStrokes(stroke) {
_strokes.push(stroke);
}
function _clearCurrentStroke() {
_currentStroke = {
points: []
};
}
var StrokeStore = assign({}, EventEmitter.prototype, {
emitChange: function() {
this.emit(CHANGE_EVENT);
},
/**
* #param {function} callback
*/
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getCurrentStroke: function() {
return _currentStroke;
},
getStrokes: function() {
return _strokes;
}
});
AppDispatcher.register(function(payload) {
var action = payload.action;
switch(action.type) {
case ActionTypes.BEGIN_STROKE:
_addPointToCurrentStroke(action.point);
break;
case ActionTypes.END_STROKE:
_addStrokeToStrokes(action.stroke);
_clearCurrentStroke();
break;
default:
}
StrokeStore.emitChange();
});
module.exports = StrokeStore;
Any help is appreciated, thanks!

Changes to Javascript created element doesn't reflect to DOM

I have a class, that is supposed to display grey overlay over page and display text and loading gif. Code looks like this:
function LoadingIcon(imgSrc) {
var elem_loader = document.createElement('div'),
elem_messageSpan = document.createElement('span'),
loaderVisible = false;
elem_loader.style.position = 'absolute';
elem_loader.style.left = '0';
elem_loader.style.top = '0';
elem_loader.style.width = '100%';
elem_loader.style.height = '100%';
elem_loader.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
elem_loader.style.textAlign = 'center';
elem_loader.appendChild(elem_messageSpan);
elem_loader.innerHTML += '<br><img src="' + imgSrc + '">';
elem_messageSpan.style.backgroundColor = '#f00';
this.start = function () {
document.body.appendChild(elem_loader);
loaderVisible = true;
};
this.stop = function() {
if (loaderVisible) {
document.body.removeChild(elem_loader);
loaderVisible = false;
}
};
this.setText = function(text) {
elem_messageSpan.innerHTML = text;
};
this.getElems = function() {
return [elem_loader, elem_messageSpan];
};
}
Problem is, when I use setText method, it sets innerHTML of elem_messageSpan, but it doesn't reflect to the span, that was appended to elem_loader. You can use getElems method to find out what both elements contains.
Where is the problem? Because I don't see single reason why this shouldn't work.
EDIT:
I call it like this:
var xml = new CwgParser('cwg.geog.cz/cwg.xml'),
loader = new LoadingIcon('./ajax-loader.gif');
xml.ondataparse = function() {
loader.stop();
document.getElementById('cover').style.display = 'block';
};
loader.setText('Loading CWG list...');
loader.start();
xml.loadXML();
xml.loadXML() is function that usually takes 3 - 8 seconds to execute (based on download speed of client), thats why I'm displaying loading icon.

Categories

Resources