HTML5 canvas stroke with consistent alpha values - javascript

I am developing a image masking tool that helps the user mark a certain regions on an underlying image. I'd like the mask to have a consistent alpha-value. The masking tool is to be implemented in HTML5 canvas.
The issue I'm facing is that when I create multiple over-lapping strokes, their alpha values stack, making it less-transparent in the overlaps. Whereas I'd like a consistent alpha value regardless of overlapping strokes, as the user might need multiple strokes to fully mask a region.
Here is the attached fiddle:
http://jsfiddle.net/o5x70fbd/
Let me know if this is a known solution

I don't know if this is the solution you are looking for. My idea is to use 2 canvases. In the first canvas the color of the stroke is opaque. This canvas is hidden. I'm using display:nonebut you can let it unattached to the DOM.
Then you copy the first canvas as an image in a second one with ctx2.globalAlpha = .5;. this will give you a a consistent alpha value.
The changes I've made to your code: I'm putting the points in arrays and I'm drawing using the points:
var canvas = document.getElementById("myCanvas");
var canvas2 = document.getElementById("_2");
var ctx = canvas.getContext("2d");
var ctx2 = _2.getContext("2d");
var drawing = false;
let points = [];
var painting = document.getElementById("paint");
var paint_style = getComputedStyle(painting);
canvas.width = canvas2.width = parseInt(paint_style.getPropertyValue("width"));
canvas.height = canvas2.height = parseInt(
paint_style.getPropertyValue("height")
);
var mouse = {
x: 0,
y: 0
};
let count = -1;
ctx.lineWidth = 30;
ctx.lineJoin = "round";
ctx.lineCap = "round";
_2.addEventListener(
"mousemove",
function(e) {
if (drawing) {
mouse.x = e.pageX - this.offsetLeft;
mouse.y = e.pageY - this.offsetTop;
points[count].push({ x: mouse.x, y: mouse.y });
onPaint();
}
},
false
);
_2.addEventListener(
"mousedown",
function(e) {
drawing = true;
count++;
mouse.x = e.pageX - this.offsetLeft;
mouse.y = e.pageY - this.offsetTop;
let ry = [];
ry.push({ x: mouse.x, y: mouse.y });
points.push(ry);
},
false
);
_2.addEventListener(
"mouseup",
function() {
drawing = false;
onPaint();
},
false
);
function onPaint() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
points.forEach(ry => {
ctx.beginPath();
ctx.moveTo(ry[0].x, ry[0].y);
ry.forEach(p => {
ctx.lineTo(p.x, p.y);
});
ctx.strokeStyle = "#00CC99";
ctx.stroke();
});
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
ctx2.globalAlpha = 0.5;
ctx2.drawImage(canvas, 0, 0);
}
body {
margin: 0px;
padding: 0px;
}
#paint {
width: 98%;
height: 550px;
border: 5px solid red;
}
#myCanvas{display:none;}
#_2{background:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/castell.jpg)}
<div id="paint">
<canvas id="myCanvas"></canvas>
<canvas id="_2"></canvas>
</div>

Related

Adjust canvas background in Javascript

