Assume you have a 500x500 2D canvas and you want to animate 100000 of elements in it, for example you want to create noise effects.
consider code bellow :
const canvas = document.getElementById("plane");
let animatelist = [];
animate = function() {
animatelist.forEach((e) => {
e.render();
});
setTimeout(animate, 1000 / 30);
}
animate();
let point = function(plane, x, y, size) {
animatelist.push(this);
this.plane = plane;
this.x = x;
this.y = y;
this.size = size;
this.render = () => {
const context = this.plane.getContext("2d");
this.x = Math.random() * 500;
this.y = Math.random() * 500;
context.fillStyle = "#000";
context.fillRect(this.x, this.y, this.size, this.size);
}
}
for (let i = 0;i < 100000;i++) {
new point(canvas, Math.random() * 500, Math.random() * 500, 0.3);
}
it barely gives you 2 or 3 fps and it is just unacceptable, i was wondering if there is a trick a about these kinda of animations or something to render massive amounts of elements smoothly!
You can play in memory and after that draw on an invisuble canvas. And when you are ready, copy all of bytes into visible canvas.
And i see, you use a lot of random. This is slow instruction. Try to make a random table and implement your own random function
Here is an 12-15 fps version but I think you can reach better performance by pixel manipulating. So this code based on your solution, but I cannot increase fps because too many function calls, object manipulating and similar baklava. (a code below reach over 100 fps)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>sarkiroka</title>
</head>
<body>
<canvas id="plane" width="500" height="500"></canvas>
<script>
// variable and function for speedup
const randomTable = [];
const randomTableLength = 1000007;
const fpsMinimum = 1000 / 30;
for (let i = 0; i < randomTableLength; i++) {
randomTable.push(Math.random() * 500);
}
let randomSeed = 0;
function getNextRandom() {
if (++randomSeed >= randomTableLength) {
randomSeed = 0;
}
return randomTable[randomSeed];
}
// html, dom speedup
const canvas = document.getElementById("plane");
const context = canvas.getContext("2d");
const drawCanvas = document.createElement('canvas');
drawCanvas.setAttribute('width', canvas.getAttribute('width'));
drawCanvas.setAttribute('height', canvas.getAttribute('height'));
const drawContext = drawCanvas.getContext('2d');
drawContext.fillStyle = "#000";
let animatelist = [];
let point = function (x, y, size) {
animatelist.push(this);
this.x = x;
this.y = y;
this.size = size;
this.render = () => {
this.x = getNextRandom();
this.y = getNextRandom();
drawContext.fillRect(this.x, this.y, this.size, this.size);
}
}
for (let i = 0; i < 100000; i++) {
new point(getNextRandom(), getNextRandom(), 0.3);
}
//the animation
let lastBreath = Date.now();
const animateListLength = animatelist.length;
let framesDrawed = 0;
let copied = false;
const maximumCallstackSize = 100;
function continouslyAnimation(deep) {
if (copied) {
drawContext.clearRect(0, 0, 500, 500);
for (let i = 0; i < animateListLength; i++) {
animatelist[i].render();
}
copied = false;
}
framesDrawed++;
let now = Date.now();
if (lastBreath + 15 > now && deep < maximumCallstackSize) {
continouslyAnimation(deep + 1);
} else { // to no hangs browser
lastBreath = now;
setTimeout(continouslyAnimation, 1, 1);
}
}
setInterval(() => {
console.log(framesDrawed);
framesDrawed = 0;
}, 1000);
continouslyAnimation(0);
function copyDrawToVisible() {
context.putImageData(drawContext.getImageData(0, 0, 499, 499), 0, 0);
copied = true;
}
setInterval(copyDrawToVisible, fpsMinimum);
</script>
</body>
</html>
And here is a pixel manipulation solution, with much better performance (over 100 fps, 220-245 fps in my computer):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>sarkiroka</title>
</head>
<body>
<canvas id="plane" width="500" height="500"></canvas>
<script>
// variable and function for speedup
const randomTable = [];
const randomTableLength = 1000007;
for (let i = 0; i < randomTableLength; i++) {
randomTable.push(Math.random());
}
let randomSeed = 0;
function getNextRandom() {
if (++randomSeed >= randomTableLength) {
randomSeed = Math.round(Math.random() * 1000);
}
return randomTable[randomSeed];
}
// html, dom speedup
const canvas = document.getElementById("plane");
const context = canvas.getContext("2d");
let framesDrawed = 0;
function drawNoise() {
context.clearRect(0, 0, 500, 500);
let imageData = context.createImageData(499, 499);
let data = imageData.data;
for (let i = 0, length = data.length; i < length; i += 4) {
if (0.1 > getNextRandom()) {
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 0;
data[i + 3] = 255;
}
}
context.putImageData(imageData, 0, 0);
framesDrawed++;
}
setInterval(drawNoise, 0);
setInterval(() => {
console.log('fps', framesDrawed);
framesDrawed = 0;
}, 1000)
</script>
</body>
</html>
Explanataion: for noise, you don't need a function / object for every colored pixel. Trust to statistics and the random. In my example 10% of pixels are colored but we don't know how many pixel before render. But this is not important. From afar it is just like that perfect. And the most important thing: it can reach more fps.
General advice:
Which code is repeated many times, organize out of it whatever you can
Draw only on the canvas when you are done drawing in memory
Measure what is slow and optimize it, and only it
Related
I'm very new to coding, specially .js and also to Stack.
So I have this code and I would like to change the images to a local folder instead of picsum. I've searched and tried but couldn't do it. Is there a way to load the images from a local folder in a way that doesn't slowdown the program?
Hope I've done it right and you can understand my question!
let imgs = [];
let num = 15;
function preload() {
for (let i = 0; i < num; i++) {
imgs[i] = loadImage("https://picsum.photos/400/400/?image=" + i * 10);
}
}
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
angleMode(DEGREES);
}
function draw() {
background(0,0,0);
orbitControl();
push();
translate(0, 0, -500);
let n = 0;
for (let j = 0; j < 20; j++) {
for (let i = 0; i < 20; i++) {
let x = map(i, 0, 20, -500, 500);
let y = map(j, 0, 20, -500, 500);
let z = sin(frameCount + j*10 + i * 10)*350+350;
push();
translate(x, y, z);
texture(imgs[n]);
plane(50, 50);
n = (n + 1) % imgs.length;
pop();
}
}
pop();
}
<html>
<head>
<script src="mySketch.js" type="text/javascript"></script><script src="https://cdn.jsdelivr.net/npm/p5#0.7.2/lib/p5.min.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
This will give you a dictionary with a key and an image corresponding to it.
For example, images["imagefile1.png"] will return an image with src imagefile1.png.
const IMAGES_FILES = [
"imagefile1.png",
"imagefile2.png"
];
const images = {};
const downloadPromiseImage = Promise.all(IMAGES_FILES.map(downloadImage));
function downloadImage(imageName) {
return new Promise((resolve) => {
const image = new Image();
image.onload = () => {
images[imageName] = image;
resolve();
};
// add path to image directory. This path should be relative to your entry HTML file.
image.src = `./Images/${imageName}`;
});
}
Promise.all([downloadPromiseImage]).then(() => {
// do whatever with images dictionary
})
I have the following code that makes the player jump when you press the up arrow key (sorry it's long):
class Vec {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
class Rect {
constructor(w, h) {
this.pos = new Vec;
this.size = new Vec(w, h);
this.vel = new Vec;
this.last = new Vec;
}
}
class Player extends Rect {
constructor() {
super(40, 40);
}
}
// setup
const backgroundContext = document.getElementById("backgroundCanvas").getContext("2d");
const groundContext = document.getElementById("groundCanvas").getContext("2d");
const objectContext = document.getElementById("objectCanvas").getContext("2d");
const WIDTH = 600;
const HEIGHT = 400;
const GROUND_Y = 50;
const player = new Player;
player.pos.x = 100;
player.pos.y = 390;
player.last.x = player.pos.x;
player.last.y = player.pos.y;
player.vel.x = 0;
let isJumping = true;
const JUMP_STRENGTH = -300;
const GRAVITY = 10;
function update(dt) {
// update player
player.last.x = player.pos.x;
player.last.y = player.pos.y;
player.vel.y += GRAVITY;
player.pos.y += player.vel.y * dt;
player.pos.y = Math.round(player.pos.y);
document.addEventListener("keydown", (e) => {
if (e.keyCode === 38 && isJumping === false) {
isJumping = true;
player.vel.y = JUMP_STRENGTH;
}
}, false);
if (player.pos.y > HEIGHT - GROUND_Y - player.size.y) {
isJumping = false;
player.pos.y = HEIGHT - GROUND_Y - player.size.y;
player.vel.y = 0;
}
}
function draw() {
// draw background
backgroundContext.fillStyle = "#000";
backgroundContext.fillRect(0, 0, WIDTH, HEIGHT);
// draw ground
objectContext.clearRect(0, HEIGHT - GROUND_Y, WIDTH, GROUND_Y);
groundContext.fillStyle = "#00ff00";
groundContext.fillRect(0, HEIGHT - GROUND_Y, WIDTH, GROUND_Y);
// draw player
objectContext.clearRect(player.last.x, player.last.y, player.size.x, player.size.y);
objectContext.fillStyle = "#fff";
objectContext.fillRect(player.pos.x, player.pos.y, player.size.x, player.size.y);
}
// game loop
const TIMESTEP = 1 / 60;
let accumulator = 0;
let lastRender = 0;
function loop(timestamp) {
accumulator += (timestamp - lastRender) / 1000;
lastRender = timestamp;
while (accumulator >= TIMESTEP) {
update(TIMESTEP);
draw();
accumulator -= TIMESTEP;
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
canvas {
position: absolute;
left: 0;
top: 0;
}
<!DOCTYPE html>
<html>
<head>
<title>Jump_Over_It</title>
<link href="css/default.css" rel="stylesheet" />
</head>
<body>
<canvas style="z-index: 0;" id="backgroundCanvas" width="600" height="400"></canvas>
<canvas style="z-index: 1;" id="groundCanvas" width="600" height="400"></canvas>
<canvas style="z-index: 2;" id="objectCanvas" width="600" height="400"></canvas>
<script src="js/main.js"></script>
</body>
</html>
I have three questions:
1) If you wait for about a minute, you start to notice that the performance starts to decrease. Why is this happening?
2) Why does the performance significantly drop when you hold the up arrow key?
3) In the game loop, I did:
while (accumulator >= TIMESTEP) {
update(TIMESTEP);
draw();
accumulator -= TIMESTEP;
}
Is it OK to put the draw() function in the same while loop as the update() function?
If you know what to do, please let me know.
For questions 1 and 2:
This is Because you are adding a new EventListener in a while loop, itself in a requestAnimationFrame loop.
I won't even calculate the number of event handlers that are attached, nor run this snippet, but do not touch your keyboard because there could be Zillions handlers executing serially there.
To fix this properly, move your addEventListener call out of these loops, you only need to call it once.
For question 3:
It is not clear why you even need this while loop at all. Would be better to actually calculate the new position directly rather than updating in a loop like that.
But at least, no. You should not call draw inside this while loop since every call will negate previous ones. So call it only once at the end of your rAF handler.
Also, note that instead of clearing on the part where your object is, you'd be better clearing the whole canvas everytime, and you might even consider moving all your drawings on a single canvas, since the compositing you thought you'd win by having three canvases actually still happens when painting the 3 DOM Elements on screen.
class Vec {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
class Rect {
constructor(w, h) {
this.pos = new Vec;
this.size = new Vec(w, h);
this.vel = new Vec;
this.last = new Vec;
}
}
class Player extends Rect {
constructor() {
super(40, 40);
}
}
// setup
const backgroundContext = document.getElementById("backgroundCanvas").getContext("2d");
const groundContext = document.getElementById("groundCanvas").getContext("2d");
const objectContext = document.getElementById("objectCanvas").getContext("2d");
const WIDTH = 600;
const HEIGHT = 400;
const GROUND_Y = 50;
const player = new Player;
player.pos.x = 100;
player.pos.y = 390;
player.last.x = player.pos.x;
player.last.y = player.pos.y;
player.vel.x = 0;
let isJumping = true;
const JUMP_STRENGTH = -300;
const GRAVITY = 10;
function update(dt) {
// update player
player.last.x = player.pos.x;
player.last.y = player.pos.y;
player.vel.y += GRAVITY;
player.pos.y += player.vel.y * dt;
player.pos.y = Math.round(player.pos.y);
if (player.pos.y > HEIGHT - GROUND_Y - player.size.y) {
isJumping = false;
player.pos.y = HEIGHT - GROUND_Y - player.size.y;
player.vel.y = 0;
}
}
document.addEventListener("keydown", (e) => {
if (e.keyCode === 38 && isJumping === false) {
e.preventDefault();
isJumping = true;
player.vel.y = JUMP_STRENGTH;
}
}, false);
function draw() {
// draw background
backgroundContext.fillStyle = "#000";
backgroundContext.fillRect(0, 0, WIDTH, HEIGHT);
// draw ground
groundContext.clearRect(0, 0, WIDTH, HEIGHT);
groundContext.fillStyle = "#00ff00";
groundContext.fillRect(0, HEIGHT - GROUND_Y, WIDTH, GROUND_Y);
// draw player
objectContext.clearRect(0, 0, WIDTH, HEIGHT); objectContext.fillStyle = "#fff";
objectContext.fillRect(player.pos.x, player.pos.y, player.size.x, player.size.y);
}
// game loop
const TIMESTEP = 1 / 60;
let accumulator = 0;
let lastRender = 0;
function loop(timestamp) {
accumulator += (timestamp - lastRender) / 1000;
lastRender = timestamp;
while (accumulator >= TIMESTEP) {
accumulator -= TIMESTEP;
update(TIMESTEP);
}
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
canvas {
position: absolute;
left: 0;
top: 0;
}
<!DOCTYPE html>
<html>
<head>
<title>Jump_Over_It</title>
<link href="css/default.css" rel="stylesheet" />
</head>
<body>
<canvas style="z-index: 0;" id="backgroundCanvas" width="600" height="400"></canvas>
<canvas style="z-index: 1;" id="groundCanvas" width="600" height="400"></canvas>
<canvas style="z-index: 2;" id="objectCanvas" width="600" height="400"></canvas>
<script src="js/main.js"></script>
</body>
</html>
I'm trying to make a simple canvas program where the user clicks to create bouncing moving circles. It keeps freezing but still creates the circles without updating. I'm not sure whats going on, please help!
I'm adding each circle to an array of circles with the constructor
The setInterval loop seems to be freezing but the circles are still created even when this is happening
I'm having a hard time debugging this, any advice is greatly appreciated
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Background Test</title>
<style>
* { margin: 0; padding: 0; overflow: hidden; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// Request animation frame -> Optimizes animation speed
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
const c = document.getElementById('canvas');
const ctx = c.getContext('2d');
// Fullscreen
c.width = window.innerWidth;
c.height = window.innerHeight;
ctx.fillStyle = 'red';
let fps = 60;
// FOR MOBILE DEVICES
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent))
fps = 29;
// Options
const background = '#333';
const circleMinSpeed = 3;
const circleMaxSpeed = 6;
const circleMinSize = 3;
const circleMaxSize = 10;
const circles = [];
let circlesCounter = 0;
const circlesTimeAlive = 20 * fps; // seconds
let i = 0;
const interval = 1000 / fps;
let now, delta;
let then = Date.now();
// Coordinate variables
let mouseX, mouseY, clickX, clickY;
// Tracks mouse movement
c.onmousemove = function(event)
{
mouseX = event.clientX;
mouseY = event.clientY;
};
// Tracks mouse click
c.onmousedown = function(event)
{
clickX = event.clientX;
clickY = event.clientY;
circle(clickX, clickY);
};
function draw()
{
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
// Clear canvas then draw
ctx.clearRect(0, 0, c.width, c.height);
drawBackground();
drawCos();
drawCircles();
drawTest();
}
}
// Circle constructor
function circle(x, y)
{
// Pick random color
let r = Math.floor(Math.random() * 255);
let g = Math.floor(Math.random() * 255);
let b = Math.floor(Math.random() * 255);
self.color = 'rgb(' + r + ', ' + g + ', ' + b + ')';
self.xCo = x;
self.yCo = y;
// Pick random size within ranges
self.size = circleMinSize + Math.floor(Math.random() *
(circleMaxSize - circleMinSize));
// Pick random direction & speed (spdX spdY)
self.speed = circleMinSpeed + Math.floor(Math.random() *
(circleMaxSpeed - circleMinSpeed));
self.spdX = self.speed * (Math.random() * 2) - 1; // picks -1 to 1
self.spdY = self.speed * (Math.random() * 2) - 1;
self.draw = function()
{
ctx.beginPath();
ctx.arc(self.xCo, self.yCo, self.size, 0, 2*Math.PI);
ctx.fillStyle = self.color;
ctx.fill();
};
circles[circlesCounter++] = self;
}
// Draw the background
function drawBackground()
{
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
function drawCircles()
{
for (let i = 0; i < circles.length; i++)
circles[i].draw();
}
function drawTest()
{
ctx.fillStyle = 'red';
ctx.fillRect(i++, i, 5, 5);
}
function drawCos()
{
ctx.fillStyle = 'white';
ctx.fillText("X: " + mouseX + " Y:" + mouseY, 10, 10, 200);
}
// Main loop
setInterval(function()
{
// Loop through circles and move them
for (let i = 0; i < circles.length; i++)
{
if (circle[i])
{
// Check left and right bounce
if (circle[i].xCo <= 0 || circle[i].xCo >= c.width)
circle[i].spdX = -circle[i].spdX;
circle[i].xCo += circle[i].spdX;
// Check left and right bounce
if (circle[i].yCo <= 0 || circle[i].yCo >= c.height)
circle[i].spdY = -circle[i].spdY;
circle[i].yCo += circle[i].spdY;
}
}
// Draw Everything
draw();
}, interval);
</script>
</body>
</html>
This code:
self.draw = function()
{
ctx.beginPath();
ctx.arc(self.xCo, self.yCo, self.size, 0, 2*Math.PI);
ctx.fillStyle = self.color;
ctx.fill();
};
Is overriding this function:
function draw()
{
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
// Clear canvas then draw
ctx.clearRect(0, 0, c.width, c.height);
drawBackground();
drawCos();
drawCircles();
drawTest();
}
}
You need to rethink how you want to draw your circles because you're re-drawing the black canvas every time a click event is triggered. I mean, when a click is triggered, you're applying new coordinates, color, Etc, and probably that's not what you want to do.
My suggestion is create canvas per circle and append them into a DIV.
Hope it helps!
window.onload = function(){
theVideo();
playVideo();
Move();
Draw();
};
let objectInfo = {
canvas: null,
context: null,
// Number of sprites
numberOfFrames: 16,
image: null,
imageWidth: 128,
imageHeight: 192,
frameIndex: 0,
frameWidth: 0,
// Animation interval (ms)
msInterval: 1000,
x: 10,
y: 10,
};
const imageFile = "shaggy.png";
function Draw(){
objectInfo.context.drawImage(myImage, shift, 0, frameWidth, frameHeight, 120, 25, frameWidth, frameHeight);
}
//image setup
window.onload= function () {
// Canvas setup
objectInfo.canvas = document.querySelector("#myCanvas");
objectInfo.context = objectInfo.canvas.getContext("2d");
// Image setup
objectInfo.image = new Image();
objectInfo.image.onload = function() {
// The this object refers to image because within image onload event handler
objectInfo.imageWidth = this.width;
objectInfo.imageHeight = this.height;
// Calculate framewidth (size of each sprite)
objectInfo.frameWidth = objectInfo.imageWidth / objectInfo.numberOfFrames;
};
// Load image
objectInfo.image.src = imageFile;
};
var xPos = 0;
var yPos = 0;
//move image
function Move(e){
//right
if(e.keyCode==39){
xPos+=5;
}
//left
if(e.keyCode==37){
xPos-=5;
}
//up
if(e.keyCode==38){
yPos-=5;
}
//down
if(e.keyCode==40){
yPos+=5;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sprite</title>
<meta charset="utf-8">
<meta name="author" content="Peyton">
<meta name="description" content="115">
<link rel= 'stylesheet' href="p4.css">
<script src="p4.js"> </script>
<style>canvas { border: 1px solid black; }</style>
<body>
<canvas width= "1300" height= "600" id= "myCanvas">
<video id="video" controls >
<source src="ScoobyDooV.mp4"/>
<source src="ScoobyDooV.ogv"/>
</video>
</canvas>
</body>
</html>
<nav>
</nav>
<main id="#wrapper"><br>
</main>
</body>
</html>
I'm really new to coding and am not sure what I am missing to call my sprite and draw the first image onto the canvas. I later have to call each measurement of my sprite and assign to a function keydown event to make it look like its walking each direction so if I could get any guidance on that too that would be great.
It looks like you're calling Draw before the image is loaded. Try placing the Draw() call within the image.onload method.
You are defining window.onload twice, only one out of your two callbacks will be executed
Your code is completely not suited to the task you are attempting. Animations require regular rendering. There is a huge amount of missing code in your example so i can really solve any problems directly.
So just thought I would give an example of how to load, animate, and render sprites sheets.
Sprite sheets
There are many ways to handle sprite sheets, though I find that using a standard method for all sprite sheets makes life easy.
Some sprite sheets have a regular layout and evenly spaced sprites, other sprite sheets, have been packed together to conserve pixels and memory.
Each sprite has a location on the sheet, the top left corner and the size as width and height.
You can attach an array of these locations to an image
For example the next function creates sprites for a regular layout (like image in your question)
function createSprites(width, height, columns, rows, image) {
const sprites = [];
var w = width / columns;
var h = height / rows;
var ix, iy;
for (iy = 0; iy < rows; iy++) {
for (ix = 0; ix < columns; ix++) {
const x = ix * w;
const y = iy * h;
sprites.push({ x, y, w, h });
}
}
image.sprites = sprites;
}
The array is added to the img so you don't have to add additional management
You can then draw the sprite by creating a custom draw function.
function drawSprite(img, sprIndex, x, y) {
const spr = img.sprites[sprIndex];
ctx.drawImage(img,
spr.x, spr.y, spr.w, spr.h, // location on sprite sheet
x , y , // location on canvas
spr.w, spr.h, // size on canvas;
);
}
You pass the sprite sheet image, the sprite index in the sprite array, and the location you want to draw the sprite.
Easy as
Because you likely know the size of the sprite sheet, and the location of the sprites you don't have to wait for the image to load to attach the sprite data.
const ctx = canvas.getContext("2d");
const spriteSheet = new Image;
spriteSheet.src = "https://i.stack.imgur.com/hOrC1.png";
// The image size is known so you dont have to wait for it to load
createSprites(128, 192, 4, 4, spriteSheet); // add a array of sprite locations
// It is important that the sprite sizes are integers
// width must be divisible by columns and height by rows
function createSprites(width, height, columns, rows, image) {
const sprites = [];
var w = width / columns;
var h = height / rows;
var ix, iy;
for (iy = 0; iy < rows; iy++) {
for (ix = 0; ix < columns; ix++) {
const x = ix * w;
const y = iy * h;
sprites.push({ x, y, w, h });
}
}
image.sprites = sprites;
}
function drawSprite(img, sprIndex, x, y) {
const spr = img.sprites[sprIndex];
ctx.drawImage(img,
spr.x, spr.y, spr.w, spr.h, // location on sprite sheet
x , y , // location on canvas
spr.w, spr.h, // size on canvas;
);
}
const walkerInfo = {
framesPerDir: 4,
movements: [{x: 0,y: 3 },{ x: -5, y: 0 }, { x: 5, y: 0 }, { x: 0, y: -3 } ],
}
const walker = {
dir: 0, // 0,1,2,3
time: 0, // time in Frames
rate: 0, // steps per frame
x: 0, // position
y: 0, //
update() {
this.time += 1;
// only move when sprite frame changes
if ((this.time % this.rate) === 0) {
this.x += walkerInfo.movements[this.dir].x;
this.y += walkerInfo.movements[this.dir].y;
if(this.x < -128 || this.x > canvas.width ||
this.y < -192 || this.y > canvas.height) {
this.x = randI(canvas.width);
this.y = randI(canvas.height);
this.dir = randI(4)
this.rate = randI(6, 12)
}
}
if(randI(1000) === 0){
this.dir = (this.dir + (randI(2) ? 2 : 1)) % 4;
this.rate = randI(6, 12)
}
},
draw() {
var index = this.dir * walkerInfo.framesPerDir;
index += (this.time / this.rate | 0) % walkerInfo.framesPerDir;
drawSprite(
spriteSheet, index,
this.x, this.y
);
}
}
function createWalker(x = randI(w), y = randI(h), dir = randI(4), rate = randI(6, 18)) {
return { ...walker, x, y, dir, rate, time: randI(100) };
}
const walkers = [];
// main update function
function update(timer) {
globalTime = timer;
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
} else {
ctx.clearRect(0, 0, w, h);
}
if (spriteSheet.complete) { // has the image loaded
if (randI(walkers.length) === 0) { // odd 1/100 to create a walker
walkers.push(createWalker());
}
walkers.sort((a,b)=>a.y - b.y);
eachOf(walkers, walk => walk.update());
eachOf(walkers, walk => walk.draw());
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const eachOf = (array, cb) => {
var i = 0;
const len = array.length;
while (i < len && cb(array[i], i++, len) !== true);
};
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
I am making a Physics engine that utilizes JavaScript to create boxes that have the properties of physics applied to them in scale of the window size. However, the class called box that contains all the physical properties of these boxes cannot be found by the function document.getElementByClassName("box").
I am trying to assign a variable boxelem to contain the location properties of each individual box so that I can integrate mouse manipulation into my program in the future. I have tried doing this through the code:
var boxelem = document.getElementsByClassName("box");
and then adding a mouse over event listener to boxelem.
Necessary Code:
var canvas;
var ctx;
var box = [];
var boxelem;
//Startup Code
window.onload = function(e) {
canvas = document.getElementById("c");
ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
box = [new box(10, 20, "cyan"), new box(299, 40, "red"), new box(90, 50,
"black"), new box(29, 20, "turquoise")];
boxelem = document.getElementsByClassName("box");
noscroll();
update();
}
//Physic Clock (For real-time physics)
var clock = {
lasttick: 0,
tick: function() {
var td = performance.now() - this.lasttick;
this.lasttick = performance.now();
return td / 1000;
},
reset: function() {
}
}
//Box objects be created here (Box Class)
var box = function(x, y, color) {
var _this = this;
//First Spawn Physics and Settings
_this.x = Math.random() * (canvas.width - 50);
_this.y = Math.random() * (canvas.height - 50);
_this.vx = 0;
_this.vy = Math.random() * 150;
_this.ax = 0;
_this.ay = 440;
_this.color = color;
_this.draw = function() {
ctx.fillStyle = _this.color;
ctx.fillRect(_this.x, _this.y, 50, 50);
}
//Normal Physics
_this.update = function(t, mX, mY) {
if (mX != 0) {
_this.x += _this.vx * t;
_this.vx += mX * t;
} else {
_this.x += _this.vx * t;
_this.vx += _this.ax * t;
}
_this.y += _this.vy * t;
_this.vy += _this.ay * t;
//Boundary Creation
if ((_this.y + 55) > canvas.height) {
_this.vy -= _this.ay * t;
_this.vy = 0;
}
if ((_this.x + 55) > canvas.width) {
_this.vx -= _this.ax * t;
_this.vx = 0;
}
if ((_this.y) < 0) {
_this.vy -= _this.ay * t;
_this.vy = 0;
_this.y = (canvas.height + 20);
}
if ((_this.x) < 0) {
_this.vx -= _this.ax * t;
_this.vx = 0;
}
}
}
//Get mouse position if over a box
var pageX = 0;
var pageY = 0;
for (var z = 0; z < box.length; z++) {
boxelem.addEventListener("mouse", mPos, false);
}
The event listener at the bottom gives an error because boxelem is not defined due to the elements not being found by getElementsByClassName.
HTML Code:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type=text/javascript src="Physics.js"></script>
<meta charset="utf-8" />
<title>Physics Engine of Boxes</title>
</head>
<body>
<canvas id="c"></canvas>
</body>
</html>
I have looked at (Unobtrusive Javascript-Basic Implementation: How do I bind all elements of a particular class to a function in Javascript?) and (Change an element's class with JavaScript) but am unsure how to apply it to my issue.
This body of your loop is not accessing the index. Also, the .length you're using is from the box function instead of the boxelem collection.
This:
for (var z = 0; z < box.length; z++)
{
boxelem.addEventListener("mouse", mPos, false);
}
should be this:
for (var z = 0; z < boxelem.length; z++)
{
boxelem[z].addEventListener("mouse", mPos, false);
}
As D Simon pointed out below, boxelem is undefined because you're not populating that variable until after the DOM loads. You should be sure to fetch those items before trying to use that variable.
If you move all the code to window.onload, it should work. Note that window.onload doesn't run until all resources are loaded. You may want to use a different event that fires sooner, but that's another topic.
//Startup Code
window.onload = function(e) {
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var box = [new box(10, 20, "cyan"), new box(299, 40, "red"), new box(90, 50,
"black"), new box(29, 20, "turquoise")];
var boxelem = document.getElementsByClassName("box");
noscroll();
update();
//Physic Clock (For real-time physics)
var clock = {
lasttick: 0,
tick: function() {
var td = performance.now() - this.lasttick;
this.lasttick = performance.now();
return td / 1000;
},
reset: function() {
}
}
//Box objects be created here (Box Class)
var box = function(x, y, color) {
// ...implementation...
}
//Get mouse position if over a box
var pageX = 0;
var pageY = 0;
for (var z = 0; z < boxelem.length; z++) {
boxelem[z].addEventListener("mouse", mPos, false);
}
}