Canvas is not drawing images - javascript

I have problem with canvas, I'm trying to draw images but is not working as you can see I'm loading images into array and wait for all is loaded after this I'm changing my images each iteration but is not drawing any one, please look at my code. I cannot find error :(
(() => {
"use strict";
const images = [];
const promises = [];
const url = './assets/';
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
const FPS = 30;
const INTERVAL = 10000 / FPS;
const canvasDraw = () => {
let i = 0;
setInterval(() => {
context.drawImage(images[i] , 300, 300);
i++;
if (i === images.length) i = 0;
}, INTERVAL);
};
const loadImage = (image) => {
return new Promise((resolve) => {
const img = new Image();
img.src = url + image + '.png';
img.onload = function () {
images.push(img);
resolve();
};
});
};
for(let i = 1; i < 14; i++) {
promises.push(loadImage(i));
}
Promise
.all(promises)
.then(() => {
canvasDraw();
});
})();
and my html file contains canvas like this one
<canvas id="canvas"></canvas>

You need to give your canvas a width and height.
Using placeholder images:
(() => {
"use strict";
const images = [];
const promises = [];
const url = './assets/';
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
const FPS = 30;
const INTERVAL = 10000 / FPS;
const canvasDraw = () => {
let i = 0;
setInterval(() => {
context.drawImage(images[i] , 0, 0);
i++;
if (i === images.length) i = 0;
}, INTERVAL);
};
const loadImage = (image) => {
return new Promise((resolve) => {
const img = new Image();
img.src = 'https://placehold.it/' + (image * 20) + 'x' + (image * 20);
img.onload = function () {
images.push(img);
resolve();
};
});
};
for(let i = 1; i < 14; i++) {
promises.push(loadImage(i));
}
Promise
.all(promises)
.then(() => {
canvasDraw();
});
})();
<canvas id="canvas" width="300" height="300"></canvas>
Depending on what you're doing, you may want to clear the canvas between renderings.
(() => {
"use strict";
const images = [];
const promises = [];
const url = './assets/';
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
const FPS = 30;
const INTERVAL = 10000 / FPS;
const canvasDraw = () => {
let i = 0;
setInterval(() => {
context.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
context.drawImage(images[i] , 0, 0);
i++;
if (i === images.length) i = 0;
}, INTERVAL);
};
const loadImage = (image) => {
return new Promise((resolve) => {
const img = new Image();
img.src = 'https://placehold.it/' + (image * 20) + 'x' + (image * 20);
img.onload = function () {
images.push(img);
resolve();
};
});
};
for(let i = 1; i < 14; i++) {
promises.push(loadImage(i));
}
Promise
.all(promises)
.then(() => {
canvasDraw();
});
})();
<canvas id="canvas" width="300" height="300"></canvas>

Related

Adding a section-wise division on loop

I'm using Canvas animation for image sequences on the scroll.
And this animation is working fine but I need to reduce the loading time because I'm using almost 884 images for this animation.
So, how can I add section-wise division in this loop? For example, the first 100 images will upload on start and after 5 seconds next 100 images will upload. So every after 5 seconds next 100 images will upload til 884 images.
const html = document.documentElement;
const canvas = document.getElementById("hero-lightpass");
const context = canvas.getContext("2d");
const frameCount = 884;
const currentFrame = index => (
`compressed/${index.toString().padStart(9, 'web_0000')}.webp`
)
const preloadImages = () => {
for (let i = 1; i < frameCount; i++) {
const img = new Image();
img.src = currentFrame(i);
}
};
const img = new Image()
img.src = currentFrame(1);
canvas.width= window.innerWidth;
canvas.height=window.innerHeight;
img.onload=function(){
scaleToFill(this);
}
function scaleToFill(img){
var scale = Math.max(canvas.width / img.width, canvas.height / img.height);
var x = (canvas.width / 2) - (img.width / 2) * scale;
var y = (canvas.height / 2) - (img.height / 2) * scale;
context.drawImage(img, x, y, img.width * scale, img.height * scale);
}
const updateImage = index => {
img.src = currentFrame(index);
}
window.addEventListener('scroll', () => {
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const frameIndex = Math.min(
frameCount - 1,
Math.ceil(scrollFraction * frameCount)
);
requestAnimationFrame(() => updateImage(frameIndex + 1))
});
preloadImages()
HTML
<canvas id="hero-lightpass"></canvas>
You can do that with an interval ...
We add a new variable loadedImages and change the preloadImages
let loadedImages = 0
let loadedImages = 0
const frameCount = 884;
const currentFrame = index => (
`compressed/${index.toString().padStart(9, 'web_0000')}.webp`
)
const preloadImages = () => {
for (let i = loadedImages + 1; i < Math.min(loadedImages + 100, frameCount); i++) {
const img = new Image();
img.src = currentFrame(i);
loadedImages++
}
};
setInterval(preloadImages, 5000)
...
preloadImages()

