Easel javascript structure explanation - javascript

I have found an API that'll make working with CANVAS a lot easier. It allows selection and modification of individual elements on the canvas very easily. It's EaselJS. The API doc is here. http://easeljs.com/docs/
I am Ok with the API so far. What confuses me is actually some javascript in there. The part that's in bold or within * *(couldn't get the formatting to work)
What kind of javascript structure is this?
(function(target){...content...})(bitmap)
and in the content, it references bitmap, which is something outside.
HERE IS THE CODE
for(var i = 0; i < 100; i++){
bitmap = new Bitmap(image);
container.addChild(bitmap);
bitmap.x = canvas.width * Math.random()|0;
bitmap.y = canvas.height * Math.random()|0;
bitmap.rotation = 360 * Math.random()|0;
bitmap.regX = bitmap.image.width/2|0;
bitmap.regY = bitmap.image.height/2|0;
bitmap.scaleX = bitmap.scaleY = bitmap.scale = Math.random()*0.4+0.6;
bitmap.mouseEnabled = true;
bitmap.name = "bmp_"+i;
(function(target) {
*bitmap.onPress = function(evt) *
{if (window.console && console.log) { console.log("press!"); }
target.scaleX = target.scaleY = target.scale*1.2;
container.addChild(target);
// update the stage while we drag this target
//Ticker provides a central heartbeat for stage to listen to
//At each beat, stage.tick is called and the display list re-rendered
Ticker.addListener(stage);
var offset = {x:target.x-evt.stageX, y:target.y-evt.stageY};
evt.onMouseMove = function(ev) {
target.x = ev.stageX+offset.x;
target.y = ev.stageY+offset.y;
if (window.console && console.log) { console.log("move!"); }
}
evt.onMouseUp = function() {
target.scaleX = target.scaleY = target.scale;
// update the stage one last time to render the scale change, then stop updating:
stage.update();
Ticker.removeListener(stage);
if (window.console && console.log) { console.log("up!"); }
}
** }})(bitmap); **
bitmap.onClick = function() { if (window.console && console.log) { console.log("click!"); } }
}

(function(target){...content...})(bitmap)
is creating a lexical scope for content so that any var or function declarations in content do not leak into the global scope. Inside content, target is just another name for
bitmap.
You can think of this as similar to
function init(target) { ...content... }
and then an immediate call to it passing bitmap as the actual value of the target parameter
but the first version interferes with the global scope even less -- it doesn't define init as a name in the global scope.
EDIT:
I think the purpose is not lexical scoping, but to make sure that the event handlers point to the right bitmap, instead of the last bitmap the for loop deals with.
init(bitmap);
See Event handlers inside a Javascript loop - need a closure? for more detail.

Related

Javascript - element.addEventListener() returns "Uncaught TypeError"

