guys. here is a bit of code that is supposed to be okay, though it doesn't work...heh...quite typical =)
function xxx() {
var txtCanvas = document.getElementById('text');
var textOne = txtCanvas.getContext('2d');
var alpha = 0.5;
textOne.globalAlpha = alpha;
// loading image
var img = new Image();
img.src = "http://tvangeste.com/gallery/selani/tv4_2.jpg"
img.onload = function () {
textOne.drawImage(img, 0, 0);
}
//end of image loader
if (alpha < 1)
{
alpha += 0.1;
}
}
requestAnimationFrame(xxx);
This is Fiddle to show how it doesn't work...
http://jsfiddle.net/gLs1owd6/
The script is supposed to do one simple thing - to fade in the image.
Any suggestions? Thanks!
You need a loop to be able to redraw the image at various opacity levels. For a loop you need something that doesn't block UI as well as refreshing with each monitor update, so, requestAnimationFrame to the rescue.
Here is one way to go about this:
var img = new Image();
img.onload = function () {
// when image has loaded we can use it.
// this is a self-invoking function used to fade in the image
(function loop() {
// we can update the property directly
textOne.globalAlpha += 0.01;
// as we have opacity involved we need to clear the canvas each time
textOne.clearRect(0, 0, txtCanvas.width, txtCanvas.height);
// redraw the image
textOne.drawImage(img, 0, 0);
// if not full opacity, loop (canvas will clamp the value for us)
if (textOne.globalAlpha < 1.0) {
requestAnimationFrame(loop);
}
else {
// when done, call the next step from here...
}
})();
}
// set source as last step for image loading
img.src = "http://tvangeste.com/gallery/selani/tv4_2.jpg"
Modified fiddle
Hope this helps!
Related
I'm loading an image into my albImg array.
in my loop i then do this:
for(var j = 1; j < albImg.length; j++){
if(albImg[j].complete == true && albImg[j].width > 0){
loadedimages++;
}
}
to make sure all my images are loaded.
I then call my flipImg() function like this:
if(loadedimages == albImg.length-1){
flipImg();
}
I then flip an image and
ctx2.save();
ctx2.scale(-1, 1);
for (var i = RszSpriteCount; i < sprites.length; i++) {
ctx2.drawImage(albImg[sprites[i][0]], sprites[i][1], sprites[i][2], sprites[i][3], sprites[i][4], 0 - (sprites[i][1] + sprites[i][3]), sprites[i][2], sprites[i][3], sprites[i][4]);
}
ctx2.restore();
var flipSz = albImg.length;
albImg[flipSz] = new Image();
albImg[flipSz].src = cnv2.toDataURL();
Here's where my problem begins.
The new image I created - albImg[5] - can't be displayed until it is loaded.
But it is created as if it already is loaded.
That is to say that:
albImg[5].width is already set (to 750) before I can display it.
albImg[5].complete is set to true, before I can display it.
albImg[5].onload = ctx.drawImage(albImg[5], 0, 0); will try to draw the image before it is loaded.
How can I check if my flipped image really is loaded before I display it? in Javascript?
(due to circumstances I'm not using jQuery for this)
Please help.
Your main error is in how you do set the onload event handler :
albImg[5].onload = ctx.drawImage(albImg[5], 0, 0)
will set the return value of drawImage() (undefined) to the onload listener.
What you want is
albImg[5].onload = e => ctx.drawImage(albImg[5], 0, 0);
or
albImg[5].onload = function(){ ctx.drawImage(this, 0, 0) };
For the complete and width properties set to true, it's because while the loading of an Image is always async, in your case, the image is probably already HTTP cached.
Since the HTTP loading and the javascript execution are not executed on the same thread, it is possible that the Image actually loaded before the browser returns its properties.
But even then, the onload event will fire (best to set it before the src though).
var cacher = new Image();
cacher.onload = function(){
var img = new Image();
img.onload = function(){
console.log('"onload" fires asynchronously even when cached');
};
img.src = c.toDataURL();
console.log('cached : ', img.complete, img.naturalWidth);
}
cacher.src = c.toDataURL();
console.log('before cache :', cacher.complete, cacher.naturalWidth);
<canvas id="c"></canvas>
So when dealing with an new Image (not one in the html markup), always simply listen to its onload event.
Now, with the few information you gave in your question, it would seem that you don't even need these images, nor to deal with any of their loadings (except for the sprites of course), since you can directly and synchronously call ctx.drawImage(CanvasElement, x, y).
const ctx = c.getContext('2d');
ctx.moveTo(0, 0);
ctx.lineTo(300, 75);
ctx.lineTo(0, 150);
ctx.fillStyle = 'rgba(120,120,30, .35)';
ctx.fill();
const flipped = c.cloneNode(); // create an offscreen canvas
const f_ctx = flipped.getContext('2d');
f_ctx.setTransform(-1, 0,0,1, c.width, 0);// flip it
f_ctx.drawImage(c,0,0);// draw the original image
// now draw this flipped version on the original one just like an Image.
ctx.drawImage(flipped, 0,0);
// again in 3s
setTimeout(()=>ctx.drawImage(flipped, 150,0), 3000);
<canvas id="c"></canvas>
I am working with a single canvas that allows the user to click on a window pane in a window image. The idea is to show where the user has clicked. The image will then be modified (by drawing a grill on the window) and then saved to in JPEG. I am saving the canvas image prior to the click function because I don't want the selection box to show in the final image. However, Firefox often displays a blank canvas when restoring the canvas where IE and Chrome do not. This works perfectly in Chrome and IE. Any suggestions? Does Firefox have a problem with toDataURL()? Maybe some async issue going on here? I am also aware that saving a canvas in this fashion is memory intensive and there may be a better way to do this but I'm working with what I have.
Code:
/**
* Restores canvas from drawingView.canvasRestorePoint if there are any restores saved
*/
restoreCanvas:function()
{
var inverseScale = (1/drawingView.scaleFactor);
var canvas = document.getElementById("drawPop.canvasOne");
var c = canvas.getContext("2d");
if (drawingView.canvasRestorePoint[0]!=null)
{
c.clearRect(0,0,canvas.width,canvas.height);
var img = new Image();
img.src = drawingView.canvasRestorePoint.pop();
c.scale(inverseScale,inverseScale);
c.drawImage(img, 0, 0);
c.scale(drawingView.scaleFactor, drawingView.scaleFactor);
}
},
/**
* Pushes canvas into drawingView.canvasRestorePoint
*/
saveCanvas:function()
{
var canvas = document.getElementById("drawPop.canvasOne");
var urlData = canvas.toDataURL();
drawingView.canvasRestorePoint.push(urlData);
},
EXAMPLE OF USE:
readGrillInputs:function()
{
var glassNum = ir.get("drawPop.grillGlassNum").value;
var panelNum = ir.get("drawPop.grillPanelNum").value;
drawingView.restoreCanvas();
drawEngine.drawGrill(glassNum, panelNum,null);
drawingView.saveCanvas();
},
sortClick:function(event)
{
..... //Sorts where user has clicked and generates panel/glass num
.....
drawingView.showClick(panelNum, glassNum);
},
showClick:function(panelNum, glassNum)
{
var glass = item.panels[panelNum].glasses[glassNum];
var c = drawEngine.context;
drawingView.restoreCanvas();
drawingView.saveCanvas();
c.strokeStyle = "red";
c.strokeRect(glass.x, glass.y, glass.w, glass.h);
},
By just looking at the code setting the img.src is an async action to retrieve the image, so when you try to draw it 2 lines later to the canvas, it probably hasn't been loaded yet (having it in cache will make it return fast enough that it might work).
You should instead use an img.onload function to draw the image when it has loaded.
restoreCanvas:function()
{
var inverseScale = (1/drawingView.scaleFactor);
var canvas = document.getElementById("drawPop.canvasOne");
var c = canvas.getContext("2d");
if (drawingView.canvasRestorePoint[0]!=null)
{
c.clearRect(0,0,canvas.width,canvas.height);
var img = new Image();
img.onload = function() {
c.scale(inverseScale,inverseScale);
c.drawImage(img, 0, 0);
c.scale(drawingView.scaleFactor, drawingView.scaleFactor);
};
img.src = drawingView.canvasRestorePoint.pop();
}
},
I have created Circle using arc like following way.
var startAngle = 0;
var arc = Math.PI / 6;
var ctx;
var leftValue=275;
var topValue=300;
var wheelImg = ["http://i.stack.imgur.com/wwXlF.png","http://i.stack.imgur.com/wwXlF.png","cat.png","cock.png",
"croco.png","dog.png","eagle.png","elephant.png","lion.png",
"monkey.png","rabbit.png","sheep.png"];
function drawWheelImg()
{
var canvas = document.getElementById("canvas");
if(canvas.getContext)
{
var outsideRadius = 260;
var textRadius = 217;
var insideRadius = 202;
ctx = canvas.getContext("2d");
for(var i = 0; i < 12; i++)
{
var angle = startAngle + i * arc;
ctx.beginPath();
ctx.arc(leftValue, topValue, outsideRadius, angle, angle + arc, false);
ctx.arc(leftValue, topValue, insideRadius, angle + arc, angle, true);
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(leftValue, topValue, outsideRadius, angle, angle + arc, false);
ctx.shadowBlur=3;
ctx.shadowColor="#A47C15";
ctx.stroke();
ctx.closePath();
ctx.save();
ctx.translate(leftValue + Math.cos(angle + arc / 2) * textRadius,
topValue + Math.sin(angle + arc / 2) * textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
var img = new Image();
img.src = wheelImg[i];
ctx.drawImage(img,-44, -25,50,40);
ctx.restore();
}
}
}
function spin()
{
drawWheelImg();
}
drawWheelImg();
<button class="btnSpin" onclick="spin();">Spin</button>
<canvas id="canvas" width="550" height="580"></canvas>
Problem is that it will not load when first page load. But when i click on spin button it will load all images.
Don't know what is the issue. Any help is greatly appreciated.
Note: Here in the question same issue is solve by img.onload function but that for only single image. If there is number of multiple images in array then it's not working.
You want to preload the images.
To do this you can start the loading at the bottom of the page just before the closing </dody> tag.
<script>
// an array of image URLs
var imageNames = ["image1.png", "image2.jpg", ...moreImageNames];
// an array of images
var images = [];
// for each image name create the image and put it into the images array
imageNames.forEach(function(name){
image = new Image(); // create image
image.src = name; // set the src
images.push(image); // push it onto the image array
});
</script>
In the code that uses the images you just need to check if they have loaded yet. To do this just check the image complete attribute.
// draw the first image in the array
if(images[0].complete){ // has it loaded
ctx.drawImage(images[0],0,0); // yes so draw it
}
Note that complete does not mean loaded. If you proved a bad URL or there is another error the complete flag will still be true. Complete means the browser has finished dealing with the image. You will have to add an onload event if there is a chance that images may fail.
Handling Errors
If you are unsure that an image will not load you will have to devise a strategy to deal with the missing content. You will need to answer questions like, Can my app function without the image? Are there alternative sources to get the image from? Is there a way to determine how often this is likely to happen? How do I stop this from happening?
On the simplest level you can flag an image as loaded by adding your own semaphore to the image object during the onload event
// an array of image URLs
var imageNames = ["image1.png", "image2.jpg", ...moreImageNames];
// an array of images
var images = [];
// function to flag image as loaded
function loaded(){
this.loaded = true;
}
// for each image name create the image and put it into the images array
imageNames.forEach(function(name){
image = new Image(); // create image
image.src = name; // set the src
image.onload = loaded; // add the image on load event
images.push(image); // push it onto the image array
});
Then in code you will check for the loaded semaphore before using the image.
// draw the first image in the array
if(images[0].loaded){ // has it loaded
ctx.drawImage(images[0],0,0); // yes so draw it
}
Critical content
If you have images that are required for you app to function then you should redirect to an error page if there is a problem loading the image. You may have several servers so you can also try the different sources before giving up.
If you need to stop the app or try an alternative URL you will have to intercept the onerror image event.
To simplify your use of the images (100% sure the images are loaded when that app runs) you should start the parts that use the images only when all the images are loaded. One way to do this is to count all the images that are being loaded, and count down as the images load. When the counter reaches zero you know all the images have loaded and you can then call you app.
The following will load images, if they fail it will try another server (source) until there are no more options at which point you should redirect to the appropriate error page to inform the client that a monkey has put a spanner in the works. It counts the loading images and when all the images have loaded then it will start the app running, sure in the knowledge that all the image content is safe to use.
// Server list in order of preferance
var servers = ["https://fakesiteABC.com/", "http://boogusplace.com/", "http://foobarpoo.com.au/"];
// an array of image URLs
var imageNames = ["image1.png", "image2.jpg", ...moreImageNames];
// an array of images
var images = [];
// loading count tracks the number of images that are being loaded
var loadingImageCount = 0;
// function to track loaded images
function loaded(){
loadingImageCount -= 1;
if(loadingImageCount === 0){
// call your application start all images have loaded
appStart();
}
}
// function to deal with error
function loadError(){ // the keyword "this" references the image that generated the event.
if(this.retry === undefined){ // is this the first error
this.retry = 1; // point to the second server
}else{
this.retry += 1; // on each error keep trying servers (locations)
}
// are the any other sources to try?
if(this.retry >= servers.length){ // no 11
// redirect to error page.
return;
}
// try a new server by setting the source and stripping the
// last server name from the previous src URL
this.src = servers[this.retry] + this.src.replace( servers[ this.retry - 1],"");
// now wait for load or error
}
// for each image name create the image and put it into the images array
imageNames.forEach(function(name){
image = new Image(); // create image
image.src = servers[0] + name; // set the src from the first server
image.onload = loaded; // add the image on load event
image.onerror = loadError; // add the image error handler
images.push(image); // push it onto the image array
loadingImageCount += 1; // count the images being loaded.
});
There are many other strategies for dealing with missing content. This just shows some of the mechanism used and does not define a perfect solution (there is none)
I am draw background on canvas and also small images on that background. But sometimes, background draw on small images. Why ?
JavaScript code -
var canvasupdate = document.getElementById("myCanvas");
ctxupdate = canvasupdate.getContext("2d");
var background = new Image();
background.src = sitePath + "ATOM/chapter1/book/" + `bgimagename`;
background.onload = function() {
ctxupdate.drawImage(background, 0, 0); // set background image
};
var imageobj = new Array();
for (var d = 0; d < calloutImageArray.length; d++) // getting small images in array
{
imageobj[d] = new Image();
(function(d) {
imageobj[d].src = sitePath + "ATOM/chapter1/book/" + calloutImageArray[d];
imageobj[d].onload = function() {
ctxupdate.drawImage(imageobj[d], calloutImageArrayX[d], calloutImageArrayY[d], calloutImageArrayW[d], calloutImageArrayH[d]);
};
})(d);
}
In above code, background image code should be execute first then call out image(small image) code execute but some time execute small images code and then background image code why?
All the images are loaded asynchronously. So sometimes the small images are loaded (and drawn) before the background image. Please take a look into e.g. Network tab in Chrome in which order the resources load is done.
The simplest solution for this problem is to move the loading of small images into the callback function for load event of the background image.
The onLoad function runs asynchronously. That means JavaScript will continue to run the rest of your code and will run the callback function when the background image is loaded. That's why your second part of the code runs first. So instead try to add your code inside the onload function like this:
var canvasupdate = document.getElementById("myCanvas");
ctxupdate = canvasupdate.getContext("2d");
var background = new Image();
background.src = sitePath + "ATOM/chapter1/book/" + `bgimagename`;
background.onload = function() {
ctxupdate.drawImage(background, 0, 0); // set background image
var imageobj = new Array();
for (var d = 0; d < calloutImageArray.length; d++) // getting small images in array
{
imageobj[d] = new Image();
(function(d) {
imageobj[d].src = sitePath + "ATOM/chapter1/book/" + calloutImageArray[d];
imageobj[d].onload = function() {
ctxupdate.drawImage(imageobj[d], calloutImageArrayX[d], calloutImageArrayY[d], calloutImageArrayW[d], calloutImageArrayH[d]);
};
})(d);
}
};
that way you can be sure that the background will be set first and then your small images. Note that I didn't try your code to check if it does what you want, I just rearranged the code blocks to show you the logic and why does not run as you would expect.
Hope it helps
Im making a basic game
http://www.jasonhuman.co.za/lazerlazer
The idea is to learn more about javascript and the canvas
The problem im having is that i bound onkeypress on the body element
it only allows for a single keypress to be picked up at a time
and its not all that smooth - the ship lags for a second before starting its movement
how can i make it that the ship immediatly starts moving in the correct direction
as soon as the keypress happens?
here is my code that controls the keypress event:
function keyPress(e){
//w=119, s = 115, a = 97, d = 100
if(e.charCode==119){
player[0].ypos -=15;
playerImg.src = 'images/playerUp.png';
engines.play();
}
if(e.charCode==115){
player[0].ypos +=15;
playerImg.src = 'images/playerDown.png';
engines.play();
}
if(e.charCode==97){
player[0].xpos-=15;
playerImg.src = 'images/playerFW.png';
engines.play();
}
if(e.charCode==100){
player[0].xpos+=15;
playerImg.src = 'images/playerFW.png';
engines.play();
}
//fire a bullet
if(e.charCode==32)
{
//fire a bullet
bullets.push(new init(player[0].xpos+88,player[0].ypos+25));
//gunshots
var gunshot = new Audio('lazer.mp3');
gunshot.play();
}
}
As #Rob Baille says, listen for keydown rather than keypress events.
Your delay is caused because you're reloading the images with every keypress.
Instead preload all the images just once.
Then set your playerImg to the appropriate image as needed in the key handler.
Also, you repeated images/playerFW.png...was that intentional?
Here's an example of your code refactored using an image preloader:
// image loader
var imageURLs=[]; // put the paths to your images here
var imagesOK=0;
var imgs=[];
imageURLs.push("images/playerUp.png");
imageURLs.push("images/playerDown.png");
imageURLs.push("images/playerFW.png");
imageURLs.push("images/playerFW.png");
loadAllImages(start);
function loadAllImages(callback){
for (var i=0; i<imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK>=imageURLs.length ) {
callback();
}
};
img.onerror=function(){alert("image load failed");}
img.crossOrigin="anonymous";
img.src = imageURLs[i];
}
}
function start(){
// All images have been fully pre-loaded
// Allow the game to begin
// Maybe hide the "Play" button until this function is triggered
}
function keyPress(e){
//w=119, s = 115, a = 97, d = 100
if(e.charCode==119){
player[0].ypos -=15;
playerImg = imgs[0];
engines.play();
}
if(e.charCode==115){
player[0].ypos +=15;
playerImg = imgs[1];
engines.play();
}
if(e.charCode==97){
player[0].xpos-=15;
playerImg = imgs[2];
engines.play();
}
if(e.charCode==100){
player[0].xpos+=15;
playerImg = imgs[3];
engines.play();
}
//fire a bullet
if(e.charCode==32)
{
//fire a bullet
bullets.push(new init(player[0].xpos+88,player[0].ypos+25));
//gunshots
var gunshot = new Audio('lazer.mp3');
gunshot.play();
}
}
Well there are two ways I know on how to approach this. You can update your frames more often or you can use a different key down method. To update your frames faster
var sim_interval = 1 / 40; // number of times per second that we step the animation.
This declares as a variable the number of times per second that we step the animation, as stated above. This next bit of code is how to reder the initial state.
render(); // render the initial state.
interval_callback_id = window.setInterval(step, sim_interval); // set the callback and save the identifier.
}
Every reder code will be a bit different your function render(){ might be different it is how to uptdate the system. The other way to do a key down method can be found here How to use spacebar and if statements in JS?