JS clearRect not clearing entire canvas

I am slightly new with js and ran into an issue. I was trying to create a stars in the sky animation. I did so by creating small circles randomly on a canvas and then randomly selecting stars which are flickered (opacity is changed). I then realised the website was not working when scaled so I decided to implement an onevent for the window resize and when the window is resized I run the same functions again to recreate the same process but while clearing all the previous stars so they don't multiply. The issue here is that the clearRect method doesn't seem to be clearing the previously drawn stars for me. Any help on this would be very much appreciated :). Here is my code.
let starCollection = [];
let randomStars = [];
let numberofStars = 100;
let flickeringStars = 50;
class Star{
constructor(x,y,color,radius){
this._canvas = document.querySelector('canvas');
this._canvas.width = window.innerWidth;
this._canvas.height = window.innerHeight;
this._c = this._canvas.getContext('2d');
this._radius = radius;
this._x = x;
this._y = y;
this._color = color;
}
//drawing individual stars
draw(){
//Drawing
this._c.beginPath();
this._c.arc(this._x,this._y,this._radius,0,Math.PI * 2,false);
this._c.fillStyle = this._color;
this._c.strokeStyle = 'black';
this._c.stroke();
this._c.fill();
this._c.closePath();
}
//Fade in and out for stars
flicker(){
setTimeout(()=>{this._color='#EBEBEB';},300);
setTimeout(()=>{this._color='#D9D9D9';},600);
setTimeout(()=>{this._color='#B6B6B6';},900);
setTimeout(()=>{this._color='#898787';},1200);
setTimeout(()=>{this._color='#4F4F4F';},1500);
setTimeout(()=>{this._color='black';},1800);
setTimeout(()=>{this._color='#4F4F4F';},2100);
setTimeout(()=>{this._color='#898787';},2400);
setTimeout(()=>{this._color='#B6B6B6';},2700);
setTimeout(()=>{this._color='#D9D9D9';},3000);
setTimeout(()=>{this._color='#EBEBEB';},3300);
setTimeout(()=>{this._color='#FFFFFF';},3600);
setTimeout(()=>{this.draw();},300);
setTimeout(()=>{this.draw();},600);
setTimeout(()=>{this.draw();},900);
setTimeout(()=>{this.draw();},1200);
setTimeout(()=>{this.draw();},1500);
setTimeout(()=>{this.draw();},1800);
setTimeout(()=>{this.draw();},2100);
setTimeout(()=>{this.draw();},2400);
setTimeout(()=>{this.draw();},2700);
setTimeout(()=>{this.draw();},3000);
setTimeout(()=>{this.draw();},3300);
setTimeout(()=>{this.draw();},3600);
}
}
window.addEventListener("showStars", ()=>{
//Stars animation
//Adding the stars to the array as objects
for(let i=0;i<numberofStars;i++){
let x = Math.floor(Math.random()*window.innerWidth);
let y = Math.floor(Math.random()*window.innerHeight);
let starSize = (Math.random()+1)-0.7;
starCollection.push(new Star(x,y,"white",starSize));
}
//Drawing all the stars on the screen
for(let i=0;i<starCollection.length;i++){
starCollection[i].draw();
}
//Storing random stars
const shuffleStars = ()=>{
randomStars = [];
for(let i=0;i<flickeringStars;i++){
randomStars.push(Math.floor(Math.random()*starCollection.length))
}
}
shuffleStars();
//Flickering stars randomly
const starflicker = ()=>{
console.log(starCollection);
console.log(randomStars);
setTimeout(()=>{
requestAnimationFrame(starflicker);
for(let i=0;i<randomStars.length;i++){
starCollection[randomStars[i]].flicker();
}
shuffleStars();
},500);
}
starflicker();
})
window.addEventListener("resize", ()=>{
let canvas = document.querySelector("canvas");
let context = canvas.getContext("2d");
canvas.width = window.innerWitdh;
canvas.height = window.innerHeight;
context.clearRect(0,0,window.innerWidth, window.innerHeight);
starCollection = [];
randomStars = [];
let event = new CustomEvent("showStars");
dispatchEvent(event);
});
let starCollection = [];
let randomStars = [];
let numberofStars = 100;
let flickeringStars = 50;
class Star {
constructor(x, y, color, radius) {
this._canvas = document.querySelector('canvas');
this._canvas.width = window.innerWidth;
this._canvas.height = window.innerHeight;
this._c = this._canvas.getContext('2d');
this._radius = radius;
this._x = x;
this._y = y;
this._color = color;
}
//drawing individual stars
draw() {
//Drawing
this._c.beginPath();
this._c.arc(this._x, this._y, this._radius, 0, Math.PI * 2, false);
this._c.fillStyle = this._color;
this._c.strokeStyle = 'black';
this._c.stroke();
this._c.fill();
this._c.closePath();
}
//Fade in and out for stars
flicker() {
setTimeout(() => {
this._color = '#EBEBEB';
}, 300);
setTimeout(() => {
this._color = '#D9D9D9';
}, 600);
setTimeout(() => {
this._color = '#B6B6B6';
}, 900);
setTimeout(() => {
this._color = '#898787';
}, 1200);
setTimeout(() => {
this._color = '#4F4F4F';
}, 1500);
setTimeout(() => {
this._color = 'black';
}, 1800);
setTimeout(() => {
this._color = '#4F4F4F';
}, 2100);
setTimeout(() => {
this._color = '#898787';
}, 2400);
setTimeout(() => {
this._color = '#B6B6B6';
}, 2700);
setTimeout(() => {
this._color = '#D9D9D9';
}, 3000);
setTimeout(() => {
this._color = '#EBEBEB';
}, 3300);
setTimeout(() => {
this._color = '#FFFFFF';
}, 3600);
setTimeout(() => {
this.draw();
}, 300);
setTimeout(() => {
this.draw();
}, 600);
setTimeout(() => {
this.draw();
}, 900);
setTimeout(() => {
this.draw();
}, 1200);
setTimeout(() => {
this.draw();
}, 1500);
setTimeout(() => {
this.draw();
}, 1800);
setTimeout(() => {
this.draw();
}, 2100);
setTimeout(() => {
this.draw();
}, 2400);
setTimeout(() => {
this.draw();
}, 2700);
setTimeout(() => {
this.draw();
}, 3000);
setTimeout(() => {
this.draw();
}, 3300);
setTimeout(() => {
this.draw();
}, 3600);
}
}
window.addEventListener("showStars", () => {
//Stars animation
//Adding the stars to the array as objects
for (let i = 0; i < numberofStars; i++) {
let x = Math.floor(Math.random() * window.innerWidth);
let y = Math.floor(Math.random() * window.innerHeight);
let starSize = (Math.random() + 1) - 0.7;
starCollection.push(new Star(x, y, "white", starSize));
}
//Drawing all the stars on the screen
for (let i = 0; i < starCollection.length; i++) {
starCollection[i].draw();
}
//Storing random stars
const shuffleStars = () => {
randomStars = [];
for (let i = 0; i < flickeringStars; i++) {
randomStars.push(Math.floor(Math.random() * starCollection.length))
}
}
shuffleStars();
//Flickering stars randomly
const starflicker = () => {
console.log(starCollection);
console.log(randomStars);
setTimeout(() => {
requestAnimationFrame(starflicker);
for (let i = 0; i < randomStars.length; i++) {
starCollection[randomStars[i]].flicker();
}
shuffleStars();
}, 500);
}
starflicker();
})
window.addEventListener("resize", () => {
let canvas = document.querySelector("canvas");
let context = canvas.getContext("2d");
canvas.width = window.innerWitdh;
canvas.height = window.innerHeight;
context.clearRect(0, 0, window.innerWidth, window.innerHeight);
starCollection = [];
randomStars = [];
let event = new CustomEvent("showStars");
dispatchEvent(event);
});
body{
background-color:black;
}
<html>
<body>
<canvas></canvas>
</body>
</html>
Your problem is in the flicker function. Those setTimeouts still fire even after your resize function. So they call this.draw() even though those stars were removed from your array. The function still remembers what 'this' is.
You can clear all your timeouts like this:
window.addEventListener("resize", () => {
let canvas = document.querySelector("canvas");
let context = canvas.getContext("2d");
canvas.width = window.innerWitdh;
canvas.height = window.innerHeight;
context.clearRect(0, 0, window.innerWidth, window.innerHeight);
starCollection = [];
randomStars = [];
var id = window.setTimeout(function () { }, 0);
while (id--) {
window.clearTimeout(id); // will do nothing if no timeout with id is present
}
let event = new CustomEvent("showStars");
dispatchEvent(event);
});
Here's an example with 10 stars, 5 of which are flickering. I made the stars bigger so it's easier to see.
<html>
<head>
<style>
body {
background: black;
}
</style>
</head>
<body>
<canvas></canvas>
<script>
let starCollection = [];
let randomStars = [];
let numberofStars = 10;
let flickeringStars = 5;
class Star {
constructor(x, y, color, radius) {
this._canvas = document.querySelector('canvas');
this._canvas.width = window.innerWidth;
this._canvas.height = window.innerHeight;
this._c = this._canvas.getContext('2d');
this._radius = radius;
this._x = x;
this._y = y;
this._color = color;
}
//drawing individual stars
draw() {
//Drawing
this._c.beginPath();
this._c.arc(this._x, this._y, this._radius, 0, Math.PI * 2, false);
this._c.fillStyle = this._color;
this._c.strokeStyle = 'black';
this._c.stroke();
this._c.fill();
this._c.closePath();
}
//Fade in and out for stars
flicker() {
setTimeout(() => {
this._color = '#EBEBEB';
}, 300);
setTimeout(() => {
this._color = '#D9D9D9';
}, 600);
setTimeout(() => {
this._color = '#B6B6B6';
}, 900);
setTimeout(() => {
this._color = '#898787';
}, 1200);
setTimeout(() => {
this._color = '#4F4F4F';
}, 1500);
setTimeout(() => {
this._color = 'black';
}, 1800);
setTimeout(() => {
this._color = '#4F4F4F';
}, 2100);
setTimeout(() => {
this._color = '#898787';
}, 2400);
setTimeout(() => {
this._color = '#B6B6B6';
}, 2700);
setTimeout(() => {
this._color = '#D9D9D9';
}, 3000);
setTimeout(() => {
this._color = '#EBEBEB';
}, 3300);
setTimeout(() => {
this._color = '#FFFFFF';
}, 3600);
setTimeout(() => {
this.draw();
}, 300);
setTimeout(() => {
this.draw();
}, 600);
setTimeout(() => {
this.draw();
}, 900);
setTimeout(() => {
this.draw();
}, 1200);
setTimeout(() => {
this.draw();
}, 1500);
setTimeout(() => {
this.draw();
}, 1800);
setTimeout(() => {
this.draw();
}, 2100);
setTimeout(() => {
this.draw();
}, 2400);
setTimeout(() => {
this.draw();
}, 2700);
setTimeout(() => {
this.draw();
}, 3000);
setTimeout(() => {
this.draw();
}, 3300);
setTimeout(() => {
this.draw();
}, 3600);
}
}
window.addEventListener("showStars", () => {
//Stars animation
//Adding the stars to the array as objects
for (let i = 0; i < numberofStars; i++) {
let x = Math.floor(Math.random() * window.innerWidth);
let y = Math.floor(Math.random() * window.innerHeight);
// let starSize = (Math.random() + 1) - 0.7;
let starSize = 10;
starCollection.push(new Star(x, y, "white", starSize));
}
//Drawing all the stars on the screen
for (let i = 0; i < starCollection.length; i++) {
starCollection[i].draw();
}
//Storing random stars
const shuffleStars = () => {
randomStars = [];
for (let i = 0; i < flickeringStars; i++) {
randomStars.push(Math.floor(Math.random() * starCollection.length))
}
}
shuffleStars();
//Flickering stars randomly
const starflicker = () => {
console.log(starCollection);
console.log(randomStars);
setTimeout(() => {
requestAnimationFrame(starflicker);
for (let i = 0; i < randomStars.length; i++) {
starCollection[randomStars[i]].flicker();
}
shuffleStars();
}, 500);
}
starflicker();
})
window.addEventListener("resize", () => {
let canvas = document.querySelector("canvas");
let context = canvas.getContext("2d");
canvas.width = window.innerWitdh;
canvas.height = window.innerHeight;
context.clearRect(0, 0, window.innerWidth, window.innerHeight);
starCollection = [];
randomStars = [];
var id = window.setTimeout(function () { }, 0);
while (id--) {
window.clearTimeout(id); // will do nothing if no timeout with id is present
}
let event = new CustomEvent("showStars");
dispatchEvent(event);
});
dispatchEvent(new CustomEvent("showStars"))
</script>
</body>
</html>
Check this post for more info on how that timeout clearing code works: javascript: Clear all timeouts?

