HTML5Canvas, remembering the last instance made visible - javascript

I would like to do a simple task in Adobe Animate under HTML5Canvas environment. There are a couple of buttons on stage and corresponding circle symbol instances beside them that are made invisible at the beginning. When I click a button, an adjacent circle is visible. Then if I click on another button randomly, its adjacent circle is visible, but the previously visible circle must become invisible again as only one circle should be visible at any given time.
As a simple solution, I started with 4 instances: button_1, button_2, circle_1, circle_2. I planned to store the circle instance's name in a variable called 'store' when I first click on any button. Then pass that information to the next button's mouse click event to make the previous circle instance invisible again. My rookie code looks like this...
/*Made circles invisible at the beginning*/
this.circle_1.visible = false;
this.circle_2.visible = false;
/*button's click events*/
var _this = this;
_this.button_1('click', function(){
_this.cicle_1.visible = true;
store.visible = false; /*make the previous circle invisible if any*/
var store = this.circle_1; /*updating current circle's name in variable 'store'*/
});
var _this = this;
_this.button_2.on('click', function(){
_this.circle_2.visible = true;
store.visible = false; /*make the previous circle invisible if any*/
var store = this.circle_2; /*updating current circle's name in variable 'store'*/
});
/* It also works if I can make all circles instances invisible and then show the intended one during every click event, but how can I get and set 20+ circle instances invisible in one step? */
However, the code didn't work. I have no programming experience so my logic could be laughable but this is the easiest solution I can think of. Maybe I should have declared my variable globally? Can anyone kindly improve this code or make it work, please? Please no For-i or Array solution because it makes my head spin :) Thanks in advance.

Welcome to Javscript! I can tell what you're trying to do, but there are a few errors you made that are easy for someone just starting out to make.
You are accessing store variable before actually declaring the variable with var store = this.circle_1. Using the var keyword will declare a variable at the top of it's "scope," no matter which line of that scope it's declared, and let will declare it on the line you specified. Either way, the existence of any variable will not exist outside of it's scope. A scope consists of a set of curly braces {} meaning you are declaring store but then it's immediately getting deleted when you leave the curly braces. Something like the following will fix that:
/*
A variable declared in one scope is available to all scopes inside it.
By declaring 'store' outside of any scope/curly braces, it will be accessible from anywhere in the code
*/
var store = this.circle_1; // you store either circle here. I'm just using circle_1 as a placeholder
//rest of code
this.button_1('click', function(){
store.visible = false;
store = this.circle_1;
this.circle_1.visible = true; //make circle 1 visible
});
I feel you are overthinking it a bit (which is ok, it happens), and if you just have 2 circles, there is a much easier way of doing it which I'll post below.
It also seems like you forgot the on keyword in this.button_1 event declaration.
You are re-declaring _this which shouldn't be necessary the first time, much less twice. The code var _this = this; stores a reference of this in a new variable named _this basically just renaming it, and doesn't do anything else.
I don't know much about Adobe Animate's style of JavaScript, but I'll try to modify your original JavaScript in a way that I assume should work with Animate.
the following code is the simplest way if you only have 2 circles
//Made circles invisible at the beginning
this.circle_1.visible = false;
this.circle_2.visible = false;
//button's click events
this.button_1.on('click', function(){
this.circle_1.visible = true; //make circle 1 visible
this.circle_2.visible = false; //make the other circle invisible
});
this.button_2.on('click', function(){
this.circle_2.visible = true;
this.circle_1.visible = false;
});
Let me know if it works, or if you run it in a browser press F12 > Click Console and let me know if there are any errors.
If you wanted an arbitrary amount of circles an "array solution," as you put it would be the best. Arrays and loops are pretty fundamental, and easy to understand once you start (even though the syntax looks scary). If you plan to proceed in learning programming, that should probably be the next thing you learn.

You likely have there a variable visibility problem. Declaring a var inside a function should not allow other pieces of code to reach that var's value. Instead, you should declare that var outside and store there a null, then call like if (store) store.visible=false;. So, just move the var store outside of both functions, and refer to store inside both of them, should do.

Related

Assignment causing program to behave strangely

