Understanding HTML5 Canvas Element - javascript

I am read tons of documentation about save/restore canvas states, but still confused with next example.
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
// save default state
ctx.save();
// draw new arc with new settings
ctx.lineWidth = 2;
ctx.fillStyle = '#bfb';
ctx.strokeStyle = '#999';
ctx.beginPath();
ctx.arc(50, 50, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
ctx.stroke();
// restore default state
ctx.restore();
// save default state again
ctx.save();
// draw line with new settings
ctx.lineWidth = 4;
ctx.strokeStyle = '#000';
ctx.moveTo(50, 50);
ctx.lineTo(100, 100);
ctx.stroke();
// restore default state
ctx.restore();
// save default state third time
ctx.save();
// draw round circle with new settings
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = '#999';
ctx.arc(100, 100, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = '#bfb';
ctx.fill();
ctx.stroke();
// restore default state
ctx.restore();
}
draw();
My logic in code comments, but result absolutely not expected. First circle has a settings from line. Circles should have different style from line.

I am not good at canvas just yet but with some basic learning I think
You are missing ctx.beginPath(); before starting to draw path.
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
// save default state
ctx.save();
// draw new arc with new settings
ctx.lineWidth = 2;
ctx.fillStyle = '#bfb';
ctx.strokeStyle = '#999';
ctx.beginPath();
ctx.arc(50, 50, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
ctx.stroke();
// restore default state
ctx.restore();
// save default state again
ctx.save();
// draw line with new settings
ctx.beginPath();
ctx.lineWidth = 4;
ctx.strokeStyle = '#000';
ctx.moveTo(50, 50);
ctx.lineTo(100, 100);
ctx.stroke();
// restore default state
ctx.restore();
// save default state third time
ctx.save();
// draw round circle with new settings
ctx.beginPath(); /* ** THIS is missing in your code ** */
ctx.lineWidth = 2;
ctx.strokeStyle = '#999';
ctx.arc(100, 100, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = '#bfb';
ctx.fill();
ctx.stroke();
// restore default state
ctx.restore();
}
draw();
DEMO
SOURCE

Related

Cube gets a little bigger when i draw it again. (Canvas, Javascript)

//Selectors
canvas = document.querySelector('.canvas');
canvas.setAttribute("tabindex", 0);
canvas.focus();
pointerImg = document.querySelector('.pointer');
//Variables
const pi = Math.PI;
const c = canvas.getContext('2d');
var grid = {
'x':60,
'y':20,
'size':20,
}
var pointerValues = {
37: {'x':-1, 'y':0},
38: {'x':0, 'y':-1},
39: {'x':1, 'y':0},
40: {'x':0, 'y':1},
}
//Event Listeners
window.addEventListener('resize', function() {
//init();
})
var cells = [];
for(let i=0;i<grid.x;i++) {
cells[i] = new Array(grid.y);
}
//Functions
function init() {
resizeGrid();
initCells();
}
init();
function update(){
requestAnimationFrame(update)
}
update();
function Cell(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.color = 'white';
this.update = function() {
this.draw();
}
this.draw = function() {
c.beginPath();
c.rect(this.x*this.size, this.y*this.size, this.size, this.size);
c.strokeStyle = 'black';
c.stroke();
c.fillStyle=this.color; c.fill();
c.closePath();
}
this.resetCell = function() {
c.clearRect(this.x, this.y, this.size, this.size);
this.update();
}
}
function resizeGrid() {
let windowX = window.innerWidth;
windowX -= windowX*0.1;
grid.size = windowX/grid.x;
let canvasX = windowX, canvasY = grid.y * grid.size;
canvas.width = canvasX;
canvas.height = canvasY;
}
function initCells() {
cells = [];
for(let i=0;i<grid.x;i++) {
cells[i] = new Array(grid.y);
}
for(let i=0;i<grid.x;i++) {
for(let j=0;j<grid.y;j++) {
cells[i][j] = new Cell(i, j, grid.size);
cells[i][j].update();
}
}
}
<html>
<head>
<link rel="stylesheet" href="assets/css/main.css">
<div style="display:none;">
<img class='pointer' src="assets/img/pointer.svg">
</div>
<div class='nav'>
</div>
</head>
<body>
<div class="content">
<canvas class='canvas'></canvas>
</div>
<script src='assets/js/canvas.js'></script>
</body>
</html>
function Cell(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.color = 'white';
this.update = function () {
this.draw();
}
this.draw = function () {
c.beginPath();
c.rect(this.x * this.size, this.y * this.size, this.size, this.size);
c.strokeStyle = 'black';
c.stroke();
c.fillStyle = this.color;
c.fill();
c.closePath();
}
this.resetCell = function () {
c.clearRect(this.x, this.y, this.size, this.size);
this.update();
}
This is my code for a single cell in a grid. After I draw it a single time, when I call resetCell(), it draws the cell just a little bit bigger.
In this picture, I call Update for the very first cell and it becomes a tiny bit bigger than the rest.
I've tried pretty much everything but nothing seems to work.
Also can someone recommend better way to draw and control grid.
I need it to demonstrate BFS algorithm.
You were tricked by the thickness of the border
The border has a thickness, which is easy to forget. If you draw a box which is X wide, its left and right border are in addition to the width of X.
So if you do NOT fill the interior, you get this nice appearance (left), but if you FILL, you get this ugly appearance (right).
When you draw a grid of these squares, each one covers over the right-hand and bottom sides of previous squares, so that it is not obvious what is happening.
Unless you redraw one that is not the last of the list, as I have done here (bottom-middle). Then it becomes obvious that something is wrong.
Here is the code to reproduce the above figures, and below I recommend a solution.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// With NO fill, it looks fine
ctx.beginPath();
ctx.rect(40, 40, 40, 40);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.rect(80, 80, 40, 40);
ctx.stroke();
ctx.closePath();
// With white fill, the inner half of each box gets covered up by the white fill.
ctx.beginPath();
ctx.rect(140, 40, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(180, 80, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
// Make Grid. This part looks OK initially.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.rect(240, 40, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(240, 80, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(280, 40, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(280, 80, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(320, 40, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(320, 80, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
// Redraw one: this will look messed up.
ctx.beginPath();
ctx.rect(280, 80, 40, 40);
ctx.stroke();
ctx.fillStyle="white";
ctx.fill();
ctx.closePath();
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="400" height="150" ></canvas>
</body>
</html>
Solution 1. Don't fill the interiors
This is the neatest solution, if you can get away with it, and it works regardless of any (non-zero) border thickness.
Solution 2. Shrink the width to allow for border thickness
This is complicated because you need to manually set the border thickness, and realise that it is in addition to the numerical values you specify for the rectangle's size.
Look at this messy business at CSS-tricks, even when they are trying to make borders interesting! https://css-tricks.com/almanac/properties/b/border/

Overlapping clipping issue

I have an issue where I am grabbing a users displayAvatar() and then I use an arc to make the image round. This works fine, however, I need to place a circle on top of that image, but it is getting cut in half because of the previous clip()
Without clip() : https://i.gyazo.com/b474c656f33a1f004f5e3becffcef527.png
With clip() : https://i.gyazo.com/da13377cd3f6dc7516c2b8fd1f0f8ac9.png
I know that in the 'With clip()' image, it appears as if the arc border is showing outside of the clip, but that is hardcoded into the image, I put it as a guide with an image editor.
I tried moving around the code, I removed the line ctx.clip() and saw that my circle displays fine on top of the image.
// circle around avatar
ctx.beginPath();
ctx.arc(122.5, 141.8, 81, 0, Math.PI * 2, true);
ctx.closePath();
ctx.clip();
const avatar = await Canvas.loadImage(
message.author.displayAvatarURL({ format: 'png' })
);
ctx.strokeStyle = '#ffffff';
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(avatar, 41.5, 60.5, 162, 162);
// presence circle
ctx.beginPath();
ctx.arc(184.5, 193.5, 19, 0, Math.PI * 2, true);
ctx.strokeStyle = '#000000';
ctx.lineWidth = 8;
ctx.stroke();
ctx.fillStyle = userStatusColor;
ctx.fill();
Take a look at the canvas clip() definition:
https://www.w3schools.com/tags/canvas_clip.asp
Tip: Once a region is clipped, all future drawing will be limited to the clipped region (no access to other regions on the canvas). You can however save the current canvas region using the save() method before using the clip() method, and restore it (with the restore() method) any time in the future.
Below is an example using the save and restore
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(90, 90, 81, 0, Math.PI * 2, true);
ctx.stroke();
ctx.save();
ctx.clip();
ctx.beginPath();
ctx.arc(150, 50, 19, 0, Math.PI * 2, true);
ctx.fillStyle = '#0000ff';
ctx.lineWidth = 8;
ctx.stroke();
ctx.fill();
ctx.restore();
ctx.beginPath();
ctx.arc(170, 99, 19, 0, Math.PI * 2, true);
ctx.fillStyle = '#ff0000';
ctx.lineWidth = 8;
ctx.stroke();
ctx.fill();
<canvas id="canvas"></canvas>

Animating an emoticon to wink with animation in HTML5 canvas

I am attempting to animate an emoticon that was previously drawn in canvas. I am attempting to do a draw and clear using frames following a tutorial but am not getting results. I have 6 frames of the emoticon coded and am unsure how to include this within the code. This is what I have so far:
<!DOCTYPE html>
<html>
<head>
<title>Adding Animation</title>
<style>
canvas {
border: 3px #CCC solid;
}
</style>
</head>
<body>
<div id="container">
<canvas id="myCanvas" height="1200" width="900"></canvas>
</div>
<script>
var mainCanvas = document.querySelector("#myCanvas");
var mainContext = mainCanvas.getContext("2d");
var canvasWidth = mainCanvas.width;
var canvasHeight = mainCanvas.height;
function drawCircle() {
mainContext.clearRect(0, 0, canvasWidth, canvasHeight);
// color in the background
mainContext.fillStyle = "#EEEEEE";
mainContext.fillRect(0, 0, canvasWidth, canvasHeight);
// draw the circle
ctx.beginPath();
ctx.strokeStyle = "000000";
ctx.lineWidth = 5;
ctx.fillStyle = "yellow";
ctx.arc(600, 450, 150, 0, Math.PI * 2, true);
ctx.stroke();
ctx.closePath();
ctx.fill();
//The smile
ctx.beginPath();
ctxstrokeStyle = "black";
ctx.lineWidth = 2;
ctx.arc(600, 475, 75, .1 * Math.PI, Math.PI * .9, false)
ctx.stroke();
ctx.closePath();
//The eyes
//Left
ctx.save();
ctx.scale(0.65, 1);
ctx.beginPath();
ctx.arc(850, 405, 40, 0 * Math.PI, Math.PI * 2, false);
ctx.fillStyle="black";
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.restore();
//Right
ctx.save();
ctx.scale(0.65, 1);
ctx.beginPath();
ctx.arc(1000,405,40, 0*Math.PI, Math.PI*2, false);
ctx.fillStyle="black";
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.restore()
}
drawCircle();
</script>
</body>
</html>
I am unsure if I am even on the right track as I have a difficult time with animation. Does anyone have any suggestions they can give me guidance on?
You have 2 names for the context: mainContext & ctx.
Change it to a single name and your face is "smiley" ! :-)
...
To animate:
Use a requestAnimationFrame loop to change the scaleY value in scale(scaleX,scaleY) over time.
Here's annotated code and a Demo:
var mainCanvas = document.querySelector("#myCanvas");
var ctx = mainCanvas.getContext("2d");
var canvasWidth = mainCanvas.width;
var canvasHeight = mainCanvas.height;
ctx.translate(-425,-275);
drawCircle(1);
// global var to hold pct the left eye is open
// 1==fully open, 0==fully closed
var scaley=1;
var direction=-1;
// request 1 animate() loop
requestAnimationFrame(animate);
function animate(time){
// draw smiley with the specified eye openness
drawCircle(scaley);
scaley+=.02*direction;
if(scaley<0){
scaley=0;
direction=1;
}
if(scaley>1){
scaley=1;
direction=-1;
}
requestAnimationFrame(animate);
}
function drawCircle(scaleY) {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// color in the background
ctx.fillStyle = "#EEEEEE";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// draw the circle
ctx.beginPath();
ctx.strokeStyle = "000000";
ctx.lineWidth = 5;
ctx.fillStyle = "yellow";
ctx.arc(600, 450, 150, 0, Math.PI * 2, true);
ctx.stroke();
ctx.closePath();
ctx.fill();
//The smile
ctx.beginPath();
ctxstrokeStyle = "black";
ctx.lineWidth = 2;
ctx.arc(600, 475, 75, .1 * Math.PI, Math.PI * .9, false)
ctx.stroke();
//The eyes
//Left
ctx.save();
// move the [0,0] origin to the left eye's centerpoint
ctx.translate(550,405);
// close the left eye by the specified scaleY
ctx.scale(0.65, scaleY);
ctx.beginPath();
// draw the left eye (arc) at 0,0 because
// we translated the origin to [550,405] earlier
ctx.arc(0, 0, 40, 0 * Math.PI, Math.PI * 2, false);
ctx.fillStyle="black";
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.restore();
//Right
ctx.save();
ctx.scale(0.65, 1);
ctx.beginPath();
ctx.arc(1000,405,40, 0*Math.PI, Math.PI*2, false);
ctx.fillStyle="black";
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.restore()
}
<canvas id="myCanvas" height="1200" width="900"></canvas>
You never declared a ctx variable.
Change all your mainContext by ctx and it should be working fine.

Canvas shadow properties affect all subsequent paths

Canvas shadow properties seem to affect all paths that follow it, even when closing the path that contains those properties. I tried changing the order of things. Ok, placing a path that doesnt require a shadow before the one that does works, but that isnt very reliable work-around. Is there any way to fix this?
Here a simple Jsfiddle demonstrating the problem: http://jsfiddle.net/zrt61283/1/
var c=document.getElementById("canvas");
var ctx=c.getContext("2d");
// Left
ctx.beginPath();
ctx.arc(100, 75, 20, 0, 2 * Math.PI);
ctx.shadowColor = 'blue';
ctx.shadowBlur = 30;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.stroke();
ctx.fill();
ctx.closePath();
// Right
ctx.beginPath();
ctx.arc(200, 75, 20, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
ctx.closePath();
you can reset the shadowBlur property with ctx.shadowBlur = 0; or ctx.shadowBlur = null;
for more info take a look at specs shadowBlur (canvas2dAPI).
here's an alternative way using save() and restore();
fiddle
As others have said doing a reset will work - also you can do .save() which will save the canvas state, you will need to restore as well...
var c=document.getElementById("canvas");
var ctx=c.getContext("2d");
// Left
ctx.save();
ctx.beginPath();
ctx.arc(100, 75, 20, 0, 2 * Math.PI);
ctx.shadowColor = 'blue';
ctx.shadowBlur = 30;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.stroke();
ctx.fill();
ctx.closePath();
ctx.restore();
// Right
ctx.save();
ctx.beginPath();
ctx.arc(200, 75, 20, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
ctx.closePath();
ctx.restore();
This will basically save the state of the canvas and allow you to take actions within it, then add new things to the canvas and keep them as different pieces.
here is a quick fiddle of this being used - https://jsfiddle.net/e0qm94dn/

How can you fill a shape created in javascript with an image?

How can you fill a shape created in javascript with an image?
I am using a shape I created with javascript and am right now filling it with a color. How can I replace that and fill it with an image/gif of my choice?
function shape(x,y) {
ctx.fillStyle = "#9dd4ff";
ctx.beginPath();
ctx.moveTo(232,213)
ctx.lineTo(315,198);
ctx.lineTo(x,y);
ctx.closePath();
ctx.fill();
}
You can solve this problem by using your path as a MASK
HERE IS THE CODE
// Create an image element
var img = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
// Save the state, so we can undo the clipping
ctx.save();
// Create a shape, of some sort
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 30);
ctx.lineTo(180, 10);
ctx.lineTo(200, 60);
ctx.arcTo(180, 70, 120, 0, 10);
ctx.lineTo(200, 180);
ctx.lineTo(100, 150);
ctx.lineTo(70, 180);
ctx.lineTo(20, 130);
ctx.lineTo(50, 70);
ctx.closePath();
// Clip to the current path
ctx.clip();
ctx.drawImage(img, 0, 0);
// Undo the clipping
ctx.restore();
}
// Specify the src to load the image
img.src = "https://www.google.com/images/srpr/logo3w.png";
Try this:
var thumbImg = document.createElement('img');
thumbImg.src = 'path_to_image';
function shape(x,y) {
ctx.fillStyle = "#9dd4ff";
ctx.beginPath();
ctx.moveTo(232,213)
ctx.lineTo(315,198);
ctx.lineTo(x,y);
ctx.drawImage(thumbImg, 0, 0, 50, 50);
ctx.closePath();
ctx.fill();
}
-- Edit --
Remove ctx.fillStyle ="#9dd4ff"
As you need to mention the path of image.

Categories