canvas use more ctx element - javascript

My task is to create two canvases (big and small). In big one create 5 stars of different colors.
Task: by clicking on the star, change the color of the small canvas to the color of the star. Now the problem is that addEventListener only works on the last element. Any ideas?
.html template:
<canvas style="background-color:rgb(255, 255, 255); border: 1px solid black" id='big'>Обновите браузер</canvas>
<canvas style="background-color:rgb(255, 255, 255); border: 1px solid black" id='small'>Обновите браузер</canvas>
.js script:
const big = document.getElementById("big");
const small = document.getElementById("small");
big.height = 600;
big.width = 600;
small.height = 600;
small.width = 50;
function createStar(moveToX, moveToY, lineToX1, lineToY1, lineToX2, lineToY2, lineToX3, lineToY3, lineToX4, lineToY4, color) {
ctx = big.getContext('2d');
ctx.beginPath();
ctx.moveTo(moveToX, moveToY);
ctx.lineTo(lineToX1, lineToY1);
ctx.lineTo(lineToX2, lineToY2);
ctx.lineTo(lineToX3, lineToY3);
ctx.lineTo(lineToX4, lineToY4);
ctx.closePath();
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.fill();
ctx.stroke();
};
const red = new createStar(20, 60, 100, 60, 35, 110, 60, 25, 85, 110, 'red');
const blue = new createStar(120, 60, 200, 60, 135, 110, 160, 25, 185, 110, 'blue');
const green = new createStar(120, 160, 200, 160, 135, 210, 160, 125, 185, 210, 'green');
const black = new createStar(220, 460, 400, 460, 235, 560, 300, 400, 385, 560, 'black');
const yellow = new createStar(220, 260, 300, 260, 235, 310, 260, 225, 285, 310, 'yellow');
big.addEventListener('click', function(e){
if(ctx.isPointInPath(e.offsetX, e.offsetY)) {
small.style.backgroundColor = 'red';
} else {
small.style.backgroundColor = 'white';
}
});

context.isPointInPath(x, y) will check if the point at coords x and y is inside the current sub-path that's being traced in the context.
Every time you call ctx.beginPath(), this sub-path is cleared.
To workaround that you would have to retrace the whole list of stars and check one by one if the point falls in there.
However, there is also a Path2D interface that you can use.
This interface allows to hold such sub-path as their own object, and can be used by the context instead of the internal context's path by e.g ctx.fill(path), ctx.stroke(path), ctx.clip(path), ctx.scrollPathIntoView(path), ctx.drawFocusIfNeeded(path, element), and you probably guessed it, ctx.isPointInPath(path, x, y), or ctx.isPointInStroke(path, x, y).
So you can rewrite your code so that each star is stored in its own Path2D instance, store all these instances in an Array and then traverse that Array to check if any of these was hit by the point:
const stars = [];
const big = document.getElementById("big");
const small = document.getElementById("small");
big.height = 600;
big.width = 500;
small.height = 600;
small.width = 50;
const ctx = big.getContext("2d");
function createStar(moveToX, moveToY, lineToX1, lineToY1, lineToX2, lineToY2, lineToX3, lineToY3, lineToX4, lineToY4, color) {
const path = new Path2D();
// store our path in an object also hodling the color
stars.push({path, color});
path.moveTo(moveToX, moveToY);
path.lineTo(lineToX1, lineToY1);
path.lineTo(lineToX2, lineToY2);
path.lineTo(lineToX3, lineToY3);
path.lineTo(lineToX4, lineToY4);
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.fill(path);
ctx.stroke(path);
};
const red = new createStar(20, 60, 100, 60, 35, 110, 60, 25, 85, 110, 'red');
const blue = new createStar(120, 60, 200, 60, 135, 110, 160, 25, 185, 110, 'blue');
const green = new createStar(120, 160, 200, 160, 135, 210, 160, 125, 185, 210, 'green');
const black = new createStar(220, 460, 400, 460, 235, 560, 300, 400, 385, 560, 'black');
const yellow = new createStar(220, 260, 300, 260, 235, 310, 260, 225, 285, 310, 'yellow');
big.addEventListener('click', function(e) {
// find the first star that got clicked
const touched = stars.find((star) =>
ctx.isPointInPath(star.path, e.offsetX, e.offsetY)
);
if (touched) {
small.style.backgroundColor = touched.color;
} else {
small.style.backgroundColor = 'white';
}
});
canvas {
background-color: rgb(255, 255, 255);
border: 1px solid black;
}
<canvas id='big'>Обновите браузер</canvas>
<canvas id='small'>Обновите браузер</canvas>