For context, I am trying to code a memory game where you have to pair two of the same colored circles until the whole board is complete. I've called it Match-Two. Here is the code that I'll reference from:
class Circle {
constructor(element, circleColor){
this.elem = element;
this.color = circleColor;
}
}
var frequency = [0, 0, 0, 0, 0, 0, 0, 0];
var num;
var hue = new Array(8);
var circle = new Array(16);
hue[0] = "#0039ff";
hue[1] = "#ff0000";
hue[2] = "#43ff00";
hue[3] = "#fffa00";
hue[4] = "#7405b5";
hue[5] = "#ff9d00";
hue[6] = "#ff00c3";
hue[7] = "#00fff6";
onload = function() {
for(var i = 0; i < 16; i++){
circle[i] = new Circle(document.getElementById("circle" + i));
while(circle[i].color === undefined){
num = Math.floor(Math.random() * 8);
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
circle[i].elem.addEventListener('click', function(){
main(circle[i])
});
}
}
}
}
function main(circle){
circle.elem.style.backgroundColor = circle.color;
}
So in this code I create a class of Circle and I create an array of Circle objects which is identified as 'circle'. When the page is loaded, I give each circle object an element reference from my html document (There are 16 circles and they each have an id of circle0, circle1, circle2.. etc. Then there's a small algorithm to ensure there are only two of each color in the matrix so they all have a matching pair. In each iteration of the for loop, I add an event listener to each circle. If the circle is clicked, I want it to change to its color which is stored in color[i].color. However, when I click the circles all it returns is:
Uncaught TypeError: Cannot read property 'elem' of undefined
at main (script.js:39)
at HTMLDivElement.<anonymous> (script.js:31)
Which is referencing:
circle.elem.style.backgroundColor = circle.color;
So I put some console.log() functions in to see what was going on:
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
console.log(circle[i].elem);
console.log(circle[i].color);
circle[i].elem.addEventListener('click', function(){
main(circle[i])
});
}
And this spits out exactly what I expect:
script.js:31 #ff9d00
script.js:30 div data-brackets-id=​"11" class=​"circle" id=​"circle1" /div
script.js:31 #ff9d00
script.js:30 div data-brackets-id=​"12" class=​"circle" id=​"circle2" /div
script.js:31 #0039ff
script.js:30 div data-brackets-id=​"13" class=​"circle" id=​"circle3" /div
script.js:31 #0039ff
So it returns the element reference and the color of the circle. So then I try putting the "circle[i].elem.style.backgroundColor = circle[i].color" into the event listener and I get the same issue as before...
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
console.log(circle[i].elem);
console.log(circle[i].color);
circle[i].elem.addEventListener('click', function(){
circle[i].elem.style.backgroundColor = circle[i].color
});
}
Circles without their colors. The console log statements are on the right-hand side with their specific colors as well...
So I gave up and decided to write that exact line of code outside the event listener to see if that works, and it changed all the circle's colors to their specific color...
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
console.log(circle[i].elem);
console.log(circle[i].color);
circle[i].elem.style.backgroundColor = circle[i].color;
circle[i].elem.addEventListener('click', function(){
circle[i].elem.style.backgroundColor = circle[i].color
});
}
The circles with their specific colors...
There is some problem the event listener not being able to pass the object of a Circle or something... I don't know please help :(
Your problem boils down to the way JS treats var variables - they sort of "leak" into the global scope.
Consider the event listener that you've attached:
circle[i].elem.addEventListener('click', function(){
main(circle[i])
});
So, whenever the listener gets triggered, it calls main() function and passes circle[i] into it. But since i is the variable that's leaked outside of the supposed scope, it always has the value of 16 - the value assigned to it during the last iteration of the for loop. That's why the main() function tries to access a style property of undefined - it's the value of circle[16] that was passed into it.
Here's a couple of ways to fix it:
If you can use ES6 let variables:
Use let i instead of var i in your for loop:
for (let i = 0; i < 16; i++) {
//...
}
If not, a classic way with function closure:
function createListener(j) {
return function () {
main(circle[j])
}
}
// and use it in your 'for' loop later:
circle[i].elem.addEventListener('click', createListener(i));
Here's a useful topic that provides more techniques to avoid this: JavaScript closure inside loops – simple practical example

JS global "let" variable not updating in function?

