I have the following problem I have 21 little pictures, and I want them positioned randomly in a the window in defined time intervals.
I have the following code, and I know what causes the problem, but I can't solve it.
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function () {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(Prep6);
addLoadEvent(Prep7);
function Prep6() {
window_Height = window.innerHeight;
window_Width = window.innerWidth;
image_Element = document.getElementById("loader06");
image_Height = image_Element.clientHeight;
image_Width = image_Element.clientWidth;
availSpace_V = window_Height - image_Height;
availSpace_H = window_Width - image_Width;
moveImage7();
var changeInterval = 300;
setInterval(moveImage7, changeInterval);
}
function moveImage6() {
var randNum_V = Math.round(Math.random() * availSpace_V);
var randNum_H = Math.round(Math.random() * availSpace_H);
image_Element.style.top = randNum_V + "px";
image_Element.style.left = randNum_H + "px";
}
function Prep7() {
window_Height = window.innerHeight;
window_Width = window.innerWidth;
image_Element = document.getElementById("loader07");
image_Height = image_Element.clientHeight;
image_Width = image_Element.clientWidth;
availSpace_V = window_Height7 - image_Height;
availSpace_H = window_Width - image_Width;
moveImage7();
var changeInterval = 300;
setInterval(moveImage7, changeInterval);
}
function moveImage7() {
var randNum_V = Math.round(Math.random() * availSpace_V);
var randNum_H = Math.round(Math.random() * availSpace_H);
image_Element.style.top = randNum_V + "px";
image_Element.style.left = randNum_H + "px";
}
And the problem is:
moveImage7();
function moveImage6()
How can I solve it?
I believe you are having some scope issues. In other words, the variables used in your moveImage6() and moveImage7() function are not seen because they are local to your other functions. To fix this you must pass those variables to your moveImage functions. Your moveImage functions are called periodically via the setInterval function, so pass parameters as follows:
setInterval(function(){
moveImage6(availSpace_V, availSpace_H, image_Element);
}, changeInterval);
This would also require you to update your moveImage6() and moveImage7() functions to accept those parameters as well.
Passing parameters with setInterval
jsfiddle: simplified in action
Hope this helps!
PS: you may want to change window_Height7 in prep7() to window_Height
Related
function ratioDetect () {
var contWidth = $('.thumbnail-container').width()
var contHeight = $('.thumbnail-container').height()
var imgWidth = $('.thumbnail-container img').width()
var imgHeight = $('.thumbnail-container img').height()
var contRatio = contWidth/contHeight
var imgRatio = imgWidth/imgHeight
if (contRatio > imgRatio) {
$('.thumbnail-container img').removeClass('full-height')
$('.thumbnail-container img').addClass('full-width')
}
else{
$('.thumbnail-container img').removeClass('full-width')
$('.thumbnail-container img').addClass('full-height')
}
}
It should be pretty clear what this function does, the problem is I have 6 of these "thumbnail-container" so the script just get the values from the FIRST container only and does this math based just on the img inside this container and applies the values to all the other images.
I've tried to follow some tutorial for the ".this" usage but I failed. I need this code act separately for each individual container and his child-img because the size of each of the 6 <img>s is different.
You need to iterate over the container and process
function ratioDetect() {
$('.thumbnail-container').each(function () {
var $this = $(this),
$img = $this.find('img'),
contWidth = $this.width(),
contHeight = $this.height(),
imgWidth = $img.width(),
imgHeight = $img.height();
var contRatio = contWidth / contHeight,
imgRatio = imgWidth / imgHeight;
if (contRatio > imgRatio) {
$img.removeClass('full-height');
$img.addClass('full-width');
} else {
$img.removeClass('full-width');
$img.addClass('full-height');
}
});
}
We're working with the HTML5 canvas, displaying lots of images at one time.
This is working pretty well but recently we've had a problem with chrome.
When drawing images on to a canvas you seem to reach a certain point where the performance degrades very quickly.
It's not a slow effect, it seems that you go right from 60fps to 2-4fps.
Here's some reproduction code:
// Helpers
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
// http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })();
// https://github.com/mrdoob/stats.js
var Stats = function () { var e = Date.now(), t = e; var n = 0, r = Infinity, i = 0; var s = 0, o = Infinity, u = 0; var a = 0, f = 0; var l = document.createElement("div"); l.id = "stats"; l.addEventListener("mousedown", function (e) { e.preventDefault(); y(++f % 2) }, false); l.style.cssText = "width:80px;opacity:0.9;cursor:pointer"; var c = document.createElement("div"); c.id = "fps"; c.style.cssText = "padding:0 0 3px 3px;text-align:left;background-color:#002"; l.appendChild(c); var h = document.createElement("div"); h.id = "fpsText"; h.style.cssText = "color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; h.innerHTML = "FPS"; c.appendChild(h); var p = document.createElement("div"); p.id = "fpsGraph"; p.style.cssText = "position:relative;width:74px;height:30px;background-color:#0ff"; c.appendChild(p); while (p.children.length < 74) { var d = document.createElement("span"); d.style.cssText = "width:1px;height:30px;float:left;background-color:#113"; p.appendChild(d) } var v = document.createElement("div"); v.id = "ms"; v.style.cssText = "padding:0 0 3px 3px;text-align:left;background-color:#020;display:none"; l.appendChild(v); var m = document.createElement("div"); m.id = "msText"; m.style.cssText = "color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; m.innerHTML = "MS"; v.appendChild(m); var g = document.createElement("div"); g.id = "msGraph"; g.style.cssText = "position:relative;width:74px;height:30px;background-color:#0f0"; v.appendChild(g); while (g.children.length < 74) { var d = document.createElement("span"); d.style.cssText = "width:1px;height:30px;float:left;background-color:#131"; g.appendChild(d) } var y = function (e) { f = e; switch (f) { case 0: c.style.display = "block"; v.style.display = "none"; break; case 1: c.style.display = "none"; v.style.display = "block"; break } }; var b = function (e, t) { var n = e.appendChild(e.firstChild); n.style.height = t + "px" }; return { REVISION: 11, domElement: l, setMode: y, begin: function () { e = Date.now() }, end: function () { var f = Date.now(); n = f - e; r = Math.min(r, n); i = Math.max(i, n); m.textContent = n + " MS (" + r + "-" + i + ")"; b(g, Math.min(30, 30 - n / 200 * 30)); a++; if (f > t + 1e3) { s = Math.round(a * 1e3 / (f - t)); o = Math.min(o, s); u = Math.max(u, s); h.textContent = s + " FPS (" + o + "-" + u + ")"; b(p, Math.min(30, 30 - s / 100 * 30)); t = f; a = 0 } return f }, update: function () { e = this.end() } } }
// Firefox events suck
function getOffsetXY(eventArgs) { return { X: eventArgs.offsetX == undefined ? eventArgs.layerX : eventArgs.offsetX, Y: eventArgs.offsetY == undefined ? eventArgs.layerY : eventArgs.offsetY }; }
function getWheelDelta(eventArgs) { if (!eventArgs) eventArgs = event; var w = eventArgs.wheelDelta; var d = eventArgs.detail; if (d) { if (w) { return w / d / 40 * d > 0 ? 1 : -1; } else { return -d / 3; } } else { return w / 120; } }
// Reproduction Code
var stats = new Stats();
document.body.appendChild(stats.domElement);
var masterCanvas = document.getElementById('canvas');
var masterContext = masterCanvas.getContext('2d');
var viewOffsetX = 0;
var viewOffsetY = 0;
var viewScaleFactor = 1;
var viewMinScaleFactor = 0.1;
var viewMaxScaleFactor = 10;
var mouseWheelSensitivity = 10; //Fudge Factor
var isMouseDown = false;
var lastMouseCoords = null;
var imageDimensionPixelCount = 25;
var paddingPixelCount = 2;
var canvasDimensionImageCount = 50;
var totalImageCount = Math.pow(canvasDimensionImageCount, 2);
var images = null;
function init() {
images = createLocalImages(totalImageCount, imageDimensionPixelCount);
initInteraction();
renderLoop();
}
function initInteraction() {
var handleMouseDown = function (eventArgs) {
isMouseDown = true;
var offsetXY = getOffsetXY(eventArgs);
lastMouseCoords = [
offsetXY.X,
offsetXY.Y
];
};
var handleMouseUp = function (eventArgs) {
isMouseDown = false;
lastMouseCoords = null;
}
var handleMouseMove = function (eventArgs) {
if (isMouseDown) {
var offsetXY = getOffsetXY(eventArgs);
var panX = offsetXY.X - lastMouseCoords[0];
var panY = offsetXY.Y - lastMouseCoords[1];
pan(panX, panY);
lastMouseCoords = [
offsetXY.X,
offsetXY.Y
];
}
};
var handleMouseWheel = function (eventArgs) {
var mouseX = eventArgs.pageX - masterCanvas.offsetLeft;
var mouseY = eventArgs.pageY - masterCanvas.offsetTop;
var zoom = 1 + (getWheelDelta(eventArgs) / mouseWheelSensitivity);
zoomAboutPoint(mouseX, mouseY, zoom);
if (eventArgs.preventDefault !== undefined) {
eventArgs.preventDefault();
} else {
return false;
}
}
masterCanvas.addEventListener("mousedown", handleMouseDown, false);
masterCanvas.addEventListener("mouseup", handleMouseUp, false);
masterCanvas.addEventListener("mousemove", handleMouseMove, false);
masterCanvas.addEventListener("mousewheel", handleMouseWheel, false);
masterCanvas.addEventListener("DOMMouseScroll", handleMouseWheel, false);
}
function pan(panX, panY) {
masterContext.translate(panX / viewScaleFactor, panY / viewScaleFactor);
viewOffsetX -= panX / viewScaleFactor;
viewOffsetY -= panY / viewScaleFactor;
}
function zoomAboutPoint(zoomX, zoomY, zoomFactor) {
var newCanvasScale = viewScaleFactor * zoomFactor;
if (newCanvasScale < viewMinScaleFactor) {
zoomFactor = viewMinScaleFactor / viewScaleFactor;
} else if (newCanvasScale > viewMaxScaleFactor) {
zoomFactor = viewMaxScaleFactor / viewScaleFactor;
}
masterContext.translate(viewOffsetX, viewOffsetY);
masterContext.scale(zoomFactor, zoomFactor);
viewOffsetX = ((zoomX / viewScaleFactor) + viewOffsetX) - (zoomX / (viewScaleFactor * zoomFactor));
viewOffsetY = ((zoomY / viewScaleFactor) + viewOffsetY) - (zoomY / (viewScaleFactor * zoomFactor));
viewScaleFactor *= zoomFactor;
masterContext.translate(-viewOffsetX, -viewOffsetY);
}
function renderLoop() {
clearCanvas();
renderCanvas();
stats.update();
requestAnimFrame(renderLoop);
}
function clearCanvas() {
masterContext.clearRect(viewOffsetX, viewOffsetY, masterCanvas.width / viewScaleFactor, masterCanvas.height / viewScaleFactor);
}
function renderCanvas() {
for (var imageY = 0; imageY < canvasDimensionImageCount; imageY++) {
for (var imageX = 0; imageX < canvasDimensionImageCount; imageX++) {
var x = imageX * (imageDimensionPixelCount + paddingPixelCount);
var y = imageY * (imageDimensionPixelCount + paddingPixelCount);
var imageIndex = (imageY * canvasDimensionImageCount) + imageX;
var image = images[imageIndex];
masterContext.drawImage(image, x, y, imageDimensionPixelCount, imageDimensionPixelCount);
}
}
}
function createLocalImages(imageCount, imageDimension) {
var tempCanvas = document.createElement('canvas');
tempCanvas.width = imageDimension;
tempCanvas.height = imageDimension;
var tempContext = tempCanvas.getContext('2d');
var images = new Array();
for (var imageIndex = 0; imageIndex < imageCount; imageIndex++) {
tempContext.clearRect(0, 0, imageDimension, imageDimension);
tempContext.fillStyle = "rgb(" + getRandomInt(0, 255) + ", " + getRandomInt(0, 255) + ", " + getRandomInt(0, 255) + ")";
tempContext.fillRect(0, 0, imageDimension, imageDimension);
var image = new Image();
image.src = tempCanvas.toDataURL('image/png');
images.push(image);
}
return images;
}
// Get this party started
init();
And a jsfiddle link for your interactive pleasure:
http://jsfiddle.net/BtyL6/14/
This is drawing 50px x 50px images in a 50 x 50 (2500) grid on the canvas. I've also quickly tried with 25px x 25px and 50 x 50 (2500) images.
We have other local examples that deal with bigger images and larger numbers of images and the other browser start to struggle with these at higher values.
As a quick test I jacked up the code in the js fiddle to 100px x 100px and 100 x 100 (10000) images and that was still running at 16fps when fully zoomed out. (Note: I had to lower the viewMinScaleFactor to 0.01 to fit it all in when zoomed out.)
Chrome on the other hand seems to hit some kind of limit and the FPS drops from 60 to 2-4.
Here's some info about what we've tried and the results:
We've tried using setinterval rather than requestAnimationFrame.
If you load 10 images and draw them 250 times each rather than 2500 images drawn once each then the problem goes away. This seems to indicate that chrome is hitting some kind of limit/trigger as to how much data it's storing about the rendering.
We have culling (not rendering images outside of the visual range) in our more complex examples and while this helps it's not a solution as we need to be able to show all the images at once.
We have the images only being rendered if there have been changes in our local code, against this helps (when nothing changes, obviously) but it isn't a full solution because the canvas should be interactive.
In the example code we're creating the images using a canvas, but the code can also be run hitting a web service to provide the images and the same behaviour (slowness) will be seen.
We've found it very hard to even search for this issue, most results are from a couple of years ago and woefully out of date.
If any more information would be useful then please ask!
EDIT: Changed js fiddle URL to reflect the same code as in the question. The code itself didn't actually change, just the formatting. But I want to be consistent.
EDIT: Updated jsfiddle and and code with css to prevent selection and call requestAnim after the render loop is done.
In Canary this code freezes it on my computer. As to why this happens in Chrome the simple answer is that it uses a different implementation than f.ex. FF. In-depth detail I don't know, but there is obviously room for optimizing the implementation in this area.
I can give some tip however on how you can optimize the given code to make it run in Chrome as well :-)
There are several things here:
You are storing each block of colors as images. This seem to have a huge performance impact on Canary / Chrome.
You are calling requestAnimationFrame at the beginning of the loop
You are clearing and rendering even if there are no changes
Try to (addressing the points):
If you only need solid blocks of colors, draw them directly using fillRect() instead and keep the color indexes in an array (instead of images). Even if you draw them to an off-screen canvas you will only have to do one draw to main canvas instead of multiple image draw operations.
Move requestAnimationFrame to the end of the code block to avoid stacking.
Use dirty flag to prevent unnecessary rendering:
I modified the code a bit - I modified it to use solid colors to demonstrate where the performance impact is in Chrome / Canary.
I set a dirty flag in global scope as true (to render the initial scene) which is set to true each time the mouse move occur:
//global
var isDirty = true;
//mouse move handler
var handleMouseMove = function (eventArgs) {
// other code
isDirty = true;
// other code
};
//render loop
function renderLoop() {
if (isDirty) {
clearCanvas();
renderCanvas();
}
stats.update();
requestAnimFrame(renderLoop);
}
//in renderCanvas at the end:
function renderCanvas() {
// other code
isDirty = false;
}
You will of course need to check for caveats for the isDirty flag elsewhere and also introduce more criteria if it's cleared at the wrong moment. I would store the old position of the mouse and only (in the mouse move) if it changed set the dirty flag - I didn't modify this part though.
As you can see you will be able to run this in Chrome and in FF at a higher FPS.
I also assume (I didn't test) that you can optimize the clearCanvas() function by only drawing the padding/gaps instead of clearing the whole canvas. But that need to be tested.
Added a CSS-rule to prevent the canvas to be selected when using the mouse:
For further optimizing in cases such as this, which is event driven, you don't actually need an animation loop at all. You can just call the redraw when the coords or mouse-wheel changes.
Modification:
http://jsfiddle.net/BtyL6/10/
This was a legitimate bug in chrome.
https://code.google.com/p/chromium/issues/detail?id=247912
It has now been fixed and should be in a chrome mainline release soon.
I want to call to method setupCanvas.Draw(); to draw rectangle.
And for some reason I can not call a function Draw(); from out the scop game.setupCanvas();
Demo jsfiddle
window.game = window.game|| {};
game.main = function() {};
game.setupCanvas = function(){
var w = $(window).outerWidth();
var h = $(window).outerHeight();
myCanvas = document.createElement('canvas');
document.body.appendChild(myCanvas);
myCanvas.id = "playground";
myCanvas.width = w * 0.8;
myCanvas.height = h * 0.8;
myCanvas.style.left= (w - myCanvas.width )/2 + 'px' ;
myCanvas.style.top= (h - myCanvas.height )/2 + 'px' ;
myCanvas.style.zIndex = 10;
myCanvas.style.position = "absolute";
myCanvas.style.border = "1px solid green ";
this.ctx= $('#playground').get(0).getContext('2d');
this.Draw = function(){
ctx.fillStyle="#0000FF";
ctx.fillRect(150,75,150,75);
ctx.fillStyle="#F0F0FF";
ctx.fillRect(0,0,150,75);
};
}();
game.setupCanvas.Draw();
Many thanks
You need to create a new instance:
var x = new game.setupCanvas();
x.Draw();
Also, this part is wrong:
};
}(); // <--
game.setupCanvas.Draw();
You're immediately-invoking the function, which you shouldn't be doing. It returns undefined to game.setupCanvas. Take it away and your code should work.
Moreover, when you reference a thectx property in your Draw method, you need to use this.ctx.
jsFiddle Demo
Your assignment of game.setupCanvas is invalid since the self invoking function doesn't return anything so game.setupCanvas will be undefined. Make sure to return an object with your public methods like this.
return {
Draw: function(){
ctx.fillStyle="#0000FF";
ctx.fillRect(150,75,150,75);
ctx.fillStyle="#F0F0FF";
ctx.fillRect(0,0,150,75);
},
DrawSomethingElse: function(){
ctx.fillStyle="#0000FF";
ctx.fillRect(150,75,150,75);
ctx.fillStyle="#F0F0FF";
ctx.fillRect(0,0,150,75);
}
};
Here is an update to your fiddle http://jsfiddle.net/dAvtS/11/
This is my first time making a mini-game from complete scratch.
Google chrome gives me these errors at runtime:
Uncaught TypeError: Cannot call method 'draw' of undefined Logic.js:28
loop Logic.js:28
startLoop Logic.js:35
init Logic.js:19
It was working fine when i used "setInterval" but i want the newest stuff.
I don't think that requestAnimationFrame has anything to do with it.
But i don't see what's wrong! Please help.
// Create the canvas
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.addEventListener('click', canvasClick, false);
//Declarations
var isPlaying = false;
var animeFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame;
var Pair1;
var Pair2;
//init
function init(){
startLoop();
Pair1 = new Pair();
Pair2 = new Pair();
alert('init called');
}
//Draw
function loop(){
if(isPlaying){
Pair1.draw();
Pair2.draw();
animeFrame(loop);
}
}
function startLoop(){
isPlaying = true;
loop();
}
function stopLoop(){
isPlaying = false;
}
//Interface
function canvasClick(){
var X = event.x;
var Y = event.y;
X -= canvas.offsetLeft;
Y -= canvas.offsetTop;
if(X > Pair1.X && X < Pair1.X + 64){
if(Y > Pair1.Y && Y < Pair1.Y + 96){
alert('Clicked Pair1');
};
};
}
//Create Images
var pairImg = new Image();
pairImg.src = "images/Gold_Pair.png";
pairImg.addEventListener('load',init,false)
//Pair
function Pair(){
var X = Math.floor(Math.random() * (canvas.width - 64));
var Y = Math.floor(Math.random() * (canvas.height - 96));
}
//Draw
Pair.prototype.draw = function(){
ctx.drawImage(pairImg, this.X, this.Y, 64, 96);
};
Thanks for you replys!!!
Your "init" function calls "startLoop", which calls "loop", which expects that "Pair1" and "Pair2" have been initialized. However, "init" doesn't initialize them until after calling "startLoop".
Try changing "init":
function init(){
Pair1 = new Pair();
Pair2 = new Pair();
startLoop();
alert('init called');
}
I think the problem is that your function loop requires Pair1 and Pair2 to exist, but init doesn’t do that until after loop has been called (via startloop).
Maybe this version of init would work?
//init
function init(){
Pair1 = new Pair();
Pair2 = new Pair();
startLoop();
alert('init called');
}
I'm using Raphael.js. Everytime i load the page i get an error that reads:
con is undefined
x = con.x
I looked up con in the Raphael documentation, and this is what i found:
var con = R._getContainer.apply(0, arguments),
container = con && con.container,
x = con.x,
y = con.y,
width = con.width,
height = con.height;
//...
con is clearly defined here. Here is the code I am trying to load:
var paper = new Raphael(ele('canvas_container'), 500, 500);
window.onload = function() {
var circle = paper.circle(100,100,100);
for (i = 0; i < 5; i++) {
var multiplier = i * 5;
paper.circle(250 + (2 * multiplier), 100 + multiplier, 50 - multiplier);
}
}
Has anyone else gotten this error? Is this a bug in the version of Raphael that I have or is there some other problem?
Try moving the paper instantiation inside your window's load function:
window.onload = function() {
var paper = new Raphael(ele('canvas_container'), 500, 500);
var circle = paper.circle(100,100,100);
for (i = 0; i < 5; i++) {
var multiplier = i * 5;
paper.circle(250 + (2 * multiplier), 100 + multiplier, 50 - multiplier);
}
}
If you try to get an element by its id before the DOM is ready, getElementById won't return anything. As you can see here, trying your code on an empty document shows the same result.
Raphael.js expects there to be a hard coded HTML element on the page with the name of the Raphael canvas (ie: "canvas_container"). If the HTML element is created during run time (dynamically in your JavaScript code), it will throw this error.
R._engine.create = function () {
var con = R._getContainer.apply(0, arguments),
container = con && con.container,
x = con.x,
y = con.y,
width = con.width,
height = con.height;
if (!container) {
throw new Error("SVG container not found.");
}
var cnvs = $("svg"),
css = "overflow:hidden;",
isFloating;
x = x || 0;
y = y || 0;
width = width || 512;
height = height || 342;
$(cnvs, {
height: height,
version: 1.1,
width: width,
xmlns: "http://www.w3.org/2000/svg"
});
if (container == 1) {
cnvs.style.cssText = css + "position:absolute;left:" + x + "px;top:" + y + "px";
R._g.doc.body.appendChild(cnvs);
isFloating = 1;
} else {
cnvs.style.cssText = css + "position:relative";
if (container.firstChild) {
container.insertBefore(cnvs, container.firstChild);
} else {
container.appendChild(cnvs);
}
}
container = new R._Paper;
container.width = width;
container.height = height;
container.canvas = cnvs;
container.clear();
container._left = container._top = 0;
isFloating && (container.renderfix = function () {});
container.renderfix();
return container;
};