I've been knocking my head against the keyboard trying to figure out why the animation from this tutorial is not showing up on the canvas correctly, if at all. In chrome it draws the leftmost part of the image on the canvas, but in safari it draws nothing at all.
I've tried different methods of delaying until the image loads, putting the script tag in various places in the html, no luck. Debugging in chrome shows no errors.
The source code for the animation is not quite the same as what he presents in the tutorial, I've tried to make sense of it. I've been at it for two days and you'll pinpoint it 30 seconds, I want to see this damn coin spin.
spriteSheet.jpg:
animation.html:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link href='http://fonts.googleapis.com/css?family=Ubuntu' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="style.css" type="text/css" />
<title>Test Profile Page</title>
</head>
<body>
<header>
<h1>Hello</h1>
</header>
<nav>
<ul>
<li>Pictures</li>
<li>Animation?</li>
<li>Cartoon</li>
</ul>
</nav>
<section>
<img src="spriteSheet.jpg" />
<canvas id="coinAnimation"></canvas>
</section>
<footer>
</footer>
<script src="animation.js"></script>
</body>
</html>
animation.js:
window.onload = function () {
var spriteSheet = new Image();
spriteSheet.src = "spriteSheet.jpg";
//define sprite class
function sprite (options) {
var that = {},
frameIndex = 0,
tickCount = 0,
ticksPerFrame = options.ticksPerFrame || 0,
numberOfFrames = options.numberOfFrames || 1;
that.context = options.context;
that.width = options.width;
that.height = options.height;
that.image = options.image;
that.loop = options.loop;
that.update = function () {
tickCount += 1;
if (tickCount > ticksPerFrame) {
tickCount = 0;
// If the current frame index is in range
if (frameIndex < numberOfFrames - 1) {
// Go to the next frame
frameIndex += 1;
} else if (that.loop) {
frameIndex = 0;
}
}
};
that.render = function () {
// Clear the canvas
that.context.clearRect(0, 0, that.width, that.height);
// Draw the animation
that.context.drawImage(
that.image,
frameIndex * that.width / numberOfFrames,
0,
that.width / numberOfFrames,
that.height,
0,
0,
that.width / numberOfFrames,
that.height);
};
return that;
}
var canvas = document.getElementById("coinAnimation");
canvas.width = 100;
canvas.height = 100;
var coin = new sprite({
context: canvas.getContext("2d"),
width: 100,
height: 100,
image: spriteSheet
});
function gameLoop () {
window.requestAnimationFrame(gameLoop);
coin.update();
coin.render();
}
spriteSheet.addEventListener("load", gameLoop);
}
When you enter the width on your coin you need to enter the width of your entire image (which seems to be 440), not the width of a single frame. Along with that you need to set the numberOfFrames to 10:
var coin = new sprite({
context: canvas.getContext("2d"),
width: 440,
height: 100,
image: spriteSheet,
numberOfFrames: 10
});
Note when it find the width of a single frame it does width/numberOfFrames to find that, this is why it will not work if you just enter 100. Now your coin should be spinning.
Fiddle Example.
If you want the slow the coin down you can add ticksPerFrame: 5 for example, the higher that is the slower it will go.
Related
I'm trying to create a wobbly circle with multiple ellipses in the background, however I can't add the for loop to the draw function otherwise it will be running in every frame, and if I add it to setup then the wobbly circle leaves a trace. And if I use clear() then it removes my background. How can I solve this, please?
function setup() {
createCanvas(600, 600);
for (var i = 0; i < 1000; i++){
fill(255);
ellipse(random(0, width), random(0, width), random(5, 5), random(5, 5));
}
x = width/2; // Circle center x
y = height/2; // Circle center y
r = 100; // Circle radius
verts = 200; // Number of vertices for drawing the circle
wobbleSlider = createSlider(0, 200, 100, 1); // How much the circle radius can vary
smthSlider = createSlider(1, 500, 70, 1); // How smooth the noise function is (higher is smoother)
t = 1;
}
function draw() {
let wobble = wobbleSlider.value()
let smth = smthSlider.value();
//clear();
t += 0.01
beginShape()
for(let i = 0; i < verts; i++){
let f = noise(50*cos(i/verts*2*PI)/smth + t, 50*sin(i/verts*2*PI)/smth + t)
vertex(x + (r+wobble*f) * cos( (i/verts) * 2 * PI ), y + (r+wobble*f) * sin( (i/verts) * 2 * PI ) )
}
endShape(CLOSE)
}
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
enter image description here
What you want is some sort of layering system that allows you to have a fixed background and draw over it things that chage. You can achieve this with graphics. It basically allows you to create an independent "canvas" and draw over it, then you can render it like an image in your animation.
First you use createGraphics which is similar to createCanvas with the difference that you can store the result in a variable like: let myGraphics = createGraphics(600,600). Then you draw over it like in your canvas. For example ellipse(...) becomes myGraphics.ellipse(...). Then you can render it using image like image(myGraphics, 0, 0).
See the working code below
If this answers your question dont forget to mark it as the answer using the green check at the left of the answer so others can reference from it in the future :)
let background;
function setup() {
createCanvas(600, 600);
background = createGraphics(600, 400);
for (var i = 0; i < 1000; i++){
background.fill(255);
background.ellipse(random(0, width), random(0, width), random(5, 5), random(5, 5));
}
x = width/2; // Circle center x
y = height/2; // Circle center y
r = 100; // Circle radius
verts = 200; // Number of vertices for drawing the circle
wobbleSlider = createSlider(0, 200, 100, 1); // How much the circle radius can vary
smthSlider = createSlider(1, 500, 70, 1); // How smooth the noise function is (higher is smoother)
t = 1;
}
function draw() {
clear();
image(background,0,0);
let wobble = wobbleSlider.value()
let smth = smthSlider.value();
//clear();
t += 0.01
beginShape()
for(let i = 0; i < verts; i++){
let f = noise(50*cos(i/verts*2*PI)/smth + t, 50*sin(i/verts*2*PI)/smth + t)
vertex(x + (r+wobble*f) * cos( (i/verts) * 2 * PI ), y + (r+wobble*f) * sin( (i/verts) * 2 * PI ) )
}
endShape(CLOSE)
}
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
I am trying to add new canvas nodes to the canvas node that is already in the HTML. After the first node is added as a child node, that node just keeps getting replaced with the new node instead of adding after the last node. I have tried using both append and appendChild and both are doing the same thing.
A little context, I am trying to make multiple balls that will "bounce" off when they collide either with each other or the canvas boundary.
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="main.css">
</head>
<body>
<canvas width="550" height="400" style="border: 1px dashed black"></canvas>
<div id="buttons">
<button id="increase">Increase Speed</button>
<button id="decrease">Decrease Speed</button>
<button id="addBall">Add a Ball</button>
</div>
<script src="scripts.js"></script>
</body>
</html>
JavaScript:
const spriteObject = {
sourceX: 0,
sourceY: 0,
sourceWidth: 62,
sourceHeight: 62,
x: 0,
y: 0,
vx: 0,
vy: 0,
width: 30,
height: 30,
radius: 30
};
let canvas = document.querySelector('canvas');
const increase = document.querySelector('#increase');
const decrease = document.querySelector('#decrease');
const addSprite = document.querySelector('#addBall');
const mainCanvas = canvas.getContext('2d');
let drawBall = document.createElement('canvas');
const sprites = []
addSprite.addEventListener('click', () => {
sprites.push(Object.create(spriteObject));
for (let i = 0; i <= sprites.length - 1; i++) {
drawBall.setAttribute('id', i);
drawBall.setAttribute('width', '62');
drawBall.setAttribute('height', '62');
canvas.append(drawBall);
// canvas.appendChild(drawBall);
console.log('append')
const ballDrawingSurface = drawBall.getContext('2d');
ballDrawingSurface.beginPath();
ballDrawingSurface.beginPath();
ballDrawingSurface.translate(sprites[i].x, sprites[i].y);
ballDrawingSurface.arc(sprites[i].width, sprites[i].height, sprites[i].radius, 0, 2 * Math.PI, false);
ballDrawingSurface.stroke();
}
console.log(sprites);
})
requestAnimationFrame(function loop() {
for (let i = 0; i <= sprites.length - 1; i++) {
increase.addEventListener('click', () => {
sprites[i].vx = +5;
});
decrease.addEventListener('click', () => {
sprites[i].vx = -5;
});
//change the ball's position
if (sprites[i].x < 0) {
sprites[i].x = 0;
sprites[i].vx = 5;
}
if (sprites[i].x + sprites[i].sourceWidth > canvas.width) {
sprites[i].x = canvas.width - sprites[i].sourceWidth;
sprites[i].vx = -5;
}
//move the ball
sprites[i].x += sprites[i].vx;
mainCanvas.clearRect(0, 0, canvas.width, canvas.height);
mainCanvas.drawImage(drawBall, sprites[i].sourceX, sprites[i].sourceY,
sprites[i].sourceWidth, sprites[i].sourceHeight,
sprites[i].x, sprites[i].y,
60, 60);
}
requestAnimationFrame(loop)
});
I believe a canvas cannot have child elements, so you do not need to append any children (or do any html manipulation).
I would suggest that you just keep track of your balls in an array (like sprites) and on each animation frame:
Calculate the new position for each ball
Clear the canvas
Draw each ball
When you want to add a ball (for example on click) you can just add a new ball by pushing it to the sprites array.
I am creating a 2D platformer game in html5 canvas. I am trying to animate a sprite so that it switches to a running image and a stationery image, to create the illusion that the sprite is running. I have tried changing the sprite images based on the frame number but it only shows the sprite running for less than a second. This is not my full code. Just the parts that are relevant to the running animation.
index.html:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Game</title>
<style media="screen">
#canvas {
background-color: #87cefa;
}
</style>
</head>
<body>
<canvas id="canvas" width="800" height="600"></canvas>
</body>
<script type="text/javascript" src="index.js">
</script>
</html>
index.js:
var canvas, ctx, player, playerPng, key, frameNo;
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
player = {
w: 100,
h: 200,
x: 40,
y: canvas.height - 20
}
playerPng = new Image();
frameNo = 0;
key = false;
function loop() {
frameNo += 1;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.imageSmoothingEnabled = false;
ctx.drawImage(playerPng, player.x, player.y, player.w, player.h);
if(key === 39) {
if(frameNo % 100 === 0){
playerPng.src = "Running.png";
} else {
playerPng.src = "normal.png";
}
}
if(key === false){
playerPng.src = "normal.png";
}
window.requestAnimationFrame(loop);
}
window.addEventListener('keydown', function(e){
key = e.keyCode;
});
window.addEventListener('keyup', function(e){
key = false;
});
loop();
Load images once
First don't load the image every time you render it. image.src = "filenameORUrl" loads the image.
Load the images once then select which one you want to render.
Example create an load images
const running = new Image; // create
const normal = new Image;
running.src = "Running.png"; //load
normal.src = "normal.png";
Animate
To animate the image add the animation images to an array. Set a variable that defines how many frames each image should appear and then divide the frameNum by this value and floor it to get the image index.
Example animating the image
Note that in (frameNo / runAnimFrames | 0) the | 0 (bitwise OR 0) floors the result. Is the same as Math.floor(frameNo / runAnimFrames). Should only use OR zero on positive numbers less than 31 ** 2 - 1
Example adds the animation sequence to the array runAnim and inside the main render loop displays the animation with each animation frame display for 30 frames runAnimFrames (half a second). Reduce the number to speed up the animation, increase to slow down.
You can add as many frames as you need to the animation array
const runAnim = [running, normal]; // add animation sequence to arr
const runAnimFrames = 30; // number of frames to display each image in animation
// Inside main render loop
if(key === 39) {
const plyImg = runAnim[(frameNo / runAnimFrames | 0) % runAnim.length];
ctx.drawImage(plyImg, player.x, player.y, player.w, player.h);
} else {
ctx.drawImage(normal, player.x, player.y, player.w, player.h);
}
How can I make the imageObject to move in a circular path? Or more specific, what are the mathematical formulas needed to make it do so?
I am required to use setInterval with a function that caluclates the new coordinates of the picture. setInterval is supposed to call the function at least 20 times a second.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Graphics and animation using HTML5 Canvas</title>
<style>
#the-canvas { border: 1px dashed gray }
</style>
<script>
addEventListener('load', function() {
var x = document.getElementById("the-canvas");
var y = x.getContext("2d");
var imageObject = new Image();
imageObject.onload = function() {
y.drawImage(imageObject, 100, 200);
};
imageObject.src = "image.jpg";
});
</script>
</head>
<body>
<canvas id="the-canvas" width="500" height="400">
Your browser does not support the <canvas> element.
</canvas>
</body>
</html>
Mathematical formulas needed would be cosine in one dimension and sine in the other.
Something like this:
addEventListener('load', function() {
var x = document.getElementById("the-canvas");
var y = x.getContext("2d");
var imageObject = new Image();
var step = 0, radius = 50, speed = 0.05;
function spin() {
y.clearRect(0, 0, x.width, x.height);
y.drawImage(imageObject, 100 + radius * Math.cos(speed * step), 200 + radius * Math.sin(speed * step));
++step;
}
imageObject.onload = function() {
y.drawImage(imageObject, 100, 200);
setInterval(spin, 50); // 20 fps
};
imageObject.src = "image.jpg";
});
I'm having an issue with drawImage() re-sizing a loaded image below a certain width and height. The image I'm trying to re-size is 1080x1920 and I want it to be re-sized to 540x960. If I apply drawImage() to reduce it to 764x1358, it works. But any resolution smaller than that (on either parameter) results in the image not being displayed on the canvas.
There is definitely some correlation between the lower bounds on the resolution, because both are approximately 70.7% of the original resolution. I'm wondering if there's an inherit limit on drawImage but I couldn't find any specification that said so.
Here's the relevant code:
var image = new Image();
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.canvas.width = this.width * 2;
ctx.canvas.height = this.height;
$(image).on("load", function() {
ctx.drawImage(image, 0, 0, 764, 1358);
});
image.src = "test2.jpg";
I edited the code to show where image came from.
Full Code:
<!DOCTYPE html>
<html>
<head>
<title>Canvas from scratch</title>
<meta charset="utf-8">
<script
src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="jcanvas.min.js"></script>
</head>
<body>
<canvas id="myCanvas" width="1920" height="1920">
</canvas>
<script>
$(document).ready(
function() {
$("<img>").attr("src", "test2.jpg").on('load', function() {
var imgWidth, imgHeight;
var imageData;
var image = new Image();
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.canvas.width = this.width * 2;
ctx.canvas.height = this.height;
$(image).on("load", function() {
/* This is where the image is loaded and
inserted into the canvas */
ctx.drawImage(image, 0, 0, 763, 1358);
});
image.src = "test2.jpg";
imageData = ctx.getImageData(0, 0,
this.width,
this.height);
/* This is just part of the image manipulation,
bland right now */
var newImgData = ctx.createImageData(imageData.width,
imageData.height);
for ( var i = 0; i < newImgData.data.length; i += 4) {
newImgData.data[i + 0] = 255;
newImgData.data[i + 1] = 0;
newImgData.data[i + 2] = 0;
newImgData.data[i + 3] = 255;
}
// ctx.putImageData(newImgData, imageData.width, 0);
});
});
</script>
</body>
</html>
Last Edit: I found out the solution.
As it turns out, my issue was the version of JQuery I was using. I was originally just version 1, but 1.8.3 fixed everything. So, the solution is to simply change
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
to
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>