Edit: I've reported this as a Chromium bug: https://bugs.chromium.org/p/chromium/issues/detail?id=668257
I'm creating a little canvas game in JS with enemies that can shoot. For testing, I created a flag, declared globally as let fancy = true;, to determine whether or not to use a "fancy" targeting algorithm. I made it so that pressing P will toggle this flag. My main function, frame, calls another function, autoShoot, five times per second. autoShoot uses the fancy flag.
Today, something strange started happening; I don't remember what change introduced it. Sometimes, when I press P, autoShoot acts like fancy didn't get toggled. I did some debugging and discovered that the new, toggled value is reflected inside frame, but in autoShoot, the value isn't updated. It happens intermittently, and sometimes the value in autoShoot will fix itself (without me having done anything).
I've reduced the code to the following, which still exhibits the problem for me. Try pressing P a bunch of times. For me, the two values get "out of sync" and display differently after pressing P just once or twice:
(I'm running Chrome "Version 54.0.2840.99 m" on Windows 10.)
const canvas = document.getElementById("c");
const width = 0;
const height = 0;
const ctx = canvas.getContext("2d");
const ratio =1;// (window.devicePixelyRatio||1)/(ctxFOOOOOOOOFOOOOOOOOOFOOOOO||1);
canvas.width = width*ratio;
canvas.height = height*ratio;
canvas.style.width = width+"px";
canvas.style.height = height+"px";
ctx.scale(ratio, ratio);
function testSet(id, val) {
console.log(id+": "+val);
document.getElementById(id).innerText = val;
}
let fancy = true;
document.body.addEventListener("keydown", function(e) {
if (e.keyCode == 80) {
fancy = !fancy;
console.log("Set fancy to: "+fancy);
}
});
let bullets = Array(2000);
let lastTime = 0, shotTimer = 0;
function frame(time) {
const dt = (time - lastTime)/1000;
lastTime = time;
if ((shotTimer -= dt) <= 0) {
testSet("frame", fancy);
autoShoot();
shotTimer = 0.2;
}
for (let b of bullets) {}
requestAnimationFrame(frame);
}
function autoShoot() {
testSet("autoShoot", fancy);
}
requestAnimationFrame(frame);
<code>
fancy (frame) = <span id="frame"></span><br>
fancy (autoShoot) = <span id="autoShoot"></span>
</code>
<canvas id="c"></canvas>
Playing around, here are some observations:
removing any of the following causes the issue to go away:
any line in the code at the top dealing with the canvas, even just the comment after const ratio
the empty for...of loop: for (let b of bullets) {}
changing let fancy = to var fancy = or just fancy =
putting the whole thing out of the global scope (by using IIFE, onload handler, or block scope)
Increasing the size of the bullets array increases the frequency that the issue occurs. I think it's because it makes frame take longer to execute; originally, bullets.length was only 20, but each loop iteration did some stuff to update the bullet, etc.
Does this happen on your computers? Is there any logical explanation for this? I've tried restarting my browser, no change.
As everybody commented out it seems to be a Chrome issue.
I've tried to reproduce the same issue on Chrome version 45.0.2454.85 m (64-bit) and 44.0.2403.107 m (32-bit) (enabling strict mode of course) but I've not succeded. But on version 54.0.2840.99 m (64-bit) it is there.
And I noticed that changing requestAnimationFrame to something like setInterval also makes the problem totally go away.
So, I assume that this strange behaviour has something to do with Chrome's requestAnimationFrame on newer versions, and maybe block scoping nature of let, and function hoisting.
I can't say from which version of Chrome we can see this kind of "bug", but I can assume that it can be the version 52, because in this version many changes occured, like new method of Garbage collection, native support for es6 and es7 etc. For more information you can watch this video from I/O 2016.
Maybe, new Garbage collection method is causing this issue, because as they told in the above mentioned video it is connected with browser frames, something like v8 does GC when the browser is idle, in order not to touch the drawing of frames etc. And as we know that requestAnimationFrame is a method which calls a callback on the next frame drawing, maybe in this process these weird thing's happening. But this is just an assumption and I've no competence to say something serious about this:)
I'm on Chrome 54.0.2840.98 on Mac and it happens too. I think it's a scoping issue, because if I wrap the declaration following the let statement into a {…} block, then the snippet works fine and both values change immediately after key press.
const canvas = document.getElementById("c");
const width = 0;
const height = 0;
const ctx = canvas.getContext("2d");
const ratio =1;// (window.devicePixelyRatio||1)/(ctxFOOOOOOOOFOOOOOOOOOFOOOOO||1);
canvas.width = width*ratio;
canvas.height = height*ratio;
canvas.style.width = width+"px";
canvas.style.height = height+"px";
ctx.scale(ratio, ratio);
function testSet(id, val) {
console.log(id+": "+val);
document.getElementById(id).innerText = val;
}
let fancy = true;
{
document.body.addEventListener("keydown", function(e) {
if (e.keyCode == 80) {
fancy = !fancy;
console.log("Set fancy to: "+fancy);
}
});
let bullets = Array(2000);
let lastTime = 0, shotTimer = 0;
function frame(time) {
const dt = (time - lastTime)/1000;
lastTime = time;
if ((shotTimer -= dt) <= 0) {
testSet("frame", fancy);
autoShoot();
shotTimer = 0.2;
}
for (let b of bullets) {}
requestAnimationFrame(frame);
}
function autoShoot() {
testSet("autoShoot", fancy);
}
requestAnimationFrame(frame);
}
<code>
fancy (frame) = <span id="frame"></span><br>
fancy (autoShoot) = <span id="autoShoot"></span>
</code>
<canvas id="c"></canvas>

