I am a very beginner web designer and I have two questions about this code.
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#canvas1{border: #666 3px solid;}
</style>
<script type="application/javascript" language="javascript">
function draw (x,y){
var canvas = document.getElementById('canvas1');
var ctx = canvas.getContext('2d');
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillstyle = "rgb (0,200, 0)";
ctx.fillRect(x, 20, 50, 50);
ctx.restore();
x += 5;
var loop = setTimeout('draw('+x+', '+y+')', 100);
}
</script>
</head>
<body>
<button onclick="draw(0,0)">Start</button>
<canvas id="canvas1" width="400" height="400"</canvas>
</body>
</html>
Why does the block always turn out black? and Why if I try to press start again, the clearRect function doesn’t work?
When you click start you are starting a new loop that runs parallel to the one already started and depending on which one executes first your canvas would be cleared and filled twice (or as many times you started the loop - the more the more pronounced the flicker will be).
You need a mechanism to prevent the loop from starting several times. One way is to use a flag. I would also suggest you refactor the code a bit separating the loop and the drawing:
Live demo here
/// put these outside, no need to re-allocate them each time
var canvas = document.getElementById('canvas1');
var ctx = canvas.getContext('2d');
var w = canvas.width;
var h = canvas.height;
var x = 0;
var isRunning = false;
/// all loop-related here:
function loop() {
/// call draw from inside the loop instead
draw(x, 0); /// you are never using y so I just set 0 here...
x += 5;
if (isRunning && x <= w) {
requestAnimationFrame(loop); /// this is a better alternative
//setTimeout(loop, 100); /// optionally, but not recommended
} else {
isRunning = false; /// box is outside visible area so we'll stop..
}
}
/// keep draw() "clean", no loop code in here:
function draw(x, y) {
/// no need for save/restore here...
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "rgb(0, 200, 0)";
ctx.fillRect(x, 20, 50, 50);
}
Your fillStyle was typed wrong, your rgb(... must be without space (as mentioned in the other answer - but it would only result in the fill style being black in this case), in addition you're missing a closing bracket for your canvas tag in the html.
To check for button clicks this is a more recommended way instead of inlining the JS in the html:
/// check for button clicks (start):
document.getElementById('start').addEventListener('click', function() {
if (!isRunning) { /// are we running? if not start loop
isRunning = true; /// set flag so we can prevent multiple clicks
x = 0; /// reset x
loop(); /// now we start the *loop*
}
}, false);
And in your html:
<button id="start">Start</button>
Now you can easily make a pause button:
<button id="stop">Stop</button>
and add this to the script:
document.getElementById('stop').addEventListener('click', function() {
isRunning = false;
}, false);
Because you need to use:
ctx.fillStyle = "rgb(0,200,0)";
with a capital 'S' and no space between 'rgb' and the opening bracket: http://jsfiddle.net/dtHjf/
Then, in terms of why pressing 'Start' multiple times causes the square to flicker, that's because each time you press it you're starting another animation loop, but without cancelling the old one, so that you've got multiple loops fighting with each other. If you make sure to cancel any pre-existing loop before you start a new one, you should be fine:
http://jsfiddle.net/dtHjf/2/
HTML:
<button id="startAnim">Start</button><br />
<canvas id="canvas1" width="400" height="400"></canvas>
JS:
var canvas = document.getElementById('canvas1'),
ctx = canvas.getContext('2d'),
loop;
function draw(x, y) {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "rgb(0,200,0)";
ctx.fillRect(x, 20, 50, 50);
ctx.restore();
x += 5;
loop = setTimeout(function(){draw(x,y)}, 100);
}
document.getElementById('startAnim').onclick = function(){
clearTimeout(loop);
draw(0,0);
};
Also, this is unrelated to your problem, but you might want to take a look at requestAnimationFrame:
https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame
http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
Related
This question already has answers here:
redrawing canvas html5 without flickering
(2 answers)
Closed 1 year ago.
I'm making a game that utilizes an HTML canvas and draws images on it. 60 times every second it clears the screen and redraws all the elements to create animations. However, when I use images instead of just shapes, the images will be there, but will flash in and out and won't be visible all the time. I've made a game like this before(https://graphics-game.napoleon1027.repl.co/) and it was composed of entirely squares and circles and never had issues. Then when I added an image it began to flash in and out.
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="900" height="600" style="border:1px solid #d3d3d3;">
</canvas>
<script>
var xcoord = 50
var ycoord = 50
function drawscreen() {
var ctx = document.getElementById('canvas').getContext('2d');
void ctx.clearRect(0, 0, 900, 600);
image = new Image()
image.src = "https://upload.wikimedia.org/wikipedia/en/c/c8/Very_Black_screen.jpg"
void ctx.drawImage(image, 0, 0, 200, 200, xcoord, ycoord, 50, 50);
ycoord++
xcoord++
}
setInterval(drawscreen, 16);
</script>
</body>
</html>
I've not reproduced your flickering behavior with the few lines of code you gave. However, there are things you can do to optimize your code from what can be seen here:
Do the least possible operations while drawing a frame:
Get your canvas drawing context only once
Load your image only once
Use window.requestAnimationFrame to run your drawing function when the client is ready to draw a new frame (aiming for 60fps)
window.addEventListener('DOMContentLoaded', _e => {
const c = document.querySelector('#game-screen');
if (!c || !c.getContext)
return;
runGame();
function runGame() {
//Resources
const ctx = c.getContext('2d');
const image = new Image();
image.src = "https://picsum.photos/200";
//Game state
let xcoord = 50;
let ycoord = 50;
//Last rendered frame timestamp
let lastTs;
//Launch animation
window.requestAnimationFrame(drawFrame);
function drawFrame(timestamp) {
//Draw frame
ctx.clearRect(0, 0, 900, 600);
ctx.drawImage(image, 0, 0, 200, 200, xcoord, ycoord, 50, 50);
//Don't count on actual frame rate to compute moves
//ycoord++; xcoord++; //Will appear slower if frameRate on client < 60fps
updatePos(timestamp, lastTs);
lastTs = timestamp;
//Carry on animation
window.requestAnimationFrame(drawFrame);
}
function updatePos(timestamp, lastTs) {
//Move picture diagonaly at 60px/s
if (lastTs) {
const delta = timestamp - lastTs;
const deltaXY = Math.floor(delta * 60 / 1000); //60px per second
ycoord += deltaXY;
xcoord += deltaXY;
}
}
}
});
canvas {
border: 1px solid black;
}
<canvas id="game-screen" width="900" height="600">
Canvas not supported
</canvas>
I want to move a clip region's position and then draw in the clip region. How come the following approach is not working?
Thanks for any enlightenment :-)
Gerard
<canvas id="canvas" width="150" height="150"></canvas>
<script>
function fade() {
var level = 0;
var xClip=0, yClip=0;
var step = function ( ) {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.fillRect(0,0,150,150);
// Create a circular clipping path
ctx.beginPath();
ctx.arc( xClip, xClip, 60, 0, Math.PI*2, true);
ctx.clip();
// draw background
var lingrad = ctx.createLinearGradient(0,-75,0,75);
lingrad.addColorStop(0, '#232256');
lingrad.addColorStop(1, '#143778');
ctx.fillStyle = lingrad;
ctx.fillRect(-75,-75,150,150);
if (level < 15) {
level ++;
xClip = yClip = level*10;
console.log("level: " + level);
console.log("xClip: " + xClip);
setTimeout(step, 1000);
}
};
setTimeout(step,100);
}
fade();
</script>
Animating the clip.
When you apply the clip more than once the clip region is clipped to the existing clip. Animating the clip region without regarding this will create an ever smaller clip region.
Save and Restore.
You need to save and restore the canvas state when animating clip regions. ctx.save() save the current canvas 2D state to a stack, ctx.restore() pops the last saved state from the top of the stack. Save and restore can be nested. Each save must have a restore at some point or you will end up chewing up memory and eventually overflowing the state stack.
Fixing your code.
Your code is almost there and only requires a few modifications to do what you want. I have also moved the canvas.getContext() out of the fade function as you only need to do this once.
function fade() {
var level = 0;
var xClip=0, yClip=0;
var step = function ( ) {
ctx.fillRect(0,0,150,150);
//---------------------------------------------
ctx.save(); // push the current unclipped state to the state stack.
// Create a circular clipping path
ctx.beginPath();
ctx.arc( xClip, xClip, 60, 0, Math.PI*2, true);
ctx.clip();
ctx.fillStyle = lingrad;
ctx.fillRect(-75,-75,150,150);
if (level < 15) {
level ++;
xClip = yClip = level*10;
setTimeout(step, 1000);
}
//---------------------------------------------
ctx.restore(); // pop the last saved state from the state stack
// restoring the clip region to the default
// ready for the next call to step.
};
setTimeout(step,100);
}
// get the 2D context only once
var ctx = document.getElementById('canvas').getContext('2d');
// As the gradient does not change over time don't create this every
// frame as it is a expensive operation
var lingrad = ctx.createLinearGradient(0,-75,0,75);
lingrad.addColorStop(0, '#232256');
lingrad.addColorStop(1, '#143778');
fade();
I am trying to make a racing game using canvas, and a png file with transparent background for the car.
I have a problem when I hold down on one of the arrows. In the opposite way of the movement it appears like a white shadow the old position of the car.
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
var canvas;
var ctx;
var car = new Image();
var x = 0;
var y = 0;
function startUp(){
canvas = document.getElementById('myCanvas');
ctx = canvas.getContext('2d');
loadImages();
startGameLoop();
}
function startGameLoop() {
setInterval(function() {
drawScreen();
drawCar();
}, 16);
window.addEventListener('keydown', whatKey, true);
}
function drawScreen(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#aaaaaa';
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fill();
}
function drawCar(){
ctx.drawImage(car, x ,y, 200, 103);
}
function whatKey(evt) {
switch (evt.keyCode) {
// Left arrow.
case 37: {
x -= 15;
}
break;
// Right arrow.
case 39:{
x += 15;
}
break;
// Down arrow
case 40:{
y += 15;
}
break;
// Up arrow
case 38: {
y -= 15;
}
break;
}
}
function loadImages() {
car.src = 'http://sammywasem.com/images/f3-top.png';
}
</script>
</head>
<body onload="startUp()">
<canvas id="myCanvas" width="1050" height="620">
</canvas>
</body>
</html>
The car move is in fact the right way.
Say you press the right key : the event is sent to the window, and trapped by your code : the car goes right. But the event is also handled by the whole window, where it is understood as : move the window to the right.
So what you see in the end, since the window move more than the car, is a move to the left of the car.
What you need to do is to prevent the key event from bubbling further once you handled it : it is done with
event.preventDefault();
and
event.stopPropagation();
look mdn to see the specifications of preventDefault or stopPropagation .
i've done a fiddle out of your code, and updated it :
http://jsbin.com/joboxuvu/1/edit?js,output
<script>
function hum() {
var width = 280,
height = 210,
frames = 3,
currentFrame = 0,
hump = document.getElementById("Canvas2").getContext('2d');
humpImage = new Image()
humpImage.src = 'images/hum.png';
var draw = function () {
hump.clearRect(0, 0, width, height);
hump.drawImage(humpImage, 0, height * currentFrame, width, height, 0, 0, width, height);
if (currentFrame == frames) {
currentFrame = 0;
} else {
currentFrame++;
}
}
setInterval(draw, 150);
}
</script>
The above is a canvas animation javascript code . Here it loops automatically.. I want to make to cancel the loop and to play the canvas only on click how to do it.
Please spot light on this
Saravan, theres a number of different ways you could go about this. Best practise, I believe is to use the RequestAnimationFrame function, though I don't seem to find any old source code here in which I've used it.
I notice in your code, each time around the draw-loop, you call setInterval. Unless I'm mistaken, the correct function to use there is actually setTimeOut. As you can see from my code, (and js help) the function setInterval only needs to be called once.
When you call it, it gives you a unique identifier which can be used later to remove the interval handler. That's the method I've used. When the button is clicked, the interval is set and it's handle saved. Then, when the canvas is clicked, I simply remove the interval handler.
It's late and I'm tired. This should give you plenty to go on.
EDIT: new code.
<!DOCTYPE html>
<html>
<head>
<script>
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
window.addEventListener('load', mInit, false);
var drawIntervalHandle;
var hump, humpImage, isRunning = false;
var width = 44,height = 128,frames = 3,currentFrame = 0;
var myImageSource = 'thermometer.png';
function mInit()
{
byId('Canvas2').addEventListener('click', onCanvasClick, false);
initAndStartAnim();
}
function onCanvasClick()
{
if (isRunning)
{
clearInterval(drawIntervalHandle);
isRunning = false;
byId('status').innerHTML = "Stopped, frame: " + currentFrame;
}
else
{
drawIntervalHandle = setInterval(draw, 150);
isRunning = true;
}
}
function draw()
{
hump.clearRect(0, 0, width, height);
hump.drawImage(humpImage, 0, height * currentFrame, width, height, 0, 0, width, height);
if (currentFrame == frames)
currentFrame = 0;
else
currentFrame++;
byId('status').innerHTML = "Running, frame: " + currentFrame;
}
function initAndStartAnim()
{
var can = document.getElementById("Canvas2");
can.width = width;
can.height = height;
hump = can.getContext('2d');
humpImage = new Image()
humpImage.src = myImageSource;
drawIntervalHandle = setInterval(draw, 150);
isRunning = true;
}
</script>
<style>
</style>
</head>
<body>
<canvas id='Canvas2'>Canvas not supported if you see this. :(</canvas>
<br>
<div id='status'></div>
</body>
</html>
Basically, a user uploads a picture and then can paint on it, and save the result. Another user can then view the photo and if they click in the same area as painted, something happens.
So user 1 can make an area click-able for user 2 by drawing on the photo.
now the upload bit is fine, and painting with help from a tutorial and example I've got sussed out. But defining what area is click-able is a bit harder. For something like a rectangle its easy enough, I made an example.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var button = new Object();
button.x = 50;
button.y = 50;
button.width = 50;
button.height = 50;
button.rgb = "rgb(0, 0, 255)";
function drawbutton(buttonobject)
{
context.fillStyle = buttonobject.rgb;
context.fillRect (buttonobject.x, buttonobject.y, buttonobject.width, buttonobject.height);
context.strokeRect(buttonobject.x, buttonobject.y, buttonobject.width, buttonobject.height);
}
drawbutton(button);
function checkIfInsideButtonCoordinates(buttonObj, mouseX, mouseY)
{
if(((mouseX > buttonObj.x) && (mouseX < (buttonObj.x + buttonObj.width))) && ((mouseY > buttonObj.y) && (mouseY < (buttonObj.y + buttonObj.height))))
return true;
else
return false;
}
$("#myCanvas").click(function(eventObject) {
mouseX = eventObject.pageX - this.offsetLeft;
mouseY = eventObject.pageY - this.offsetTop;
if(checkIfInsideButtonCoordinates(button, mouseX, mouseY))
{
button.rgb = "rgb(0, 255, 0)";
drawbutton(button);
} else {
button.rgb = "rgb(255, 0, 0)";
drawbutton(button);
}
});
but when it comes to other shapes like circles, or just someone smothering the page, how would you go about detecting that ?
one thought I had was using the edited layer, making it hidden, and detecting a pixel color of say blue, from here but that limits the color use of the photo and im not entirely sure how to implement it. any other ideas ?
EDIT:
I figured out circles after some tinkering, using Pythagoras theorem to see if mouse coordinates are smaller than the radius, but this assumes circle center of 0,0, so then offset mouse by circles actual center. example
function checkIfInsideButtonCoordinates(buttonObj, mouseX, mouseY) {
actualX = mouseX - buttonObj.x
actualY = mouseY - buttonObj.y
mousesqX = actualX * actualX
mousesqY = actualY * actualY
sqR = buttonObj.r * buttonObj.r
sqC = mousesqX + mousesqY
if (sqC < sqR) return true;
else return false;
}
Here’s how to test whether user#2 is inside user#1’s paintings
Create a second canvas used to hit-test whether user#2 is inside of user#1’s paintings.
The hit-test canvas is the same size as the drawing canvas, but it only contains user#1’s paintings…not the image.
When user#1 is painting, also draw their paintings on the hit canvas.
When user#1 is done painting, save all their paintings from the hit canvas.
You have at least 2 ways to save user#1’s paintings from the hit canvas:
Serialize all the canvas commands needed to recreate the shapes/paths that user#1 paints.
Save the hit canvas as an image using canvas.toDataURL.
When user#2 clicks, check if the corresponding pixel on the hit canvas is filled or is transparent (alpha>0).
// getImageData for the hit-test canvas (this canvas just contains user#1's paintings)
imageDataData=hitCtx.getImageData(0,0,hit.width,hit.height).data;
// look at the pixel under user#2's mouse
// return true if that pixel is filled (not transparent)
function isHit(x,y){
var pixPos=(x+y*hitWidth)*4+3;
return( imageDataData[pixPos]>10)
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/etA5a/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:15px; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var hit=document.getElementById("hit");
var hitCtx=hit.getContext("2d");
var user2=document.getElementById("user2");
var ctx2=user2.getContext("2d");
var canvasOffset=$("#user2").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var imageDataData;
var hitWidth=hit.width;
var img=document.createElement("img");
img.onload=function(){
// left canvas: image+user#1 paintings
ctx.globalAlpha=.25;
ctx.drawImage(img,0,0);
ctx.globalAlpha=1.00;
scribble(ctx,"black");
// mid canvas: just user#1 paintings (used for hittests)
scribble(hitCtx,"black");
// right canvas: user#2
ctx2.drawImage(img,0,0);
imageDataData=hitCtx.getImageData(0,0,hit.width,hit.height).data;
}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/colorhouse.png";
function scribble(context,color){
context.beginPath();
context.moveTo(70,2);
context.lineTo(139,41);
context.lineTo(70,41);
context.closePath();
context.rect(39,54,22,30);
context.arc(73,115,3,0,Math.PI*2,false);
context.fillStyle=color;
context.fill();
}
function handleMouseMove(e){
var mouseX=parseInt(e.clientX-offsetX);
var mouseY=parseInt(e.clientY-offsetY);
// If user#2 has a hit on user#1's painting, mid-canvas turns red
var color="black";
if(isHit(mouseX,mouseY)){ color="red"; }
scribble(hitCtx,color);
}
function isHit(x,y){
var pixPos=(x+y*hitWidth)*4+3;
return( imageDataData[pixPos]>10)
}
$("#user2").mousemove(function(e){handleMouseMove(e);});
}); // end $(function(){});
</script>
</head>
<body>
<p>Left: original image with user#1 painting</p>
<p>Mid: user#1 painting only (used for hit-testing)</p>
<p>Right: user#2 (move mouse over hit areas)</p>
<canvas id="canvas" width=140 height=140></canvas>
<canvas id="hit" width=140 height=140></canvas>
<canvas id="user2" width=140 height=140></canvas><br>
</body>
</html>