I plan on using the coordinates of my sprites in a canvas to make a board game by making the canvas a background image.
Here is the 8x8 board's js code.
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
c.fillStyle = "#ffb933";
c.fillRect(0,0,100,100);
c.fillRect(200,0,100,100);
c.fillRect(400,0,100,100);
c.fillRect(600,0,100,100);
c.fillRect(100,100,100,100);
c.fillRect(300,100,100,100);
c.fillRect(500,100,100,100);
c.fillRect(700,100,100,100);
c.fillRect(0,200,100,100);
c.fillRect(200,200,100,100);
c.fillRect(400,200,100,100);
c.fillRect(600,200,100,100);
c.fillRect(100,300,100,100);
c.fillRect(300,300,100,100);
c.fillRect(500,300,100,100);
c.fillRect(700,300,100,100);
c.fillRect(0,400,100,100);
c.fillRect(200,400,100,100);
c.fillRect(400,400,100,100);
c.fillRect(600,400,100,100);
c.fillRect(100,500,100,100);
c.fillRect(300,500,100,100);
c.fillRect(500,500,100,100);
c.fillRect(700,500,100,100);
c.fillRect(0,600,100,100);
c.fillRect(200,600,100,100);
c.fillRect(400,600,100,100);
c.fillRect(600,600,100,100);
c.fillRect(100,700,100,100);
c.fillRect(300,700,100,100);
c.fillRect(500,700,100,100);
c.fillRect(700,700,100,100);
Is there any way I can make this a background image of my html file so I can still use the same coordinates and make new sprites above it which I could manipulate with js also. Thanks!
You can significantly reduce that ...
It is just a couple of loops and a condition
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
c.fillStyle = "#ffb933";
size = 20
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
if ((i + j) % 2 != 0)
c.fillRect(i * size, j * size, size, size);
}
}
<canvas width=200 height=200>
If you are set on using an image an easy option is with an SVG image
I got this one from wikimedia:
https://upload.wikimedia.org/wikipedia/commons/7/70/Checkerboard_pattern.svg
function svgimage() {
var image = `
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 5 5">
<rect width="5" height="5"/>
<path d="M0,0V5H1V0zM2,0V5H3V0zM4,0V5H5V0zM0,0H5V1H0zM0,2H5V3H0zM0,4H5V5H0z" fill="#fff" fill-rule="evenodd"/>
</svg>`;
return encodeURIComponent(image);
}
function drawImage() {
ctx.drawImage(img, 0, 0, 500, 500);
}
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = drawImage
img.src = 'data:image/svg+xml;charset=utf-8,' + svgimage();
<canvas id=canvas width=500 height=500></canvas>
You can draw anything you like on top of that, here is an example:
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
c.globalAlpha = 0.5
function drawBoard() {
c.beginPath()
c.fillStyle = "#ffb933";
size = 20
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
if ((i + j) % 2 != 0)
c.fillRect(i * size, j * size, size, size);
}
}
}
delta = 0
function draw() {
c.clearRect(0, 0, canvas.width, canvas.height)
drawBoard()
delta += 0.05
x = Math.sin(delta) * 40 + 80
y = Math.cos(delta) * 20 + 60
c.beginPath()
c.fillStyle = "#ff0000";
c.arc(x, y, 10, 0, 2 * Math.PI);
c.fill()
}
setInterval(draw, 20);
<canvas width=200 height=200>
You can see I converted the double loop example into a function that we call every time we draw
Related
This question already has answers here:
Canvas is stretched when using CSS but normal with "width" / "height" properties
(10 answers)
Closed last month.
I've made a basic snake game, and I'm trying to improve one minor detail.
You see, when I place the apple image [or leaf image because I had an SVG image for that handy], the canvas scales the image and makes it quite big.
Here is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>
Basic Snake Game
</title>
</head>
<body>
<svg style="width:50px;height:50px;"><polygon points="10,10 10,14 10.5,17 12,23 14,30 16,34 18,36 22,37 25,37 25,30 24.5,28 24,27 23.5,26 11,10" style="fill:lime;stroke:green;stroke-width:1;" /><line x1="10" y1="10" x2="25" y2="37" style="stroke:green;stroke-width:1;" /></svg>
<script>
var html = document.querySelector('body');
html.innerHTML += '<p></p><br/><input type="button" onclick="heading -= 90" value="Left 90*" style="font-size:20pt;"/><input type="button" onclick="heading += 90" value="Right 90*" style="font-size:20pt;"/><canvas style="width:1000px;height:700px;border:3px solid red;"></canvas>';
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var img = document.querySelector('svg');
var xml = new XMLSerializer().serializeToString(img);
img = new Image();
img.src = 'data:image/svg+xml;base64,' + btoa(xml);
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 2000, 1400);
var width = 15;
var speed = 1;
var heading = 0;
var pos = [15, 10];
var leafPos;
leafPos = [Math.floor(Math.random() * 250), Math.floor(Math.random() * 75)];
ctx.fillRect(30, 20, width, 10);
var main = setInterval(function() {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 2000, 1400);
ctx.fillStyle = '00ff00';
if(heading == -90) {
heading = 270;
} if(heading == 360) {
heading = 0;
} if(heading == 0) {
pos[0] += speed;
} if(heading == 180) {
pos[0] -= speed;
} if(heading == 0 || heading == 180) {
ctx.fillRect(pos[0], pos[1], width, 5);
} if(heading == 270) {
pos[1] -= speed;
} if(heading == 90) {
pos[1] += speed;
} if(heading == 270 || heading == 90) {
ctx.fillRect(pos[0], pos[1], 5, width);
}
if((Math.abs(leafPos[0] - pos[0]) + Math.abs(leafPos[1] - pos[1])) <= 15) {
speed += 0.5;
width ++;
leafPos = [Math.floor(Math.random() * 250), Math.floor(Math.random() * 75)];
}
ctx.drawImage(img, leafPos[0], leafPos[1]); // The line of interest
document.querySelector('p').innerHTML = (speed - 1) *10;
}, 50);
</script>
</body>
</html>
The original SVG image is at the top for reference.
To remedy this, I tried putting ctx.scale(0.5, 0.5) before the main interval and doubling all x, y, width, and height values EXCEPT that of our line of interest. This worked, but the image quality was compromised quite badly. I also tried editing the line of interest by scaling its width and height:
ctx.drawImage(img, leafPos[0], leafPos[1], img.width /2, img.height /2)
However, this produced the same result.
Is there some way to disable automatic scaling of images to avoid this altogether?
OR
Is there some way to resize images without reducing image quality?
Any help is appreciated.
Instead of setting the canvas size on the style:
<canvas style="width:1000px;height:700px;border:3px solid red;"></canvas>'
You should set it like this:
<canvas width=1000 height=700 style="border:3px solid red;"></canvas>
See working sample below
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var img = document.querySelector('svg');
var xml = new XMLSerializer().serializeToString(img);
img = new Image();
img.src = 'data:image/svg+xml;base64,' + btoa(xml);
function draw() {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 2000, 1400);
ctx.fillStyle = '00ff00';
ctx.drawImage(img, 20, 20);
}
setInterval(draw, 50);
<svg style="width:50px;height:50px;">
<polygon points="10,10 10,14 10.5,17 12,23 14,30 16,34 18,36 22,37 25,37 25,30 24.5,28 24,27 23.5,26 11,10" style="fill:lime;stroke:green;stroke-width:1;" />
<line x1="10" y1="10" x2="25" y2="37" style="stroke:green;stroke-width:1;" />
</svg>
<canvas width=1000 height=700 style="border:3px solid red;"></canvas>
I am new to html5 canvas. I want to draw one canvas grid and fill each grid square with an image from an API response. I have following code to draw the grid but I am struggling to fill each square with image.
This is js code:
window.onload = function(){
var c= document.getElementById('canvas');
var ctx=c.getContext('2d');
ctx.strokeStyle='white';
ctx.linWidth=4;
for(i=0;i<=600;i=i+60)
{
ctx.moveTo(i,0);
ctx.lineTo(i,600);
ctx.stroke();
}
for(j=0; j<=600; j=j+60)
{
ctx.moveTo(0,j);
ctx.lineTo(600,j);
ctx.stroke();
}
}
This code helps me drawing canvas grid but how to access each square and fill it with the image. I referred links related to this but seems difficult to understand. Can somebody please help me with this?
It's hard to answer your question without knowing exactly how the image is being returned by the api response. Assuming the api response is returning the image itself (not the image data in JSON or something like that) here is a solution:
html:
<canvas id="canvas" width="600" height="600"></canvas>
javascript:
window.onload = function() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeStyle = "green";
ctx.lineWidth = 4;
//draw grid
for (let i = 0; i <= 10; i++) {
const x = i*60;
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
const y = i*60;
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
//draw images
const p = ctx.lineWidth / 2; //padding
for (let xCell = 0; xCell < 10; xCell++) {
for (let yCell = 0; yCell < 10; yCell++) {
const x = xCell * 60;
const y = yCell * 60;
const img = new Image();
img.onload = function() {
ctx.drawImage(img, x+p, y+p, 60-p*2, 60-p*2);
};
//TODO: set img.src to your api url instead of the dummyimage url.
img.src = `https://dummyimage.com/60x60/000/fff&text=${xCell},${yCell}`;
}
}
};
Working example:
https://codepen.io/rockysims/pen/dLZgBm
Only want shadows to animate and keep the fillText from animating due to letters pixelating from getting ran over and over.
var canvas = document.getElementById('canvas')
var ctx = this.canvas.getContext('2d')
var width = canvas.width = canvas.scrollWidth
var height = canvas.height = canvas.scrollHeight
var start;
var j=0;
var makeText = function(){
j+=1
ctx.shadowColor= 'red';
ctx.shadowOffsetX = j; //animate
ctx.shadowOffsetY = j; //animate
ctx.globalAlpha=0.5;
ctx.font = "48px serif";
ctx.fillStyle = "black";
ctx.fillText('hey you', width/2, height / 2); //Only ran once so letters
//don't pixelate!
}
function animateText(timestamp){
var runtime = timestamp - start;
var progress = Math.min(runtime / 1400, 1);
makeText(progress)
if(progress < 1){
requestAnimationFrame(animateText)
}else {
return;
}
}
requestAnimationFrame(function(timestamp){
start = timestamp;
animateText(timestamp)
})
<canvas id="canvas" width=500px height=500px></canvas>
My outcome of the process would only have shadows animate and keeping letters where they are
Just draw your own shadows, here is an example:
var canvas = document.getElementById('canvas')
var ctx = this.canvas.getContext('2d')
ctx.font = "68px serif";
var base = {text: 'hey you', x: 10, y: 60 }
var inc = 2;
var j = 30;
var makeText = function() {
ctx.globalAlpha = 1;
ctx.fillStyle = "black";
ctx.fillText(base.text, base.x, base.y);
}
var makeshadow = function(offset) {
ctx.fillStyle = "red";
for (var i = 0; i < offset; i++) {
ctx.globalAlpha = 1/i;
ctx.fillText(base.text, base.x + i, base.y + i);
}
}
function animateText() {
ctx.clearRect(0, 0, 999, 999)
makeshadow(j);
makeText();
j += inc;
if (j > 35 || j < 3) inc *= -1
}
setInterval(animateText, 50)
<canvas id="canvas" width=300px height=170px></canvas>
And if you add some math in the mix you can get some cool effects:
var canvas = document.getElementById('canvas')
var ctx = this.canvas.getContext('2d')
ctx.font = "68px serif";
var base = {text: '123456', x: 30, y: 80 }
var inc = 5;
var j = 0;
var makeText = function() {
ctx.globalAlpha = 1;
ctx.fillStyle = "black";
ctx.fillText(base.text, base.x, base.y);
}
var makeshadow = function(offset) {
ctx.globalAlpha = 0.05;
ctx.fillStyle = "red";
for (var i = 0; i < offset; i++)
ctx.fillText(base.text, base.x + Math.sin(i/5)*10, base.y + Math.cos(i/5)*15);
}
function animateText() {
ctx.clearRect(0, 0, 999, 999)
makeshadow(j);
makeText();
j += inc;
if (j > 120 || j < 0) inc *= -1
}
setInterval(animateText, 50)
<canvas id="canvas" width=300px height=170px></canvas>
Your main issue (the text pixelisation) is due to you not clearing the canvas between every frames, and drawing again and again over the same position. semi-transparent pixels created by antialiasing mix up to more and more opaque pixels.
But in your situation, it seems that you actually want at-least the shadow to mix up like this.
To do it, one way would be to draw only once your normal text, and to be able to draw only the shadow, behind the current drawing.
Drawing only the shadow of a shape.
One trick to draw only the shadows of your shape is to draw your shape out of the visible viewPort, with shadowOffsets set to the inverse of this position.
var text = 'foo bar';
var ctx = canvas.getContext('2d');
var original_x = 20; // the position it would have been
ctx.font = '30px sans-serif';
var targetPosition = ctx.measureText(text).width + original_x + 2;
// default shadow settings
ctx.shadowColor = 'red';
ctx.shadowBlur = 3;
// just to show what happens
var x = 0;
anim();
function anim() {
if(++x >= targetPosition) {
x=0;
return;
}
// if we weren't to show the anim, we would use 'targetPosition'
// instead of 'x'
ctx.shadowOffsetX = x;
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillText(text, -x + original_x, 30);
requestAnimationFrame(anim);
}
// restart the anim on click
onclick = function() {
if(x===0)anim();
};
<canvas id="canvas"></canvas>
Once we have this clear shadow, without our shape drawn on it, we can redraw it as we wish.
Drawing behind the current pixels
The "destination-over" compositing option does just that.
So if we put these together, we can draw behind the normal text, and only draw our shadow behind it at each frame, avoiding antialiasing mix-up.
(Note that we can also keep the clean shadow on an offscreen canvas for performances, since shadow is a really slow operation.)
var text = 'foo bar';
var ctx = canvas.getContext('2d');
ctx.font = '48px sans-serif';
var x = 20;
var y = 40;
var shadow = generateTextShadow(ctx, text, x, y, 'red', 5);
ctx.globalAlpha = 0.5;
ctx.fillText(text, x, y);
// from now on we'll draw behind current content
ctx.globalCompositeOperation = 'destination-over';
var shadow_pos = 0;
anim();
// in the anim, we just draw the shadow at a different offset every frame
function anim() {
if(shadow_pos++ > 65) return;
ctx.drawImage(shadow, shadow_pos, shadow_pos);
requestAnimationFrame(anim);
}
// returns a canvas where only the shadow of the text provided is drawn
function generateTextShadow(original_ctx, text, x, y, color, blur, offsetX, offsetY) {
var canvas = original_ctx.canvas.cloneNode();
var ctx = canvas.getContext('2d');
ctx.font = original_ctx.font;
var targetPosition = ctx.measureText(text).width + 2;
// default shadow settings
ctx.shadowColor = color || 'black';
ctx.shadowBlur = blur || 0;
ctx.shadowOffsetX = targetPosition + x +(offsetX ||0);
ctx.shadowOffsetY = (offsetY || 0);
ctx.fillText(text, -targetPosition, y);
return canvas;
}
<canvas id="canvas"></canvas>
I have a percentage that I want to represent as dots on a canvas. This is essentially a 10 x 10 matrix where I have a dot or image that represents one percent. so when it is 100% it will be green and when it is 10% only 10 of the dots will be green and the rest red.
Any idea what would be the best way to approach this problem?
something similar to this:
Except they should be circles/images instead of squares?
Here i created a simple example. Hope it'd be helpful to you.
var canvas = document.getElementById('mycanvas');
var context = canvas.getContext('2d');
var sizeX = canvas.width / 10;
var sizeY = canvas.height / 10;
var total = 15;
var count = 0;
for (var j = 0; j < 10; j++) {
for (var i = 0; i < 10; i++) {
context.beginPath();
context.arc(sizeX * (i+.5), sizeY * (j+.5), sizeX / Math.PI, 0, 2 * Math.PI, false);
context.fillStyle = total > count ? 'green' : 'red';
context.fill();
count++;
}
}
<canvas id="mycanvas" width="200" height="200" style="border:1px solid black;"></canvas>
WHAT? I am attempting to use canvas and JavaScript to display an animation on top of a grid which also must be drawn using JavaScript. https://jsfiddle.net/cp1wqeeg/6/
PROBLEM! To remove the previous frames of the animation I have used clearRect(). This however breaks my grid which I do not want :(
JSFiddle: https://jsfiddle.net/cp1wqeeg/5
ctx.clearRect(50, 100, width, height);
QUESTION How can I remove the previous frames of my animation without breaking the grid behind my sprite?
The common action here is to clear all and redraw everything.
But it may become cumbersome if e.g in your case, your background doesn't change.
In this case, an simple solution, is to use offscreen canvases, that will act as layers.
First you draw you grid on this off-screen canvas in the init phase.
Then in your loop, you just draw your offscreen canvas on the main context, with the drawImage method.
var canvas = document.getElementById("myCanvas"),
ctx = canvas.getContext("2d"),
fov = 300,
viewDist = 5,
w = canvas.width / 2,
h = canvas.height / 2,
// here we create an offscreen canvas for the grid only
gridCtx = canvas.cloneNode().getContext('2d'),
angle = 0,
i, p1, p2,
grid = 5;
function initGrid(){
/// create vertical lines on the off-screen canvas
for(i = -grid; i <= grid; i++) {
p1 = rotateX(i, -grid);
p2 = rotateX(i, grid);
gridCtx.moveTo(p1[0], p1[1]);
gridCtx.lineTo(p2[0], p2[1]);
i++;
}
/// create horizontal lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(-grid, i);
p2 = rotateX(grid, i);
gridCtx.moveTo(p1[0], p1[1]);
gridCtx.lineTo(p2[0], p2[1]);
}
gridCtx.stroke();
}
function rotateX(x, y) {
var rd, ca, sa, ry, rz, f;
rd = angle * Math.PI / 180;
ca = Math.cos(rd);
sa = Math.sin(rd);
ry = y * ca;
rz = y * sa;
f = fov / (viewDist + rz);
x = x * f + w;
y = ry * f + h;
return [x, y];
}
initGrid();
var width = 200,
height = 200,
frames = 2,
currentFrame = 0,
imageSprite = new Image()
imageSprite.src = 'https://s27.postimg.org/eg1cjz6cz/sprite.png';
var drawSprite = function(){
ctx.clearRect(0,0,canvas.width, canvas.height);
ctx.drawImage(gridCtx.canvas, 0,0); // now draw our grid canvas
ctx.drawImage(imageSprite, 0, height * currentFrame, width, height, 50, 100, width, height);
if (currentFrame == frames) {
currentFrame = 0;
} else {
currentFrame++;
}
}
setInterval(drawSprite, 500);
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #c3c3c3;"></canvas>