HTML5 Canvas, Having Trouble Running Multiple Animations At Once

I have written code that takes two arrays, both of which contain co-ordinates for a four-cornered shape (effectively a start frame and an end frame), a canvas ID and a time value. The function then calculates dX and dY of each corner and uses window.performance.now() to create a timestamp. Then, on every requestAnimationFrame(), it calculates what the co-ordinates should be by using dX, dY, the old timestamp, a new timestamp and the time value from the function call. It looks like this:
function doAnim(cv, startFrame, endFrame, animTime)
{
this.canvas = document.getElementById(cv);
this.ctx = this.canvas.getContext('2d');
if(startFrame.length != endFrame.length)
{
return('Error: Keyframe arrays do not match in length');
};
this.animChange = new Array();
for(i=1;i<=startFrame.length;i++)
{
var a = startFrame[i];
var b = endFrame[i]
var c = b - a;
this.animChange[i] = c;
}
this.timerStart = window.performance.now();
function draw()
{
this.requestAnimationFrame(draw, cv);
this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
this.currentFrame = new Array();
for(i=1;i<=startFrame.length;i++)
{
this.currentFrame[i] = startFrame[i]+(this.animChange[i]*((window.performance.now()-this.timerStart)/animTime));
}
if((window.performance.now()-this.timerStart)>=animTime)
{
this.ctx.beginPath()
this.ctx.moveTo(endFrame[1], endFrame[2]);
this.ctx.lineTo(endFrame[3], endFrame[4]);
this.ctx.lineTo(endFrame[5], endFrame[6]);
this.ctx.lineTo(endFrame[7], endFrame[8]);
this.ctx.fill();
return;
}
else
{
this.ctx.beginPath()
this.ctx.moveTo(this.currentFrame[1], this.currentFrame[2]);
this.ctx.lineTo(this.currentFrame[3], this.currentFrame[4]);
this.ctx.lineTo(this.currentFrame[5], this.currentFrame[6]);
this.ctx.lineTo(this.currentFrame[7], this.currentFrame[8]);
this.ctx.fill();
}
}
draw();
}
The goal is to have multiple animations of objects happening at once. I took the whole co-ordinate approach because I want the objects to appear as if they are coming from the horizon, creating a fake 3D perspective effect (all objects' starting frames would be a single point at the center of the canvas), and I do not want to warp the objects' textures.
Well, it works great for a single animation, but if I try to start a new animation on a completely different canvas while the first one is running, then the first animation stops dead in its tracks.
As you can see from my JS, I've tried getting around this with gratuitous use of this (I do not fully understand how this works yet, and every explanation I've read has left me even more confused), but it has not worked. I also tried a horrific approach which stored all the functions' own variables in one global array (the first time the function runs, all the variables are put in entries 1-30, the second time they're put in 31-60, etc). Unsurprisingly, that did not work either.
Here is a JSFiddle so you can see this scenario for yourself and play with my code. I am officially out of ideas. Any help will be greatly appreciated.
Like markE linked too, trying to call requestAnimationFrame multiple times won't work.
Instead you make multiple objects and then call some sort of function on them each frame.
I have created an example using your code:
https://jsfiddle.net/samcarlin/2bxn1r79/7/
var anim0frame1 = new Array();
anim0frame1[1] = 0;
anim0frame1[2] = 0;
anim0frame1[3] = 50;
anim0frame1[4] = 0;
anim0frame1[5] = 50;
anim0frame1[6] = 150;
anim0frame1[7] = 0;
anim0frame1[8] = 150;
var anim0frame2 = new Array();
anim0frame2[1] = 200;
anim0frame2[2] = 200;
anim0frame2[3] = 300;
anim0frame2[4] = 250;
anim0frame2[5] = 300;
anim0frame2[6] = 300;
anim0frame2[7] = 200;
anim0frame2[8] = 250;
//Call global
animations = [];
requestAnimationFrame( GlobalStep );
function GlobalStep(delta){
//Functions called by request animation frame have the new time as an argument
//so delta should be approximately the same as window.performance.now()
//especially in realtime applications, which this is
//Check if we have any animation objects
if(animations.length > 0){
//Iterate through and call draw on all animations
for(var i=0; i<animations.length; i++){
if(animations[i].draw(delta)){
//Basically we have it so if the draw function returns true we stop animating the object
//And remove it from the array, so have the draw function return true when animation is complete
animations[i].splice(i, 0);
//We removed an object from the array, so we decrement i
i--;
}
}
}
//And of course call requestAnimationFrame
requestAnimationFrame( GlobalStep );
}
function AnimationObject(cv, startFrame, endFrame, animTime){
//Add this object to the objects arrays
animations.push(this);
//We need to store start and end frame
this.startFrame = startFrame;
this.endFrame = endFrame;
this.animTime = animTime;
//Your code
this.canvas = document.getElementById(cv);
this.ctx = this.canvas.getContext('2d');
if (startFrame.length != endFrame.length) {
return ('Error: Keyframe arrays do not match in length');
};
this.animChange = new Array();
for (i = 1; i <= startFrame.length; i++) {
var a = startFrame[i];
var b = endFrame[i]
var c = b - a;
this.animChange[i] = c;
}
this.timerStart = window.performance.now();
}
//This adds a function to an object, but in such a way that every object shares the same function
//Imagine a kitchen, each object is a person, and this function is a spoon
//by defining this function in this manner Object.prototype.function_name = function(arguments){}
//We make it so one function definition is needed, essentially allowing all the people to share one spoon,
//the 'this' variable still refers to whichever object we call this method, and we save memory etc.
AnimationObject.prototype.draw = function(newTime){
//I added this to start frame so we get what we stored earlier
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.currentFrame = new Array();
for (i = 1; i <= this.startFrame.length; i++) {
this.currentFrame[i] = this.startFrame[i] + (this.animChange[i] * ((newTime - this.timerStart) / this.animTime));
}
if ((newTime - this.timerStart) >= this.animTime) {
this.ctx.beginPath()
this.ctx.moveTo(this.endFrame[1], this.endFrame[2]);
this.ctx.lineTo(this.endFrame[3], this.endFrame[4]);
this.ctx.lineTo(this.endFrame[5], this.endFrame[6]);
this.ctx.lineTo(this.endFrame[7], this.endFrame[8]);
this.ctx.fill();
return;
} else {
this.ctx.beginPath()
this.ctx.moveTo(this.currentFrame[1], this.currentFrame[2]);
this.ctx.lineTo(this.currentFrame[3], this.currentFrame[4]);
this.ctx.lineTo(this.currentFrame[5], this.currentFrame[6]);
this.ctx.lineTo(this.currentFrame[7], this.currentFrame[8]);
this.ctx.fill();
}
}
Notes:
Everytime you press the button a new object is added and simply overwrites previous ones for each frame, you should implement your program so that it checks if a specific animation has already started, you could also use the builtin mechanism to stop animation when complete (read the comments in the code)
You also need to change the on button click code
<button onclick="new AnimationObject('canvas1', anim0frame1, anim0frame2, 3000);">
Lastly if you have further questions feel free to contact me