I'm making a chess app for practice and I have a function called movePiece that gets called after a user selects a square to move a piece to. Before I call it I have a line that gets the destination piece's attribute (queen, pawn, etc..) using a dictionary:
secondAttr = pieceDict[posStr][0];
Strangely, when I comment out this line my movePiece() function gets called correctly, but when I leave it in there nothing happens until I click for a third time. I'm super confused why this random line before my function call that has nothing to do with the function itself is causing such behavior.
Here's a jsFiddle:
https://jsfiddle.net/eqmbk0u1/
, the pieces png's are stored on my computer so they won't show up, but they're all in their normal starting spots and when you select one it's square turns blue. See what happens when you comment out line 67 vs it normally.
You seem to have an error in you else block, of you $('square').click() event.
} else {
$("#"+firstID).removeClass('selected');
$("#"+firstID).addClass(lastSqrColor);
destRow = parseInt($(this).attr('row'));
destCol = parseInt($(this).attr('col'));
destID = parseInt($(this).attr('id'));
secondAttr = pieceDict[posStr][0]; //<--- Swap
posStr = positions[destRow][destCol]; //<--- Swap
movePiece();
firstTouch = false;
}
When you first click on a square, you save the name of the piece into posStr. Then, on your second click, you overwrite that value with an undefined value, because there is no piece in positions[destRow][destCol]. If you swap the lines 66 and 67, the code will work, because you set the secondAttr before overwriting posStr.

Without jQuery, make a "show more" "show less" a href