I'm trying to drawn a rect on canvas, but I want that canvas has lightly transparent background, but that drawn rect has no background.
What I will is something as follows:
I have code as follows:
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
function init() {
img.addEventListener('load', function(){
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas(){
if(update){
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
function drawRect(){
ctx.setTransform(1,0,0,1,rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.beginPath();
ctx.rect(-rect.w/2, -rect.h/2, rect.w, rect.h);
/* ctx.fill(); */
ctx.stroke();
}
// clears canvas sets filters and draws rectangles
function drawCanvas(){
// restore the default transform as rectangle rendering does not restore the transform.
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX : e.offsetX,
startY : e.offsetY,
w : 1,
h : 1,
rotate : 0,
};
drag = true;
}
function mouseUp() { drag = false; buttons_shown = true; update = true; }
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
update = true;
}
}
init();
.hide{
display: none !important;
}
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display:inline-block;
background:rgba(0,0,0,0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg"/>
<canvas id="canvas"></canvas>
</div>
<div id="buttons" class="hide"></div>
In my example I set the background of the canvas to what I will but I cannot remove that background for the drawn rect, it has the same color as the canvas.
Here is the fiddle.
Any idea how to solve it?
This could be achieved in several ways (compositing, clip-path...) but the easiest for such a simple path is probably to use the "evenodd" fill-rule parameter of fill() method which will allow us to draw this rectangle with a hole.
The process is simply to draw a first rect the size of the canvas, then, in the same path declaration, draw your own smaller rectangle. The fill-rule will then exclude this smaller inner rectangle from the bigger one.
function drawRect() {
ctx.beginPath(); // a single path
// the big rectangle, covering the whole canvas
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
// your smaller, inner rectangle
ctx.setTransform(1, 0, 0, 1, rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.rect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
// set the fill-rule to evenodd
ctx.fill('evenodd');
// stroke
// start a new Path declaration
ctx.beginPath
// redraw only the small rect
ctx.rect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
ctx.stroke();
}
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
function init() {
img.addEventListener('load', function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
// set our context's styles here
ctx.fillStyle = 'rgba(0,0,0,.5)';
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas() {
if (update) {
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
// clears canvas sets filters and draws rectangles
function drawCanvas() {
// restore the default transform as rectangle rendering does not restore the transform.
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX: e.offsetX,
startY: e.offsetY,
w: 1,
h: 1,
rotate: 0,
};
drag = true;
}
function mouseUp() {
drag = false;
buttons_shown = true;
update = true;
}
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
update = true;
}
}
init();
.hide {
display: none !important;
}
canvas {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: inline-block;
background: rgba(0, 0, 0, 0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg" />
<canvas id="canvas"></canvas>
</div>
<div id="buttons" class="hide"></div>
Try filling your rect before calling ctx.stroke(), like this:
ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
ctx.fill();
This will produce similar effect to what you have shown in your question. Now the inside of rectangle has both css and fillStyle effect, so it's not ideal - for even better effect you would have to fill outside of rect with desired style instead of setting background in css.
first, draw a full canvas with semi-transparent background, like
ctx.fillStyle = 'rgba(32, 32, 32, 0.7)';
ctx.fillRect(0, 0, width, height);
Then just clear your rectangular form this canvas
ctx.clearRect(x, y, mini_width, mini_height);

Javascript: Navigatable Viewport that can also be interacted with

I am in the process of learning javascript for game design, and wanted to make a separate achievement page that the user can navigate to that will allow them to check their achievements on various games. (at the moment I am not concerned with implementing localstorage/cookies etc, I just want to work on the page for now)
So the requirements of my base idea is as follows:
Able to drag around the viewport/page to view all the achievement categories as they will likely not all be in view on smaller screens
Able to click on a category to open a small box containing all achievements belonging to that game/category
Able to mouse over all achievements in the boxes to get text descriptions of what they are
OPTIONAL: have lines connecting each box on the "overworld" to show users where nearby boxes are if they are off screen
At first, I thought I would need canvas to be able to do this. I learned a bit about it and got decently far until I realized that canvas has a lot of restrictions like not being able to do mouseover events unless manually implementing each one. Here is the current progress I was at in doing a test-run of learning canvas, but it's not very far:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function resize()
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(panning.offset.x, panning.offset.y);
draw();
}
window.addEventListener("resize", resize);
var global = {
scale : 1,
offset : {
x : 0,
y : 0,
},
};
var panning = {
start : {
x : null,
y : null,
},
offset : {
x : 0,
y : 0,
},
};
var canvasCenterWidth = (canvas.width / 2);
var canvasCenterHeight = (canvas.height / 2);
function draw() {
ctx.beginPath();
ctx.rect(canvasCenterWidth, canvasCenterHeight, 100, 100);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.beginPath();
ctx.arc(350, 250, 50, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
}
draw();
canvas.addEventListener("mousedown", startPan);
function pan() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(panning.offset.x, panning.offset.y);
draw();
}
function startPan(e) {
window.addEventListener("mousemove", trackMouse);
window.addEventListener("mousemove", pan);
window.addEventListener("mouseup", endPan);
panning.start.x = e.clientX;
panning.start.y = e.clientY;
}
function endPan(e) {
window.removeEventListener("mousemove", trackMouse);
window.removeEventListener("mousemove", pan);
window.removeEventListener("mouseup", endPan);
panning.start.x = null;
panning.start.y = null;
global.offset.x = panning.offset.x;
global.offset.y = panning.offset.y;
}
function trackMouse(e) {
var offsetX = e.clientX - panning.start.x;
var offsetY = e.clientY - panning.start.y;
panning.offset.x = global.offset.x + offsetX;
panning.offset.y = global.offset.y + offsetY;
}
body{
overflow: hidden;
}
canvas {
top: 0;
left: 0;
position: absolute; }
<canvas id="canvas"></canvas>
So I guess my question is now: what is the best way to implement this? Is it feasable to do it with canvas, or should I just scrap that and try to figure out something with div movement? Should I be concerned with performance issues and should that affect how I implement it?

Animation stops after mousemove (canvas)

I created a script that makes a canvas circle follow the mouse when is X is bigger.However as you can see it only works during the mouse move. when the mouse stops I couldn't find a way to make the circle move. Plus, did i use the correct logic for making this code?
Heres a snippet:
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
var PI = Math.PI;
window.requestAnimFrame = (function(){
return window.requestAnimationFrame || // La forme standardisée
window.webkitRequestAnimationFrame || // Pour Chrome et Safari
window.mozRequestAnimationFrame || // Pour Firefox
window.oRequestAnimationFrame || // Pour Opera
window.msRequestAnimationFrame || // Pour Internet Explorer
function(callback){ // Pour les élèves du dernier rang
window.setTimeout(callback, 1000 / 60);
};
})();
function pos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
function elm_fixe() {
ctx.fillStyle = "rgba(050, 155, 255, 1)";
ctx.fillRect(0, 0, 30, 30, 1);
for (var x = 0, y = 0, alpha = 1; alpha >= 0; alpha -= 0.1, x += 40) {
ctx.fillStyle = "rgba(050, 155, 255, " + alpha + ")";
ctx.fillRect(x, 0, 30, 30);
}
}
function cercle(x, y) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(x, y, 30, 0, PI * 2, true);
ctx.fill();
}
var x = 250,
y = 250;
function bouger(e) {
console.log(e.clientX)
if ( pos(canvas, e).x > x) {
x += 1;
};
}
function draw(e) {
ctx.clearRect(0, 0, 800, 500);
bouger(e);
cercle(x, y);
elm_fixe();
}
/* window.requestAnimFrame(function() {
canvas.addEventListener('mousemove', function(e) {
window.requestAnimFrame(function() { draw(e) });
});
}
);
*/
window.addEventListener('mousemove', function(e) {
draw(e);
});
<canvas height="500" width="800" id="canvas"></canvas>
First of all, when experimenting with this it's important to focus on getting the task at hand done, so I removed your fixed drawn elements, as that's easy.
The main issue you are having is that you only update onmousemove, which could get in your way. The best thing to do is simply to store the mouse coordinates in a separate object, here I have done it as such:
var mouse = {x: 0, y: 0};
After that, simply update the coordinates when the mousemove event fires. Now we remember the position, which means in the future you could actually animate this circle from point to point as it does not rely on the event to actually know the values.
A polyfill for requestAnimationFrame is actually no longer necessary, almost every browser supports it except some older ones.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var mouse = {x: 0, y: 0};
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function circle() {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, 30, 0, Math.PI * 2, true);
ctx.fill();
}
function draw(e) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
circle();
window.requestAnimationFrame(draw);
}
document.addEventListener('mousemove', function(e) {
mouse.x = e.pageX > mouse.x ? e.pageX : mouse.x;
mouse.y = e.pageY > mouse.y ? e.pageY : mouse.y;
});
window.requestAnimationFrame(draw);
html, body { height: 100%; }
body { overflow: hidden; padding: 0; margin: 0;}
<canvas id="canvas"></canvas>
It might be better to rename some functions, just for future obviousness. I have also learned the hard way, a long time ago, that you should keep your function names in English, mostly because programming happens to be based on English. This way every user on this site can decipher what a function might do, and future developers will be able to debug your code without knowing french. For example, I would rename circle to something like drawCircleAtMousePosition - its a mouthful, but nobody can confuse what this function does.
Another advantage of using a stored variable is that you can do your pos (which is a really bad name for a function - maybe localiseCoordinatesTo(canvas)) right in the onmousemove event, so you never have to think about this at a later point.
Update: Animating
Here is an implementation that uses a very simple linear interpolation to animate the circle from place to place:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// We will need a from value, a to value, and store a time;
var mouse = {from: {x: 0, y: 0}, to: {x: 0, y: 0}, time: Date.now()};
// As well as a duration
var duration = 1000;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function position(){
// This will calculate the position
var time = Date.now(), progress;
if(time > mouse.time + duration)
return mouse.to;
else
progress = (time - mouse.time) / duration;
return {
x: mouse.from.x + (mouse.to.x - mouse.from.x) * progress,
y: mouse.from.y + (mouse.to.y - mouse.from.y) * progress
}
}
function circle() {
ctx.fillStyle = "red";
ctx.beginPath();
var pos = position();
ctx.arc(pos.x, pos.y, 30, 0, Math.PI * 2, true);
ctx.fill();
}
function draw(e) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
circle();
window.requestAnimationFrame(draw);
}
document.addEventListener('mousemove', function(e) {
// Update FROM to the current position
mouse.from = position();
// Reassign the to values
mouse.to.x = e.pageX > mouse.to.x ? e.pageX : mouse.to.x;
mouse.to.y = e.pageY > mouse.to.y ? e.pageY : mouse.to.y;
// Update the animation start time.
mouse.time = Date.now();
});
window.requestAnimationFrame(draw);
html, body { height: 100%; }
body { overflow: hidden; padding: 0; margin: 0;}
<canvas id="canvas"></canvas>

Canvas draw following the path

I want to do the following on a HTML5 canvas / or SVG:
Have a background path, move cursor over and draw (fill) the background path
After the user complete drawing have a callback function
My problem is that I dont have any idea how to check if the drawed line is following the path.
Can someone explain me how to do this or maybe give some tips?
http://jsbin.com/reguyuxawo/edit?html,js,console,output
function drawBgPath() {
context.beginPath();
context.moveTo(100, 20);
context.lineTo(200, 160);
context.quadraticCurveTo(230, 200, 250, 120);
context.bezierCurveTo(290, -40, 300, 200, 400, 150);
context.lineTo(500, 90);
context.lineWidth = 5;
context.strokeStyle = 'rgba(0,0,0,.2)';
context.stroke();
}
Create a hidden canvas that stores the origin path as question Canvas, lets say, as #q.
Draw the question on the #c.
When user about to draw, get the pixel value from question to see whether its on a line or not.
Decide the draw color by the info above.
var mousePressed = false;
var lastX, lastY;
var ctx;
var canvas = document.getElementById('c');
var context = canvas.getContext('2d');
var canvasq = document.getElementById('q');
var contextq = canvasq.getContext('2d');
canvas.width = 500;
canvas.height = 500;
canvasq.width = 500;
canvasq.height = 500;
$('#c').mousedown(function (e) {
mousePressed = true;
Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, false);
});
$('#c').mousemove(function (e) {
if (mousePressed) {
Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, true);
}
});
$('#c').mouseup(function (e) {
mousePressed = false;
});
$('#c').mouseleave(function (e) {
mousePressed = false;
});
function drawBgPath() {
contextq.beginPath();
contextq.moveTo(100, 20);
contextq.lineTo(200, 160);
contextq.quadraticCurveTo(230, 200, 250, 120);
contextq.bezierCurveTo(290, -40, 300, 200, 400, 150);
contextq.lineTo(500, 90);
contextq.lineWidth = 5;
contextq.strokeStyle = 'rgba(0,0,0,.2)';
contextq.stroke();
context.drawImage(canvasq, 0, 0);
}
function Draw(x, y, isDown) {
// If not integer, getImageData will get a 2x2 region.
x = Math.round(x);
y = Math.round(y);
if (isDown) {
var pixel = contextq.getImageData(x, y, 1, 1);
// If the canvas is not draw by line, the opacity value will be 0.
var color = (pixel.data[3] === 0) ? 'red' : 'purple';
context.beginPath();
context.strokeStyle = color;
context.lineWidth = 5;
context.lineJoin = "round";
context.moveTo(lastX, lastY);
context.lineTo(x, y);
context.closePath();
context.stroke();
}
lastX = x; lastY = y;
}
drawBgPath();
Draw();
#q {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<canvas id="c"></canvas>
<canvas id="q"></canvas>
Another way is:
Create 2 additional canvas, for answer and question.
When mouse down, draw the path on answer first.
Then compare answer canvas with question canvas.
Draw the compared answer on the canvas for show.
I'll just demo how it can be achieve here. You can clip the draw region to improve the performance.
It's somehow hard to decide whether the path is complete or not. But you can still:
Clip the answer image by question, then compare their pixel value one-by-one.
If pixel on question has color, total + 1, if both pixel have color and color is same, count + 1.
Check if count/total is over a specific threshold.
It may be slow if the image is large, so I'd prefer to only check it when user mouseup or click a check button.
I've also tried to use .toDataURL to compare their value by string, however, its too strict and can't let you have a threshold.
var mousePressed = false;
var lastX, lastY;
var ctx;
// Question part
var qCanvas = document.createElement('canvas');
var qContext = qCanvas.getContext('2d');
var aCanvas = document.createElement('canvas');
var aContext = aCanvas.getContext('2d');
var canvas = document.getElementById('c');
var context = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
qCanvas.width = 500;
qCanvas.height = 500;
aCanvas.width = 500;
aCanvas.height = 500;
$('#c').mousedown(function (e) {
mousePressed = true;
Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, false);
});
$('#c').mousemove(function (e) {
if (mousePressed) {
Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, true);
}
});
$('#c').mouseup(function (e) {
mousePressed = false;
});
$('#c').mouseleave(function (e) {
mousePressed = false;
});
function drawBgPath() {
qContext.beginPath();
qContext.moveTo(100, 20);
qContext.lineTo(200, 160);
qContext.quadraticCurveTo(230, 200, 250, 120);
qContext.bezierCurveTo(290, -40, 300, 200, 400, 150);
qContext.lineTo(500, 90);
qContext.lineWidth = 5;
qContext.strokeStyle = 'rgb(0,0,0)';
qContext.stroke();
// Draw Question on canvas
context.save();
context.globalAlpha = 0.2;
context.drawImage(qCanvas, 0, 0);
context.restore();
// Now fill the question with purple.
qContext.fillStyle = 'purple';
qContext.globalCompositeOperation = 'source-atop';
qContext.fillRect(0, 0, qCanvas.width, qCanvas.height);
}
function Draw(x, y, isDown) {
if (isDown) {
// First draw on answer canvas
aContext.beginPath();
aContext.strokeStyle = 'red';
console.log(x, y);
aContext.lineWidth = 5;
aContext.lineJoin = "round";
aContext.moveTo(lastX, lastY);
aContext.lineTo(x, y);
aContext.closePath();
aContext.stroke();
// Compare answer with question.
aContext.save();
aContext.globalCompositeOperation = 'source-atop';
aContext.drawImage(qCanvas, 0, 0);
aContext.restore();
// Draw the result on what you want to show.
context.drawImage(aCanvas, 0, 0);
}
lastX = x; lastY = y;
}
var cv = document.createElement('canvas');
cv.width = 500;
cv.height = 500;
//document.body.appendChild(cv);
var ctx = cv.getContext('2d');
function checkAnswer() {
cv.width = 500;
cv.height = 500;
ctx.globalCompositeOperation = 'source-over';
ctx.drawImage(aCanvas, 0, 0);
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(qCanvas, 0, 0);
var qData = qContext.getImageData(0, 0, 500, 500).data;
var aData = ctx.getImageData(0, 0, 500, 500).data;
var idx = 0, i, j;
var count = 0, total = 0;
for (i = 0; i < 500; ++i) {
for (j = 0; j < 500; ++j) {
if (qData[idx] !== 0) {
++total;
if (aData[idx] === qData[idx]) {
++count;
}
}
idx += 4;
}
}
console.log(count,total);
// Threshold.
if (count/total > 0.95) {
alert('Complete');
}
}
drawBgPath();
Draw();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<canvas id="c"></canvas>
<button onclick="checkAnswer()">check</button>