Bouncing ball code causes jitter in HTML5 canvas

I have a HTML Canvas on my website that I use to contain balls that drop into the canvas, they bounce around and have a real good time settling down at the bottom in a range of ways.
I was under the impression this was working perfectly. However, it has now been brought to my attention these balls jitter and freak out on other people's computers. I checked the browser they are using and it is the same as mine (Chrome V79). So just to clarify - I have never had it bug on my computer but it consistently has this jitter on other peoples computers, some with more powerful computers as well as lower spec computers.
This is it (this CodePen doesn't have the jitter on their computers, so you may not see it also): https://codepen.io/jason-is-my-name/pen/gObKGRg
This is a video of the bug: https://streamable.com/vtg0g
$(document).ready(function () {
$.ajaxSetup({
cache: true
});
$.when(
$.getScript("https://code.createjs.com/easeljs-0.6.0.min.js"),
$.getScript("https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.min.js"),
$.Deferred(function (deferred) {
$(deferred.resolve);
})
).done(function () {
new App();
});
});
function App() {
var self = this;
self.running = false;
self.initialized = false;
var stageClicked = false;
var stage, canvas;
var canvasWidth = 410;
var canvasHeight = 550;
var ballBounce = 0.8;
var balls = [];
var matterJsBalls = [];
var _gravityY = 1;
var _gravityX = 0;
var FPS = 60;
var infoText, detailsText;
var ballsInitalized = false;
var iOS = navigator.userAgent.match(/(iPod|iPhone|iPad)/);
var startTime = (new Date()).getTime();
var engine = Matter.Engine.create();
var world = engine.world;
var floor = Matter.Bodies.rectangle(canvasWidth / 2, canvasHeight + 50, canvasWidth, 100, {
isStatic: true,
render: {
visible: false
}
});
var leftWall = Matter.Bodies.rectangle(-50, canvasHeight / 2, 100, canvasHeight, {
isStatic: true,
render: {
visible: false
}
});
var rightWall = Matter.Bodies.rectangle(canvasWidth + 50, canvasHeight / 2, 100, canvasHeight, {
isStatic: true,
render: {
visible: false
}
});
Matter.World.add(world, [floor, leftWall, rightWall]);
self.initialize = function () {
toggleListeners(true);
self.initCanvas();
self.initGame();
};
var toggleListeners = function (enable) {
if (!enable) return;
};
self.refresh = function () {};
self.initCanvas = function () {
canvas = $("#ball-stage").get(0);
stage = new createjs.Stage(canvas);
window.addEventListener("resize", onStageResize, false);
onStageResize();
createjs.Touch.enable(stage);
createjs.Ticker.addListener(tick);
createjs.Ticker.setFPS(FPS);
self.initialized = true;
};
self.initGame = function () {
initBalls(canvasWidth, canvasHeight);
};
var onStageResize = function () {
stage.canvas.width = canvasWidth;
stage.canvas.height = canvasHeight;
};
var initBalls = function (stageX, stageY) {
var imagesArray = [
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg",
"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3-320-80.jpg"
];
for (var i = 0; i < imagesArray.length; i++) {
(function (imageArray) {
setTimeout(function () {
var arrayImage = new Image();
arrayImage.onload = function () {
addBall(arrayImage, stageX / 2, 52);
};
arrayImage.src = imageArray;
}, (i * 1000) + 1000);
})(imagesArray[i]);
}
};
var addBall = function (img, x, y) {
var shape = new createjs.Shape();
shape.id = balls.length;
shape.radius = 51.25;
shape.mass = shape.radius;
shape.x = x;
shape.y = y;
shape.vx = rand(-3, 3);
shape.vy = rand(-3, 3);
shape.stuck = false;
var transform = new createjs.Matrix2D();
transform.appendTransform(-shape.radius, -shape.radius, 1, 1, 0);
shape.graphics.beginBitmapFill(img, "repeat", transform).drawCircle(0, 0, shape.radius);
stage.addChild(shape);
balls.push(shape);
var circle = Matter.Bodies.circle(x, y, shape.radius, {
isStatic: false,
restitution: ballBounce
});
Matter.World.add(world, circle);
Matter.Body.applyForce(circle, {
x: circle.position.x,
y: circle.position.y
}, {
x: shape.vx * 0.05,
y: shape.vy * 0.05
});
matterJsBalls.push(circle);
};
var tick = function () {
Matter.Engine.update(engine, 16);
for (var i = 0; i < matterJsBalls.length; i++) {
var curBall = balls[i];
var curMatterJsBall = matterJsBalls[i];
curBall.x = curMatterJsBall.position.x;
curBall.y = curMatterJsBall.position.y;
}
stage.update();
};
var rand = function (min, max) {
return Math.random() * (max - min) + min;
return Math.random() * max + min;
};
self.initialize();
return self;
}
window.log = function f() {
log.history = log.history || [];
log.history.push(arguments);
if (this.console) {
var args = arguments,
newarr;
args.callee = args.callee.caller;
newarr = [].slice.call(args);
if (typeof console.log === "object")
log.apply.call(console.log, console, newarr);
else console.log.apply(console, newarr);
}
};
(function (a) {
function b() {}
for (
var c = "assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(
","
),
d; !!(d = c.pop());
) {
a[d] = a[d] || b;
}
})(
(function () {
try {
console.log();
return window.console;
} catch (a) {
return (window.console = {});
}
})()
);
.tech-stack-icons-container{width:410px;height:100%}#ball-stage{width:100%;height:100%;background-color:pink;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="tech-stack-icons-container">
<canvas id="ball-stage"></canvas>
</div>
How can I find a fix to this invisible issue?