Related

fillText method is not working

I am trying to write a text on a canvas. Following is the code :
HTML
<canvas id="scaleChart" width="1500" height="500"></canvas>
JS
var scaleCtx = document.getElementById("scaleChart").getContext("2d");
scaleCtx.fillStyle = 'rgba(40, 220, 140, 1)';
scaleCtx.fillRect(0, 0, 800, 200);
scaleCtx.fillStyle = 'Black';
scaleCtx.font = '20px Calibri';
scaleCtx.fillText = ('00:00', 100, 50);
But no text is appearing. What wrong am I doing? Here is the Fiddle
Simple typo. Last line of JS should not have an equal sign in it.
var scaleCtx = document.getElementById("scaleChart").getContext("2d");
scaleCtx.fillStyle = 'rgba(40, 220, 140, 1)';
scaleCtx.fillRect(0, 0, 800, 200);
scaleCtx.fillStyle = 'Black';
scaleCtx.font = '20px Calibri';
scaleCtx.fillText('00:00', 100, 50);

Sorting objects on canvas

I draw two bottons using a rectangle with text over top.
As you can see I get two different results using the same loop.
The first "button" has the text hidden behind the box.
The second has the text written on top.
Why is this? How does sorting work in canvas?
<body>
<canvas id="canvas" width="320" height="512"
style="position: absolute; left: 500px; top: 50px; z-index: 1;"></canvas>
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext("2d");
canvas.style.backgroundColor = 'rgba(0, 0, 0, 0)';
context.clearRect(0, 0, 320, 16);
gameMenu();
function gameMenu(){
var buttons = [ {x: 210, y: 420, w: 80, h: 30, s: "Messages"},
{x: 210, y: 470, w: 80, h: 30, s: "Pause"} ], i = 0, r;
while(r = buttons[i++]) {
context.rect(r.x, r.y, r.w, r.h);
context.fillStyle = "rgb(26,26,26)";
context.fill();
context.fillStyle = 'White';
context.font = "16px Tahoma";
context.fillText(r.s, r.x + 18, r.y + 22);
}
}
</script>
</body>
Here is a JS Fiddle:
https://jsfiddle.net/oa84Lsxn/1/
You must begin each new path operation (==each new .rect) with context.beginPath. Otherwise all previous .rects will be redrawn along with the current .rect.
Your issue is that all previous paths are redrawn along with the new path. This means your first rect is being redrawn along with your new second rect -- causing the first rect's text to be overwritten by the first rect.
Here's a working version your code with context.beginPath added.
var canvas=document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.style.backgroundColor = 'rgba(0, 0, 0, 0)';
context.clearRect(0, 0, 320, 16);
gameMenu();
function gameMenu(){
// x,y changed to fit demo on StackSnipped window
var buttons = [ {x: 40, y: 20, w: 80, h: 30, s: "Messages"},
{x: 40, y: 70, w: 80, h: 30, s: "Pause"} ],
i = 0, r;
while(r = buttons[i++]) {
context.beginPath();
context.rect(r.x, r.y, r.w, r.h);
context.fillStyle = "rgb(26,26,26)";
context.fill();
context.fillStyle = 'White';
context.font = "16px Tahoma";
context.fillText(r.s, r.x + 18, r.y + 22);
}
}
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>