I know this is fairly straight forward using jQuery but for college assignment need to insert new paragraphs on clicking more link then remove them on clicking less links - we are not to use css or jQuery so my code so far looks like this - the insert works but the remove less() function doesn't any ideas why (even tried simple alert from teh less function and the return false on the a href doesn't work redirecting page to no javascript default.
window.onload= function()
{
var href = document.getElementById("more");
href.setAttribute("onclick","more(); return false;");
var more = document.getElementById("more");
more.onclick = function more()
{
var para1 = document.createElement("p");
para1.setAttribute("id", "para1");
var para1Cont = document.createTextNode("my text block 1");
para1.appendChild(para1Cont);
var more = document.getElementById("more");
more.parentNode.insertBefore(para1,more);
var para2 = document.createElement("p");
para2.setAttribute("id", "para2");
var para2Cont = document.createTextNode("My text block 2");
para2.appendChild(para2Cont);
more.parentNode.insertBefore(para2,more);
var toLess = more.setAttribute("id", "less");
var less = document.getElementById("less");
less.setAttribute("onclick", "less(); return false;");
less.innerHTML ="click here for less";
return false;
};
var less = document.getElementById("less");
less.onclick = function less()
{
var para1 = document.getElementById("para1");
var para2 = document.getElementById("para2");
alert("fr");
alert( para1.innerHTML);
para1.parentNode.removeChild(para1);
para2.parentNode.removeChild(para2);
var less = document.getElementById("less");
var toMore = less.setAttribute("id", "more");
var more = document.getElementById("more");
more.setAttribute("onclick", "more(); return false;");
more.innerHTML ="click here for more";
return false;
};
};
and the html code
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Help meeeee</title>
<link rel="stylesheet" type="text/css" href="styles/style.css">
<link href="scripts/mystyles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header">
<h1>test page</h1>
</div>
<div id="content">
click for more
</div>
<script type="text/javascript" src="scripts/myscript.js"></script>
</body>
Ok, You've got a lot of learning to do. I don't mean that in a bad way, but here's a vanillaJS example of how I'd tackle this:
window.addEventListener('load',function l()
{//use addEventListener, to avoid mem-leaks
"use strict";//for JSLint
var more = document.getElementById('more'),
less = document.getElementById('less'),
div = more.parentNode,//3 DOM reference, to be used by event handlers
added = [],//keep references to added elements, use as stack
rmHandle = function(e)
{//callback definition, don't bind unless less link should be usable
var rm = added.pop();
rm.parentNode.removeChild(rm);
if (added.length === 0)
{
less.removeEventListener('click', rmHandle, false);
}
e.preventDefault();
e.stopPropagation();
};
more.addEventListener('click',function(e)
{//add node:
var newP, count = added.length;
e.preventDefault();
e.stopPropagation();
if (count === 0)
{//bind less event handler here
less.addEventListener('click', rmHandle, false);
}
++count;
newP = document.createElement('p');//create node
newP.setAttribute('id','param'+count);//set id
newP.appendChild(document.createTextNode('New Paragraph #'+count));//add txt content
added.push(newP);//keep reference to node
div.insertBefore(newP, less);//append at end...
},false);
window.removeEventListener('load',l,false);//unbind load handler, this is the leak in IE
}, false);
Now, this by itself is a bit meaningless, so I've gone ahead and set up this fiddle
There are quite a few things left to be done (ie an unload event handler, hide the less link etc...)
Some clarification to help you understand the code:
addEventListener: Instead of setting attibutes, or directly binding event handlers using onload or onclick, I'm adding event listeners. These have the benefit of keeping everything JS-side. You're using setAttribute('onclick'...) somewhere in your code. This sets an attribute in the DOM, that refers back to JS. This is considered bad practice, and quite out-dated.
l-callback. My main callback (window.addEventListener('load', function l()... is called l. In this function, I query the DOM three times (sort of) and I assign the references to these DOM nodes to a variable: more, less and div. Next, I declare an array, to hold all nodes I'll be creating. A sort of stack, so I never need to query the dom to get a reference to those nodes I've created.
I also declare a function (rmHandle), which will handle the clicks on the less link. Because I declare this function within the scope of l, the function has access to all variables I previously declared (less, more, added). Again: I need never query the DOM...
more.addEventListener: This link has to work from the off, so I'm attaching my event listener to this DOM node on load.
no return false: Your question suggests that you know of/have used jQuery. return false in jQ and return false in JavaScript are not the same thing. if you attach an event handler to a form in VanillaJS return false might leave you gobsmacked, because at times: the form will still be submitted. Read about the W3C event model: the capturing and bubbling phases. quirksmode.org is a good resource for details. You'll understand why I'm calling these methods explicitly soon enough.
document.createTextNode: Now I will admit to using innerHTML every now and then, too. But since you're learning, I might aswell point out that innerHTML is not standard, the official standard is to use createTextNode.
At the end I remove the load event listener Because IE tends to leak memory if you don't. Then, the callback goes out of scope and there's nothing you can do about it. So everything can be flagged for GC, and there is no way anything can leak memory, still...
Edit:
I will admit that, listing up a few list-items, of which one just briefly touches on JS's way of resolving variable names in nested scopes isn't quite clear enough. It wasn't for me when I first started learning about closures, and it certainly isn't enough to explain why the code I posted is going to outperform yours by quite a bit.
So if you're up to it, I'm going to explain this a bit more, using a excerpt from your code, and walk you through a clean-up review:
var less = document.getElementById("less");
less.onclick = function less()
{
var para1 = document.getElementById("para1");
var para2 = document.getElementById("para2");
alert("fr");
alert( para1.innerHTML);
para1.parentNode.removeChild(para1);
para2.parentNode.removeChild(para2);
var less = document.getElementById("less");
var toMore = less.setAttribute("id", "more");
var more = document.getElementById("more");
more.setAttribute("onclick", "more(); return false;");
more.innerHTML ="click here for more";
return false;
};
This code should look familiar to you (it's copy-pasted from your question after all). Now why would I change this? First off: the DOM API (not part of JS BTW) is slow, clunky, illogical and a main source of frustration and using it too much kills woodland critters. In this snippet, we see this, though:
var less = document.getElementById("less");
less.onclick = function less()
{
//...
var less = document.getElementById("less");
}
So the name less is being used 3 times in an assignment context. Two of those assignments involve a DOM query. Not just a query, but exactly the same query: document.getElementById("less");! It's often said that one of the rules for writing good code is Do Not Repeat Yourself.
Another thing you might have heard is that, even when using loosely typed languages, it's not a bad idea to not assign different types to one variable. You're doing just that, though when you write function less(){}. Apart from a few (at times significant, but that's for some other time) semantic differences, this is basically the same as doing:
var less = function(){};
Each of these assignments is masking the previous one. If wou would've written:
var less = document.getElementById("less");
less.onclick = function less_func()
{
console.log(less);//logs a dom reference!
};
//or even:
less.onclick = function()
{//anonymous or lambda functions are valid... and quite common, too
console.log(less);
};
You wouldn't need that second DOM query witing the onclick function at all. This is because if JS's way of trying to resolve all variables to a previously declared variable. Consider this:
var evilGlobal = 'Do not use Globals';
function()
{
var goodLocal = 'Declared in function',
funcVar = function()
{
console.log(goodLocal);
console.log(evilGlobal);
},
func2 = function goodLocal(evilGlobal)
{
console.log(goodLocal);
console.log(evilGlobal);
console.log(funcVar());
};
funcVar();//logs Declared in function and Do not use Globals
func2();//logs itself (function), and undefined and then same as above
func2(goodLocal);//logs itself, Declared in Function and the same as funcVar
}
How does this come about? within funcVar it's fairly simple:
console.log(goodLocal);//<-- JS looks inside funcVar's function scope for var goodLocal
//not found? JS looks in the outer scope, that of the anonymous function that starts
//with var goodLocal = 'Declared in Function'
//This is the var used
The same applies to console.log(evilGlobal). Only this time, JS scans funcVar's scope, the anonymous function's scope and the global namespace. Why shouldn't you use globals? well, they're clearly slower, they can change state because functions can access them freely, and they clog the memory (the garbage collector only frees what is no longer referenced anywhere. The global namespace is always accessible).
The second case is a tad trickier, but not by much:
function goodLocal()//the goodLocal name is defined as the function!
this name is masks the variable in the outer scope. JS starts scanning the local scope, and finds goodLocal to be pointing to the function. It never checks the outer scope, so it never sees the goodLocal var in the parent function.
The same applies to evilGlobal:
function goodLocal(evilGlobal)
An argument is a variable, declared in the scope of the function. JS will never scan the global ns, because both names can be resolves localy, except for:
console.log(funcVar());
This will result in a scope scan of the parent function, which declares the funcVar variable, and assigns the previously discussed function to it. This function will still behave no different, as the function is called in its own scope/context.
Call contexts are quite tricky, too, so I'm going to gloss over this for a moment.
Back to your code: the other statements are actually repetitions of stuff you've written before, too: var para1 and var para2 are redundant, if you just keep them accessible in the outer scope.
Ah well, just keep reading, and keep learning, you'll get it soon enough...

Titanium Javascript: "that." does not work

Neither this nor that works. Does anyone know what is going on??
Edit:
qwerty is simply called as "qwerty();" when in other pieces of code.
It is supposed to be indepedent.
Edit: I realize what is wrong. The problem lies with the i...
function qwerty () {
..... for loop that changes i ......
var that = this;
this.chara[i] = createlabel.....
this.chara[i].addEventListener('click', function(e) {
var j = e.source.id;
alert("hello word");
alert(this.chara[j].width); // I get the error here
});
this.chara[i].addEventListener('doubleclick', function(e) {
alert("hello word");
alert(that.chara[i].width); // I get the error here too.
});
}
Any JS problem relating to this is likely due to the way the function using this is called. Storing a reference to this in your that variable should let you reference it from within your nested functions, exactly the way you are doing it already - assuming that qwerty() is called in a way that sets this to the correct object in the first place. (Personally I like to call such a variable self since it more accurately reflects what the variable is doing.)
However, in your function you say you get the error on this line:
that.chara[i].width
Given that you say this.chara[i].addEventListener(...) I'm guessing that the chara[i] variable holds a reference to a DOM element. If that is the case I'm guessing it is an element type that doesn't have a width property. Try this:
that.chara[i].style.width
https://developer.mozilla.org/en/CSS/width
That's the best I can do for you without more information about what error you're getting and how the qwerty() function is called...

Optimizing Javascript Loop for Wheel Game

I have a game I'm creating where lights run around the outside of a circle, and you must try and stop the light on the same spot three times in a row. Currently, I'm using the following code to loop through the lights and turn them "on" and "off":
var num_lights = 20;
var loop_speed = 55;
var light_index = 0;
var prevent_stop = false; //If true, prevents user from stopping light
var loop = setTimeout(startLoop, loop_speed);
function startLoop() {
prevent_stop = false;
$(".light:eq(" + light_index + ")").css("background-color", "#fff");
light_index++;
if(light_index >= num_lights) {
light_index = 0;
}
$(".light:eq(" + light_index + ")").css("background-color", "red");
loop = setTimeout(startLoop, loop_speed);
}
function stopLoop() {
clearTimeout(loop);
}
For the most part, the code seems to run pretty well, but if I have a video running simultaneously in another tab, the turning on and off of the lights seems to chug a bit. Any input on how I could possibly speed this up would be great.
For an example of the code from above, check out this page: http://ericditmer.com/wheel
When optimizing the thing to look at first is not doing twice anything you only need to do once. Looking up an element from the DOM can be expensive and you definitely know which elements you want, so why not pre-fetch all of them and void doing that multiple times?
What I mean is that you should
var lights = $('.light');
So that you can later just say
lights.eq(light_index).css("background-color", "red");
Just be sure to do the first thing in a place which keeps lights in scope for the second.
EDIT: Updated per comment.
I would make a global array of your selector references, so they selector doesn't have to be executed every time the function is called. I would also consider swapping class names, rather than attributes.
Here's some information of jQuery performance:
http://www.componenthouse.com/article-19
EDIT: that article id quite old though and jQuery has evolved a lot since. This is more recent: http://blog.dynatrace.com/2009/11/09/101-on-jquery-selector-performance/
You could try storing the light elements in an array instead of using a selector each time. Class selectors can be a little slow.
var elements = $('.light');
function startLoop() {
prevent_stop = false;
$(elements[light_index]).css('background-color', '#fff');
...
}
This assumes that the elements are already in their intended order in the DOM.
One thing I will note is that you have used a setTimeout() and really just engineered it to behave like setInterval().
Try using setInterval() instead. I'm no js engine guru but I would like to think the constant reuse of setTimeout has to have some effect on performance that would not be present using setInterval() (which you only need to set once).
Edit:
Curtousy of Diodeus, a related post to back my statement:
Related Stack Question - setTimeout() vs setInterval()
OK, this includes some "best practice" improvements, if it really optimizes the execution speed should be tested. At least you can proclaim you're now coding ninja style lol
// create a helper function that lend the array reverse function to reverse the
// order of a jquery sets. It's an object by default, not an array, so using it
// directly would fail
$.fn.reverse = Array.prototype.reverse;
var loop,
loop_speed = 55,
prevent_stop = false,
// prefetch a jquery set of all lights and reverses it to keep the right
// order when iterating backwards (small performance optimization)
lights = $('.light').reverse();
// this named function executes as soon as it's initialized
// I wrapped everything into a second function, so the variable prevent_stop is
// only set once at the beginning of the loop
(function startLoop() {
// keep variables always in the scope they are needed
// changed the iteration to count down, because checking for 0 is faster.
var num_lights = light_index = lights.length - 1;
prevent_stop = false;
// This is an auto-executing, self-referencing function
// which avoids the 55ms delay when starting the loop
loop = setInterval((function() {
// work with css-class changing rather than css manipulation
lights.eq( light_index ).removeClass('active');
// if not 0 iterate else set to num_lights
light_index = (light_index)? --light_index:num_lights;
lights.eq( light_index ).addClass('active');
// returns a referenze to this function so it can be executed by setInterval()
return arguments.callee;
})(), loop_speed);
})();
function stopLoop() {
clearInterval(loop);
}
Cheers neutronenstern

slide a div using javascript

I wanted to write a javascript code that will slide a div in specific direction, distance and in some given time. I wrote this small script. but doesn't work at all. Instead browser gets slow. No change in position is visible.
Can someone tell me how to achieve the result ? I know there are many ready made libraries that can do this easily. But I just wanted to give it a try.
<script type="text/javascript" language="javascript">
var element = '';
var slidePerMS = '';
function slideIt(ele, direction, distance, slideDuration){
element = ele;
var i=0;
slidePerMS = distance / (slideDuration*1000);
for(i=0; i<3000; i++){
setTimeout("changePosition()",1);
}
}
function changePosition(){
var currElement = document.getElementById(element);
currElement.style.left = "'"+slidePerMS+"px'";
}
</script>
SOO many things wrong with that code it's not even funny... Let's see...
You are trying to render a 1,000 FPS animation. This is simply impossible for a browser.
You are passing a string as parameter to setTimeout, which is as evil as eval.
You set slidePerMS once but never change it after, resulting in the div being moved to the exact same spot over and over.
You are setting the style with extra quotes inside - do you put quotes in a CSS file?
That's to name but a few. Try this instead:
<script type="text/javascript" language="javascript">
function slideIt(elem, direction, distance, slideDuration){
var elmt = document.getElementById(elem),
i=0, step = distance / (slideDuration*20),
stepper = setInterval(function() {
i = Math.min(distance,i+step);
elmt.style.left = i+'px';
if( i == distance) clearInterval(stepper);
},50);
}
</script>
You have many problems.
You are treating setTimeout as if it was sleep. Don't do that. It isn't like sleep at all, it runs a function after a given period of time, but doesn't pause the execution of anything else.
This means you just hammer the function repeatedly 3000 times, which is what is locking up the browser.
Instead of using a for loop, you should be using setInterval.
Don't pass a string to setInterval (or setTimeout), it gets evaled, which is slow and hard to debug, and it breaks scope. Pass a function instead.
Inside changePosition you are trying to use a variable called slidePerMS, which is undefined because it is defined in the scope of slideIt.
You are also trying to set left to "'123px'". You can't quote your values in CSS.
Get rid of both the 's.
This is why you can't see any change. Invalid values are ignored in CSS.

Categories

Resources