Draw an image on a Canvas from an Object property

I want to draw an image on the canvas at each iteration, an image coming from an object.
Either I get "not the format HTML..." in the console, or nothing and it blocks the loop.
Is there a way to draw an image on a canvas without putting it first on the index.html, or without loading from an URL?
I tried the two standard solutions and they didn't work.
I didn't find a similar problem using an object property containing the image, to draw it on a canvas.
function Board(width, height) {
this.width = width;
this.height = height;
this.chartBoard = [];
// Création du plateau logique
for (var i = 0; i < this.width; i++) {
const row = [];
this.chartBoard.push(row);
for (var j = 0; j < this.height; j++) {
const col = {};
row.push(col);
}
}
}
let board = new Board(10, 10);
console.log(board);
// CONTEXT OF THE CANVAS
const ctx = $('#board').get(0).getContext('2d');
Board.prototype.drawBoard = function () {
for (var i = 0; i < this.width; i++) {
for (var j = 0; j < this.height; j++) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.strokeRect(j * 64, i * 64, 64, 64);
ctx.closePath();
}
}
};
board.drawBoard();
Board.prototype.test = test;
function test() {
console.log(this);
}
// OBJECT TO DRAW
function Obstacle(name, sprite) {
this.name = name;
this.sprite = sprite;
}
const lava = new Obstacle("Lave", "assets/lave.png");
const lava1 = new Obstacle("Lave1", "assets/lave.png");
const lava2 = new Obstacle("Lave2", "assets/lave.png");
const lava3 = new Obstacle("Lave3", "assets/lave.png");
const lava4 = new Obstacle("Lave4", "assets/lave.png");
const lava5 = new Obstacle("Lave5", "assets/lave.png");
const lava6 = new Obstacle("Lave6", "assets/lave.png");
const lava7 = new Obstacle("Lave7", "assets/lave.png");
const lava8 = new Obstacle("Lave8", "assets/lave.png");
const lava9 = new Obstacle("Lave9", "assets/lave.png");
const lavaArray = [lava, lava1, lava2, lava3, lava4, lava5, lava6, lava7, lava8, lava9];
// FUNCTION TO DRAW
Board.prototype.setPiece = function (piece) {
let randomX = Math.floor(Math.random() * board.width);
let randomY = Math.floor(Math.random() * board.height);
let drawX = randomX * 64;
let drawY = randomY * 64;
if (randomX >= this.width || randomY >= this.height) {
throw new Error('Pièce hors limite');
}
if (piece instanceof Obstacle) {
if (!(this.chartBoard[randomY][randomX] instanceof Obstacle)) {
this.chartBoard[randomY][randomX] = piece;
// CODE TO DRAW, BUG DOESN'T WORK
ctx.fillRect(drawX, drawY,64,64);
let image = Obstacle.sprite;
ctx.drawImage = (image, drawX, drawY);
}
}
} else {
throw new Error('Pièce non valide');
}
};
Board.prototype.setObstacles = function () {
for (let lava of lavaArray) {
const obstacle = board.setPiece(lava);
}
};
board.setObstacles();
Actual: No image is drawn. And if I try fillRect, it works well. So the loop works.
Expected: Be able to draw an image on a canvas from an object property.
It's not really clear what you're trying to do.
The code you have commented
ctx.drawImage = (image, drawX, drawY);
should be this
ctx.drawImage(image, drawX, drawY);
Looking a little broader you have this
let image = Obstacle.sprite;
ctx.drawImage(image, drawX, drawY); // assume this was fixed
But Obstacle is a class, not an instance of that class. You want
let image = piece.sprite;
ctx.drawImage(image, drawX, drawY);
But that leads the next issue. looking at the rest of the code piece.sprite is a string not an image. See this code.
// OBJECT TO DRAW
function Obstacle(name, sprite) {
this.name = name;
this.sprite = sprite;
}
const lava = new Obstacle("Lave", "assets/lave.png");
There are a several ways you can draw images to a canvas. If they come from a file you do have to wait for them to download. Otherwise you can generate them from another canvas. You can also use createImageData and putImageData as another way to make images.
Let's change the code to load a bunch of images and then start
I moved all the class code to the top and the start up code to the bottom.
There were a few places inside Board methods where the global variable board was used instead of this so I fixed those.
Here`s a function that loads an image and returns a Promise
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => { resolve(img); };
img.onerror = reject;
img.crossOrigin = 'anonymous'; // REMOVE IF SAME DOMAIN!
img.src = url;
});
}
You could use that to load one image like this
loadImage(urlOfImage).then(function(img) {
// use the loaded img
});
I used that function to write this function that takes an object of names to urls and returns an object of names to loaded images.
function loadImages(images) {
return new Promise((resolve) => {
const loadedImages = {};
const imagePromises = Object.entries(images).map((keyValue) => {
const [name, url] = keyValue;
return loadImage(url).then((img) => {
loadedImages[name] = img;
});
});
Promise.all(imagePromises).then(() => {
resolve(loadedImages);
});
});
}
I then call that and pass the object of names to loaded images to the start function. Only one image is loaded at the moment but you can add more.
const images = {
lave: 'https://i.imgur.com/enx5Xc8.png',
// player: 'foo/player.png',
// enemy: 'foo/enemny.png',
};
loadImages(images).then(start);
// CONTEXT OF THE CANVAS
const ctx = $('#board').get(0).getContext('2d');
function Board(width, height) {
this.width = width;
this.height = height;
this.chartBoard = [];
// Création du plateau logique
for (var i = 0; i < this.width; i++) {
const row = [];
this.chartBoard.push(row);
for (var j = 0; j < this.height; j++) {
const col = {};
row.push(col);
}
}
}
Board.prototype.drawBoard = function () {
for (var i = 0; i < this.width; i++) {
for (var j = 0; j < this.height; j++) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.strokeRect(j * 64, i * 64, 64, 64);
ctx.closePath();
}
}
};
// OBJECT TO DRAW
function Obstacle(name, sprite) {
this.name = name;
this.sprite = sprite;
}
// FUNCTION TO DRAW
Board.prototype.setPiece = function (piece) {
let randomX = Math.floor(Math.random() * this.width);
let randomY = Math.floor(Math.random() * this.height);
let drawX = randomX * 64;
let drawY = randomY * 64;
if (randomX >= this.width || randomY >= this.height) {
throw new Error('Pièce hors limite');
}
if (piece instanceof Obstacle) {
if (!(this.chartBoard[randomY][randomX] instanceof Obstacle)) {
this.chartBoard[randomY][randomX] = piece;
// CODE TO DRAW, BUG DOESN'T WORK
ctx.fillRect(drawX, drawY,64,64);
let image = piece.sprite;
ctx.drawImage(image, drawX, drawY);
}
} else {
throw new Error('Pièce non valide');
}
};
Board.prototype.setObstacles = function (lavaArray) {
for (let lava of lavaArray) {
const obstacle = this.setPiece(lava);
}
};
function start(images) {
let board = new Board(10, 10);
// console.log(board);
const lava = new Obstacle("Lave", images.lave);
const lava1 = new Obstacle("Lave1", images.lave);
const lava2 = new Obstacle("Lave2", images.lave);
const lava3 = new Obstacle("Lave3", images.lave);
const lava4 = new Obstacle("Lave4", images.lave);
const lava5 = new Obstacle("Lave5", images.lave);
const lava6 = new Obstacle("Lave6", images.lave);
const lava7 = new Obstacle("Lave7", images.lave);
const lava8 = new Obstacle("Lave8", images.lave);
const lava9 = new Obstacle("Lave9", images.lave);
const lavaArray = [lava, lava1, lava2, lava3, lava4, lava5, lava6, lava7, lava8, lava9];
board.drawBoard();
board.setObstacles(lavaArray);
}
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => { resolve(img); };
img.onerror = reject;
img.crossOrigin = 'anonymous'; // REMOVE IF SAME DOMAIN!
img.src = url;
});
}
function loadImages(images) {
return new Promise((resolve) => {
const loadedImages = {};
// for each name/url pair in image make a promise to load the image
// by calling loadImage
const imagePromises = Object.entries(images).map((keyValue) => {
const [name, url] = keyValue;
// load the image and when it's finished loading add the name/image
// pair to loadedImages
return loadImage(url).then((img) => {
loadedImages[name] = img;
});
});
// wait for all the images to load then pass the name/image object
Promise.all(imagePromises).then(() => {
resolve(loadedImages);
});
});
}
const images = {
lave: 'https://i.imgur.com/enx5Xc8.png',
// player: 'foo/player.png',
// enemy: 'foo/enemny.png',
};
loadImages(images).then(start);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="board" width="640" height="640"></canvas>

Javascript Image not rendered unless global

I'm trying to render an image from a sprite sheet using JS. The curious thing is, unless the object that does the rendering is global, it doesn't work (see code and comments). The behaviour is identical in both FF and Chrome.
resetGame() is executed on page load.
var TILE_SIZE = 24;
function CharacterImage(imageSource)
{
var tile_x = 0;
var tile_y = 0;
var img = new Image();
img.src = imageSource;
this.render = function(ctx, x, y)
{
ctx.drawImage(img, tile_x, tile_y, TILE_SIZE, TILE_SIZE,
x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
}
function Hero(canvas, image)
{
var ctx = canvas.getContext("2d");
var img = image;
this.render = function()
{
var x = 1;
var y = 1;
img.render(ctx, x, y);
}
}
// If the heroImage is constructed here, instead of within the function below,
// the image is rendered as expected.
var heroImage = new CharacterImage("img/sf2-characters.png");
function resetGame()
{
var heroCanvas = document.getElementById("heroLayer");
// On the otherhand, if the object is constructed here, instead of
// globally, the rendering doesn't work.
var heroImage = new CharacterImage("img/sf2-characters.png");
var hero = new Hero(heroCanvas, heroImage);
hero.render();
}
Oh hang on I think I see what's happening. The image needs time to load, so you should somehow bind an event to the loading of the image. This could be done as for example:
var TILE_SIZE = 24;
function CharacterImage(imageSource)
{
var tile_x = 0;
var tile_y = 0;
var img = new Image();
img.src = imageSource;
this.render = function(ctx, x, y)
{
ctx.drawImage(img, tile_x, tile_y, TILE_SIZE, TILE_SIZE,
x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
// Set up a "load" event for the img
this.loaded = function(callback) {
img.addEventListener('load', callback);
}
}
function resetGame()
{
var heroCanvas = document.getElementById("heroLayer");
var heroImage = new CharacterImage("img/sf2-characters.png");
var hero;
// Initiate the "load" event
heroImage.loaded(function() {
hero = new Hero(heroCanvas, heroImage);
hero.render();
};
}
What you'll probably want though is some sort of preloader "class"/event that keeps track of everything being loaded before you actually continue with rendering. It could look something like this.
var TILE_SIZE=60;
function Sprite(imageSource)
{
this.img = new Image();
this.img.src = imageSource;
this.position = { x:0, y:0 };
}
Sprite.prototype = {
isLoaded: function() {
return this.img.complete;
},
onLoad: function(callback) {
if (typeof callback !== "function") return;
if (this.isLoaded()) {
callback();
}
else {
this.img.removeEventListener('load', callback);
this.img.addEventListener('load', callback);
}
},
moveBy: function(x, y) {
this.position.x += x;
this.position.y += y;
},
render: function(ctx) {
if (!this.isLoaded()) return;
ctx.drawImage(this.img, this.position.x * TILE_SIZE, this.position.y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
};
function SpriteList()
{
this.list = {};
}
SpriteList.prototype = {
isLoaded: function() {
for (var i in this.list) {
if (!this.list[i].isLoaded()) {
return false;
}
}
return true;
},
_onLoadFunc: null,
onLoad: function(callback) {
this._onLoadFunc = callback;
this.onImageLoaded();
},
onImageLoaded: function() {
if (this.isLoaded() && typeof this._onLoadFunc === "function") {
this._onLoadFunc();
}
},
add: function(name, sprite) {
this.list[name] = sprite;
sprite.onLoad(this.onImageLoaded.bind(this));
},
get: function(name) {
return this.list[name];
}
};
var sprites = new SpriteList();
sprites.add("player", new Sprite("http://www.fillmurray.com/200/200"));
sprites.add("enemy", new Sprite("http://www.fillmurray.com/100/100"));
sprites.add("pickup", new Sprite("http://www.fillmurray.com/60/60"));
sprites.get("pickup").moveBy(1,2);
sprites.get("enemy").moveBy(2,0);
sprites.onLoad(function() {
document.getElementById("loading").innerHTML = "Loaded!";
var c = document.getElementById("ctx");
var ctx = c.getContext("2d");
sprites.get("player").render(ctx);
sprites.get("enemy").render(ctx);
sprites.get("pickup").render(ctx);
});
<div id="loading">Loading...</div>
<canvas id="ctx" width="200" height="200">
Anyways, that's why your code isn't firing, probably.

Categories