naturalWidth and naturalHeight returns 0 using onload event

I have read countless of answers of this issue and I came up with the following, but it doesn't work either.
function fitToParent(objsParent, tagName) {
var parent, imgs, imgsCant, a, loadImg;
//Select images
parent = document.getElementById(objsParent);
imgs = parent.getElementsByTagName(tagName);
imgsCant = imgs.length;
function scaleImgs(a) {
"use strict";
var w, h, ratioI, wP, hP, ratioP, imgsParent;
//Get image dimensions
w = imgs[a].naturalWidth;
h = imgs[a].naturalHeight;
ratioI = w / h;
//Get parent dimensions
imgsParent = imgs[a].parentNode;
wP = imgsParent.clientWidth;
hP = imgsParent.clientHeight;
ratioP = wP / hP;
//I left this as a test, all this returns 0 and false, and they shouldn't be
console.log(w);
console.log(h);
console.log(ratioI);
console.log(imgs[a].complete);
if (ratioP > ratioI) {
imgs[a].style.width = "100%";
} else {
imgs[a].style.height = "100%";
}
}
//Loop through images and resize them
var imgCache = [];
for (a = 0; a < imgsCant; a += 1) {
imgCache[a] = new Image();
imgCache[a].onload = function () {
scaleImgs(a);
//Another test, this returns empty, for some reason the function fires before aplying a src to imgCache
console.log(imgCache[a].src);
}(a);
imgCache[a].src = imgs[a].getAttribute('src');
}
}
fitToParent("noticias", "img");
To summarise, the problem is the event onload triggers before the images are loaded (or that is how I understand it).
Another things to add:
I don't know at first the dimensions of the parent nor the child,
because they varied depending of their position on the page.
I don't want to use jQuery.
I tried with another function, changing the onload event to
window, and it worked, but it takes a lot of time to resize because
it waits for everything to load, making the page appear slower,
that's how I came to the conclusion the problem has something to do
with the onload event.
EDIT:
I made a fiddle, easier to look at the problem this way
https://jsfiddle.net/whn5cycf/
for some reason the function fires before aplying a src to imgCache
Well, the reason is that you are calling the function immedeatly:
imgCache[a].onload = function () {
}(a);
// ^^^ calls the function
You call the function and assign undefined (the return value of that function) to .onload.
If you want to use an IIFE to capture the current value of a, you have to make it return a function and accept a parameter to which the current value of a is assigned to:
imgCache[a].onload = function (a) {
return function() {
scaleImgs(a);
};
}(a);
Have a look again at JavaScript closure inside loops – simple practical example .