fabric.js limit rotation to x degrees with rotation handle

I am using fabric.js and trying to allow rotation for any object on my canvas, not freely 360 degrees, but only 15 degrees at a time, using the rotation handle. I searched really hard but couldn't find an answer so far. Is this possible?
Shorter solution:
canvas.on('object:rotating', function(options) {
var step = 15;
options.target.angle = Math.round(options.target.angle / step) * step;
});
UPD: since 1.6.7 you can just use fabric.Object.snapAngle property:
someFabricObject.snapAngle = 15;
You can use canvas.on("object:rotating") to interfer with rotation of all objects.
I defined a set of valid angles, and then on the rotation event i checked which one to use.
var angles = [0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 315, 330, 345, 360];
canvas.on("object:rotating", function(rotEvtData) {
var targetObj = rotEvtData.target;
var angle = targetObj.angle % 360;
for (var i=0; i < angles.length; i++) {
if (angle <= angles[i]) {
targetObj.angle = angles[i];
break;
}
}
});
var canvas = new fabric.Canvas("c");
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 90,
height: 90
});
canvas.add(rect);
var angles = [0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 315, 330, 345, 360];
canvas.on("object:rotating", function(rotEvtData) {
var targetObj = rotEvtData.target;
var angle = targetObj.angle % 360;
for (var i=0; i < angles.length; i++) {
if (angle <= angles[i]) {
targetObj.angle = angles[i];
break;
}
}
});
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>

Canvas Bar Graph Animation