Update HTML5 canvas rectangle on hover?

I've got some code which draws a rectangle on a canvas, but I want that rectangle to change color when I hover the mouse over it.
The problem is after I've drawn the rectangle I'm not sure how I select it again to make the adjustment.
What I want to do:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.rect(20,20,150,100);
ctx.stroke();
$('c.[rectangle]').hover(function(this){
this.fillStyle = 'red';
this.fill();
});
You can't do this out-of-the-box with canvas. Canvas is just a bitmap, so the hover logic has to be implemented manually.
Here is how:
Store all the rectangles you want as simple object
For each mouse move on the canvas element:
Get mouse position
Iterate through the list of objects
use isPointInPath() to detect a "hover"
Redraw both states
Example
var canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
rects = [
{x: 10, y: 10, w: 200, h: 50},
{x: 50, y: 70, w: 150, h: 30} // etc.
], i = 0, r;
// render initial rects.
while(r = rects[i++]) ctx.rect(r.x, r.y, r.w, r.h);
ctx.fillStyle = "blue"; ctx.fill();
canvas.onmousemove = function(e) {
// important: correct mouse position:
var rect = this.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i = 0, r;
ctx.clearRect(0, 0, canvas.width, canvas.height); // for demo
while(r = rects[i++]) {
// add a single rect to path:
ctx.beginPath();
ctx.rect(r.x, r.y, r.w, r.h);
// check if we hover it, fill red, if not fill it blue
ctx.fillStyle = ctx.isPointInPath(x, y) ? "red" : "blue";
ctx.fill();
}
};
<canvas/>
This is a stable code in base of #K3N answer. The basic problem of his code is because when one box is over the another the two may get mouse hover at same time. My answer perfectly solves that adding a 'DESC' to 'ASC' loop.
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
var map = [
{x: 20, y: 20, w: 60, h: 60},
{x: 30, y: 50, w: 76, h: 60}
];
var hover = false, id;
var _i, _b;
function renderMap() {
for(_i = 0; _b = map[_i]; _i ++) {
ctx.fillStyle = (hover && id === _i) ? "red" : "blue";
ctx.fillRect(_b.x, _b.y, _b.w, _b.h);
}
}
// Render everything
renderMap();
canvas.onmousemove = function(e) {
// Get the current mouse position
var r = canvas.getBoundingClientRect(),
x = e.clientX - r.left, y = e.clientY - r.top;
hover = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = map.length - 1, b; b = map[i]; i--) {
if(x >= b.x && x <= b.x + b.w &&
y >= b.y && y <= b.y + b.h) {
// The mouse honestly hits the rect
hover = true;
id = i;
break;
}
}
// Draw the rectangles by Z (ASC)
renderMap();
}
<canvas id="canvas"></canvas>
You may have to track the mouse on the canvas using JavaScript and see when it is over your rectangle and change the color then. See code below from my blog post
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="700" height="500" style="border:1px solid #c3c3c3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
var myRect={x:150, y:75, w:50, h:50, color:"red"};
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = myRect.color;
ctx.fillRect(myRect.x, myRect.y, myRect.w, myRect.h);
c.addEventListener("mousemove", function(e){
if ((e.clientX>=myRect.x)&(e.clientX<=myRect.x+myRect.w)&(e.clientY>=myRect.y)&(e.clientY<=myRect.y+myRect.h)){
myRect.color = "green";}
else{
myRect.color = "red";}
updateCanvas();
}, false);
function updateCanvas(){
ctx.fillStyle = myRect.color;
ctx.fillRect(myRect.x, myRect.y, myRect.w, myRect.h);
}
</script>
</body>
</html>
I believe this is a slightly more in-depth answer that would work better for you, especially if you are interested in game design with the canvas element.
The main reason this would work better for you is because it focuses more on an OOP (object orientated programming) approach. This allows for objects to be defined, tracked and altered at a later time via some event or circumstance. It also allows for easy scaling of your code and in my opinion is just more readable and organized.
Essentially what you have here is two shapes colliding. The cursor and the individual point / object it hovers over. With basic squares, rectangles or circles this isn't too bad. But, if you are comparing two more unique shapes, you'll need to read up more on Separating Axis Theorem (SAT) and other collision techniques. At that point optimizing and performance will become a concern, but for now I think this is the optimal approach.
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;
const cx = width / 2;
const cy = height / 2;
const twoPie = Math.PI * 2;
const points = []; // This will be the array we store our hover points in later
class Point {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r || 0;
}
}
class HoverPoint extends Point {
constructor(x, y, r, color, hoverColor) {
super(x, y, r);
this.color = color;
this.hoverColor = hoverColor;
this.hovered = false;
this.path = new Path2D();
}
draw() {
this.hovered ? ctx.fillStyle = this.hoverColor : ctx.fillStyle = this.color;
this.path.arc(this.x, this.y, this.r, 0, twoPie);
ctx.fill(this.path);
}
}
class Cursor extends Point {
constructor(x, y, r) {
super(x, y, r);
}
collisionCheck(points) {
// This is the method that will be called during the animate function that
// will check the cursors position against each of our objects in the points array.
document.body.style.cursor = "default";
points.forEach(point => {
point.hovered = false;
if (ctx.isPointInPath(point.path, this.x, this.y)) {
document.body.style.cursor = "pointer";
point.hovered = true;
}
});
}
}
function createPoints() {
// Create your points and add them to the points array.
points.push(new HoverPoint(cx, cy, 100, 'red', 'coral'));
points.push(new HoverPoint(cx + 250, cy - 100, 50, 'teal', 'skyBlue'));
// ....
}
function update() {
ctx.clearRect(0, 0, width, height);
points.forEach(point => point.draw());
}
function animate(e) {
const cursor = new Cursor(e.offsetX, e.offsetY);
update();
cursor.collisionCheck(points);
}
createPoints();
update();
canvas.onmousemove = animate;
There is one more thing that I would like to suggest. I haven't done tests on this yet but I suspect that using some simple trigonometry to detect if our circular objects collide would preform better over the ctx.IsPointInPath() method.
However if you are using more complex paths and shapes, then the ctx.IsPointInPath() method would most likely be the way to go. if not some other more extensive form of collision detection as I mentioned earlier.
The resulting change would look like this...
class Cursor extends Point {
constructor(x, y, r) {
super(x, y, r);
}
collisionCheck(points) {
document.body.style.cursor = "default";
points.forEach(point => {
let dx = point.x - this.x;
let dy = point.y - this.y;
let distance = Math.hypot(dx, dy);
let dr = point.r + this.r;
point.hovered = false;
// If the distance between the two objects is less then their combined radius
// then they must be touching.
if (distance < dr) {
document.body.style.cursor = "pointer";
point.hovered = true;
}
});
}
}
here is a link containing examples an other links related to collision detection
I hope you can see how easily something like this can be modified and used in games and whatever else. Hope this helps.
Below code adds shadow to canvas circle on hovering it.
<html>
<body>
<canvas id="myCanvas" width="1000" height="500" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
</body>
<script>
var canvas = document.getElementById("myCanvas"),
ctx = canvas.getContext("2d"),
circle = [{
x: 60,
y: 50,
r: 40,
},
{
x: 100,
y: 150,
r: 50,
} // etc.
];
// render initial rects.
for (var i = 0; i < circle.length; i++) {
ctx.beginPath();
ctx.arc(circle[i].x, circle[i].y, circle[i].r, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
}
canvas.onmousemove = function(e) {
var x = e.pageX,
y = e.pageY,
i = 0,
r;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < circle.length; i++) {
if ((x > circle[i].x - circle[i].r) && (y > circle[i].y - circle[i].r) && (x < circle[i].x + circle[i].r) && (y < circle[i].y + circle[i].r)) {
ctx.beginPath();
ctx.arc(circle[i].x, circle[i].y, circle[i].r, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
ctx.shadowBlur = 10;
ctx.lineWidth = 3;
ctx.strokeStyle = 'rgb(255,255,255)';
ctx.shadowColor = 'grey';
ctx.stroke();
ctx.shadowColor = 'white';
ctx.shadowBlur = 0;
} else {
ctx.beginPath();
ctx.arc(circle[i].x, circle[i].y, circle[i].r, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
ctx.shadowColor = 'white';
ctx.shadowBlur = 0;
}
}
};
</script>
</html>
I know this is old, but I am surprised no one has mentioned JCanvas. It adds to the simplicity of animating canvas on events. More documentation here https://projects.calebevans.me/jcanvas/docs/mouseEvents/
<html lang="en">
<head>
<!-- css and other -->
</head>
<body onload="draw();">
<canvas id = "canvas" width="500" height="500" style= border:1px solid #000000;"> </canvas>
<script>
function draw() {
$('canvas').drawRect({
layer: true,
fillStyle:'#333',
x:100, y: 200,
width: 600,
height: 400,
mouseover: function(layer) {
$(this).animateLayer(layer, {
fillStyle: 'green'
}, 1000, 'swing');
}
});
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jcanvas/21.0.1/jcanvas.js" crossorigin="anonymous"></script>
</body>
</html>
Consider this following code:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.rect(20,20,150,100);
ctx.stroke();
c.addEventListener("mouseover", doMouseOver, false);//added event to canvas
function doMouseOver(e){
ctx.fillStyle = 'red';
ctx.fill();
}
DEMO
You could use canvas.addEventListener
var canvas = document.getElementById('canvas0');
canvas.addEventListener('mouseover', function() { /*your code*/ }, false);
It worked on google chrome
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.rect(20,20,150,100);
ctx.stroke();
$(c).hover(function(e){
ctx.fillStyle = 'red';
ctx.fill();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="myCanvas"/>

Categories

Resources