Write a wrapper object in Javascript

First off, let me apologize if my question isn't worded correctly - I'm not a professional coder so my terminology might be weird. I hope my code isn't too embarrassing :(
I have a fade() method that fades an image in and out with a mouse rollover. I would like to use a wrapper object (I think this is the correct term), to hold the image element and a few required properties, but I don't know how to accomplish this. fade() is called from the HTML, and is designed to be dropped into a page without much additional setup (so that I can easily add new fading images to any HTML), just like this:
<div id="obj" onmouseover="fade('obj', 1);" onmouseout="fade('obj', 0);">
The fade(obj, flag) method starts a SetInterval that fades the image in, and when the pointer is moved away, the interval is cleared and a new SetInterval is created to fade the image out. In order to save the opacity state, I've added a few properties to the object: obj.opacity, obj.upTimer, and obj.dnTimer.
Everything works okay, but I don't like the idea of adding properties to HTML elements, because it might lead to a future situation where some other method overwrites those properties. Ideally, I think there should be a wrapper object involved, but I don't know how to accomplish this cleanly without adding code to create the object when the page loads. If anyone has any suggestions, I would greatly appreciate it!
Here's my fader method:
var DELTA = 0.05;
function fade(id, flag) {
var element = document.getElementById(id);
var setCmd = "newOpacity('" + id + "', " + flag + ")";
if (!element.upTimer) {
element.upTimer = "";
element.dnTimer = "";
}
if (flag) {
clearInterval(element.dnTimer);
element.upTimer = window.setInterval(setCmd, 10);
} else {
clearInterval(element.upTimer);
element.dnTimer = window.setInterval(setCmd, 10);
}
}
function newOpacity(id, flag) {
var element = document.getElementById(id);
if (!element.opacity) {
element.opacity = 0;
element.modifier = DELTA;
}
if (flag) {
clearInterval(element.dnTimer)
element.opacity += element.modifier;
element.modifier += DELTA; // element.modifier increases to speed up fade
if (element.opacity > 100) {
element.opacity = 100;
element.modifier = DELTA;
return;
}
element.opacity = Math.ceil(element.opacity);
} else {
clearInterval(element.upTimer)
element.opacity -= element.modifier;
element.modifier += DELTA; // element.modifier increases to speed up fade
if (element.opacity < 0) {
element.opacity = 0;
element.modifier = DELTA;
return;
}
element.opacity =
Math.floor(element.opacity);
}
setStyle(id);
}
function setStyle(id) {
var opacity = document.getElementById(id).opacity;
with (document.getElementById(id)) {
style.opacity = (opacity / 100);
style.MozOpacity = (opacity / 100);
style.KhtmlOpacity = (opacity / 100);
style.filter = "alpha(opacity=" + opacity + ")";
}
}
You are right, adding the handlers in your HTML is not good. You also loose the possible to have several handlers for event attached to one object.
Unfortunately Microsoft goes its own way regarding attaching event handlers. But you should be able to write a small wrapper function to take care of that.
For the details, I suggest you read quirksmode.org - Advanced event registration models.
An example for W3C compatible browsers (which IE is not): Instead of adding your event handler in the HTML, get a reference to the element and call addEventListener:
var obj = document.getElementById('obj');
obj.addEventListener('mouseover', function(event) {
fade(event.currentTarget, 1);
}, false);
obj.addEventListener('mouseout', function(event) {
fade(event.currentTarget, 0);
}, false);
As you can see I'm passing directly a reference to the object, so in you fade method you already have a reference to the object.
You could wrap this in a function that accepts an ID (or reference) and every time you want to attach an event handler to a certain element, you can just pass the ID (or reference) to this function.
If you want to make your code reusable, I suggest to put everything into an object, like this:
var Fader = (function() {
var DELTA = 0.05;
function newOpacity() {}
function setStyle() {}
return {
fade: function(...) {...},
init: function(element) {
var that = this;
element.addEventListener('mouseover', function(event) {
that.fade(event.currentTarget, 1);
}, false);
element.addEventListener('mouseout', function(event) {
that.fade(event.currentTarget, 0);
}, false);
}
};
}())
Using an object to hold your functions reduces pollution of the global namespace.
Then you could call it with:
Fader.init(document.getElementById('obj'));
Explanation of the above code:
We have an immediate function (function(){...}()) which means, the function gets defined and executed (()) in one go. This function returns an object (return {...};, {..} is the object literal notation) which has the properties init and fade. Both properties hold functions that have access to all the variables defined inside the immediate function (they are closures). That means they can access newOpacity and setStyle which are not accessible from the outside. The returned object is assigned to the Fader variable.
This doesn't directly answer your question but you could use the jQuery library. It's simple, all you have to do is add a script tag at the top:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js">
Then your div would look like:
<div id="obj" onmouseover="$('#obj').fadeIn()" onmouseout="$('#obj').fadeOut()">
jQuery will handle all the browser dependencies for you so you don't have to worry about things like differences between firefox and mozilla etc...
If you want to keep your HTML clean, you should consider using JQuery to set up the events.
Your HTML will look like this:-
<div id="obj">
Your JavaScript will look "something" like this:-
$(document).ready(function() {
$("#obj").mouseover(function() {
Page.fade(this, 1);
}).mouseout(function(){
Page.fade(this, 0);
});
});
var Page = new function () {
// private-scoped variable
var DELTA = 0.05;
// public-scoped function
this.fade = function(divObj, flag) {
...
};
// private-scoped function
var newOpacity = function (divObj, flag) {
...
};
// private-scoped function
var setStyle = function (divObj) {
...
};
};
I introduced some scoping concept in your Javascript to ensure you are not going to have function overriding problems.

Categories

Resources