I'm trying to get up to speed on the canvas tag and I'm getting stuck when it comes to basic animations. I've searched around and I can get most of the tutorials I've found working, but I've had some trouble translating that into what I'm trying to do.
I have a bar graph that I'm building for my portfolio, and I would like to have the ability to animate the bars on the graph. Basically when the page loads, the bars start from the bottom of the graph, and grow upwards to where they need to be. I have a version I built in jquery found here to give an idea of what I'm after: http://jokedesigns.com/portfoliov6/
Could someone point me in the right direction on how to achieve the animations I'm looking for? Even if its a simple function I should be able to rework it into what I need. Most of the tutorials I've found mainly deal with rotation, I did find one that was a linear animation, but I still wasn't able to quite rework that into what I need.
Here is the code I have so far for the graph:
<html>
<head>
<script type="application/javascript">
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function draw() {
var canvas = document.getElementById("canvas");
var canvasbg = document.getElementById("canvasbg");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
var ctx2 = canvasbg.getContext("2d");
var img = new Image();
img.onload = function(){
ctx2.drawImage(img,0,0);
};
img.src = 'skills_bg.jpg';
ctx.fillStyle = "#0a4888";
ctx.fillRect (0, 82, 34, 50);
ctx.fillStyle = "#ed9323";
ctx.fillRect (60, 82, 34, 50);
ctx.fillStyle = "#87982d";
ctx.fillRect (120, 82, 34, 50);
ctx.fillStyle = "#902e63";
ctx.fillRect (180, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (360, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (420, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (480, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (540, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (600, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (660, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (720, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (780, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (840, 82, 34, 50);
var sources = {
ps: 'icn_ps.png',
ai: 'icn_ai.png',
dw: 'icn_dw.png',
id: 'icn_id.png',
ht: 'icn_html.png',
cs: 'icn_css.png',
js: 'icn_js.png',
sql: 'icn_mysql.png',
php: 'icn_php.png',
perl: 'icn_perl.png',
ruby: 'icn_ruby.png',
cplus: 'icn_cplusplus.png',
asp: 'icn_asp.png'
};
loadImages(sources, function(images) {
ctx.drawImage(images.ps, 0, 132, 36, 37);
ctx.drawImage(images.ai, 60, 132, 36, 37);
ctx.drawImage(images.dw, 120, 132, 36, 37);
ctx.drawImage(images.id, 180, 132, 36, 37);
ctx.drawImage(images.ht, 360, 132, 36, 37);
ctx.drawImage(images.cs, 420, 132, 36, 37);
ctx.drawImage(images.js, 480, 132, 36, 37);
ctx.drawImage(images.sql, 540, 132, 36, 37);
ctx.drawImage(images.php, 600, 132, 36, 37);
ctx.drawImage(images.perl, 660, 132, 36, 37);
ctx.drawImage(images.ruby, 720, 132, 36, 37);
ctx.drawImage(images.cplus, 780, 132, 36, 37);
ctx.drawImage(images.asp, 840, 132, 36, 37);
});
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvasbg" width="960" height="200" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
<canvas id="canvas" width="960" height="200" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
</body>
</html>
You can use requestAnimationFrame to create animation loops to allow your bars to grow
To create uniform growth for bars that are uneven height you can apply a percentage
// this will grow all bars at an even rate
bar1Height = bar1.maxHeight * percentComplete/100;
bar2Height = bar2.maxHeight * percentComplete/100;
percentComplete++;
Here's example code and a Demo: http://jsfiddle.net/m1erickson/YR4D7/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" />
<script src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var skillBars=[];
skillBars.push({max:200,color:"red"});
skillBars.push({max:75,color:"green"});
skillBars.push({max:275,color:"blue"});
var chartBottomY=325;
var chartBarWidth=30;
var chartBarPadding=20;
var percent=0;
animate();
function animate() {
// if not 100% done, request another frame
if(percent++<100){
requestAnimationFrame(animate);
}
// Drawing code goes here
ctx.clearRect(0,0,canvas.width,canvas.height);
var x=chartBarPadding;
for(var i=0;i<skillBars.length;i++){
var height=skillBars[i].max*percent/100;
ctx.fillStyle=skillBars[i].color;
ctx.fillRect(x,chartBottomY,chartBarWidth,-height);
x+=chartBarWidth+chartBarPadding;
}
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=350 height=350></canvas>
</body>
</html>
I see a couple issues.
First, you draw everything at one time, whereas you should draw after each image is loaded, but yiu will want to use setTimeout or something similar to allow the canvas element to be drawn.
You should also have a method for the bar, and have it remember the current step value and it will just draw the next block.
I would put onload on the image tag, image.onload function with return, and when it loaded draw the next bar and load the next one.
But remember to use setTimeout to allow the drawing to take place.
Here are some links that may help
JavaScript animation with multiple setTimeout
https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/AnimatingtheCanvas/AnimatingtheCanvas.html

canvas : shape + shadow

I need to draw a shape then add shadow but shadow is over the filled color I need it to be under it .. I can't explain the situation well so here is an example on jsfiddle
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.beginPath();
context.moveTo(170, 80);
context.bezierCurveTo(130, 100, 130, 150, 230, 150);
context.bezierCurveTo(250, 180, 320, 180, 340, 150);
context.bezierCurveTo(420, 150, 420, 120, 390, 100);
context.bezierCurveTo(430, 40, 370, 30, 340, 50);
context.bezierCurveTo(320, 5, 250, 20, 250, 50);
context.bezierCurveTo(200, 5, 150, 20, 170, 80);
context.closePath();
context.lineWidth = 5;
context.fillStyle = "#8ED6FF";
context.strokeStyle = "#0000ff";
context.shadowColor = "#000000";
context.shadowBlur = 2;
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.fill();
context.stroke();
http://jsfiddle.net/j8u8p/ thx
http://jsfiddle.net/j8u8p/11/
Note: All I did was rearrange the context calls and add in a globalCompositeOperation
p.s. this looks nicer: http://jsfiddle.net/j8u8p/13/
p.p.s this is tweaked because you moaned about the gap: http://jsfiddle.net/j8u8p/16/

Categories