I have a simple function designed to make it easier to create patterns using the canvas tag in HTML. I have an image on the site embedded with this code:
<style type="text/css">
.invisible {
display: none;
}
</style>
<img class="invisible" id="image" src="Images/test.png"></img>
and my function that makes a pattern is this:
//Function that allows you to create a pattern
this.pattern = function(image, mode) {
var pat = this.ctx.createPattern(image, mode);
return pat;
};
but when I try to call the function with this code:
var testImage = document.getElementById("image");
myCanvas.fill(myCanvas.pattern(testImage, "repeat"));
an error appears in the log that says this:
Uncaught TypeError: Failed to execute 'createPattern' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)'
now, I'm pretty sure the variable that I put in was an HTMLImageElement, but the error is still there.
full code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Canvas Library</title>
</head>
<style type="text/css">
.invisible {
display: none;
}
</style>
<body>
<p id="text">Unknown</p>
<canvas width="400" height="400" id="canvas"></canvas>
<script type="text/javascript" src="Canvas.js"></script>
<img class="invisible" id="image" src="Images/test.png"></img>
</body>
</html>
// JavaScript File
var Canvas = document.getElementById("canvas");
var label = document.getElementById("text");
var testImage = document.getElementById("image");
var distance = function(x1, y1, x2, y2) {
var a = Math.pow(x2 - x1, 2);
var b = Math.pow(y2 - y1, 2);
return Math.sqrt(a + b);
};
//Declare the Canvas Object
var canvas = function(src) {
//Function to see if a line is colliding with a certain point Has an accuracy of about 1 pixel
this.lineIsColliding = function(startX, startY, endX, endY, testX, testY) {
const v1 = {
x: endX - startX,
y: endY - startY
};
const l2 = v1.x * v1.x + v1.y * v1.y;
if (l2 === 0) {
return false;
} // line has no length so can't be near anything
const v2 = {
x: testX - startX,
y: testY - startY
};
const u = (v1.x * v2.x + v1.y * v2.y) / l2;
return u >= 0 && u <= 1 && Math.abs((v1.x * v2.y - v1.y * v2.x) / Math.sqrt(l2)) < 1;
};
//The Canvas to draw on
this.src = src;
//The context of source(used for drawing)
this.ctx = this.src.getContext("2d");
//The Mouse Move Function
this.showCoordinates = function(e) {
console.log(e);
label.innerHTML = "<b>x: </b>" + e.offsetX + " <b>y: </b>" + e.offsetY + ", " + myCanvas.lineIsColliding(358, 277, 365, 268, e.offsetX, e.offsetY);
};
//Show coordinates variable
this.showCoordinatesBool = true;
//The boolean to tell if we should use stroke
var useStroke = true;
//The fill style and stroke style(can be color, pattern, or gradient)
this.fillStyle = "#000000";
this.strokeStyle = "#000000";
//The Line cap style (can be butt, square, or round)
this.lineCap = "butt";
//The Stroke Weight (how wide the strokes are)
this.strokeWeightVar = "default";
//The corner style (how the corners are drawn)
this.cornerStyle = "miter";
//The Shadow Color
this.shadowColorVar = "#000000";
//The shadow Blur
this.shadowBlurVar = 0;
//The shadow Offsets
this.shadowOffsetX = 0;
this.shadowOffsetY = 0;
//Function to set the fill style
this.fill = function(style) {
this.fillStyle = style;
this.ctx.fillStyle = style;
};
//Function to set the stroke style
this.stroke = function(style) {
this.useStroke = true;
this.strokeStyle = style;
this.ctx.strokeStyle = style;
};
//Function to delete the stroke
this.noStroke = function() {
this.useStroke = false;
};
//Function to draw a rectangle
this.rect = function(x, y, width, height) {
this.ctx.fillRect(x, y, width, height);
if (this.useStroke) {
this.ctx.strokeRect(x, y, width, height);
}
};
//Function to draw a corner
this.corner = function(style, centerX, centerY, x1, y1, x2, y2) {
this.ctx.lineJoin = style;
this.cornerStyle = style;
this.ctx.beginPath();
this.ctx.moveTo(x1, y1);
this.ctx.lineTo(centerX, centerY);
this.ctx.lineTo(x2, y2);
this.ctx.stroke();
};
//Function to draw a hollow rectangle
this.hollowRect = function(x, y, width, height) {
this.ctx.strokeRect(x, y, width, height);
};
//Function to set the canvas background
this.background = function(style) {
this.fillStyle = style;
this.ctx.fillStyle = style;
this.ctx.fillRect(0, 0, this.src.width, this.src.height);
};
//Function to draw a line
this.line = function(startX, startY, endX, endY) {
this.ctx.beginPath();
this.ctx.moveTo(startX, startY);
this.ctx.lineTo(endX, endY);
this.ctx.stroke();
};
//Function to change line style
this.lineCap = function(mode) {
this.ctx.lineCap = mode;
this.lineCap = mode;
};
//Function to change stroke weight
this.strokeWeight = function(weight) {
this.useStroke = true;
this.ctx.lineWidth = weight;
this.strokeWeightVar = weight;
};
//Function to clear a certain area
this.clearArea = function(x, y, width, height) {
this.ctx.clearRect(x, y, width, height);
};
//Turn the show coordinate function on
this.enableCoordinates = function() {
this.showCoordinatesBool = true;
this.src.addEventListener("mousemove", this.showCoordinates);
};
/*Shadows*/
//Set the shadow color
this.shadowColor = function(color) {
this.shadowColorVar = color;
this.ctx.shadowColor = color;
};
//Set the shadow blur
this.shadowBlur = function(blur) {
this.shadowBlurVar = blur;
this.ctx.shadowBlur = blur;
};
//Set the shadow offset
this.shadowOffset = function(offsetX, offsetY) {
this.shadowOffsetX = offsetX;
this.shadowOffsetY = offsetY;
this.ctx.shadowOffsetX = offsetX;
this.ctx.shadowOffsetY = offsetY;
};
//Remove shadows
this.noShadow = function() {
this.shadowOffset(0, 0);
this.shadowColor("#000000");
this.shadowBlur(0);
};
//Function to see if a rectangle is colliding with a specific point
this.rectIsColliding = function(rectX, rectY, rectWidth, rectHeight, testX, testY) {
this.ctx.rect(rectX, rectY, rectWidth, rectHeight);
return this.ctx.isPointInPath(testX, testY);
};
//Function that returns a custom linear gradient
this.linearGradient = function(startX, startY, endX, endY, colorStops) {
var gradient = this.ctx.createLinearGradient(startX, startY, endX, endY);
for (var i = 0; i < colorStops.length; i++) {
gradient.addColorStop(colorStops[i].location, colorStops[i].color);
}
return gradient;
};
//Function that returns a custom radial gradient
this.radialGradient = function(x0, y0, r0, x1, y1, r1, colorStops) {
var radialGradientVar = this.ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
for (var i = 0; i < colorStops.length; i++) {
radialGradientVar.addColorStop(colorStops[i].location, colorStops[i].color);
}
return radialGradientVar;
};
//Function that allows you to create a pattern
this.pattern = function(image, mode) {
var pat = this.ctx.createPattern(image, mode);
return pat;
};
};
//The following code is for testing purposes ONLY!
{
//Create a new canvas
var myCanvas = new canvas(Canvas);
//Set the Background Color
myCanvas.background("#ff0000");
//Set the fill color
myCanvas.fill("#0000ff");
//Set the Stroke Color
myCanvas.stroke("#00ff00");
//Draw a rectangle
myCanvas.rect(164, 153, 50, 100);
//Draw a hollow rectangle
myCanvas.hollowRect(300, 300, 50, 50);
//Disable the Stroke
myCanvas.noStroke();
//Draw a rectangle with no stroke
myCanvas.rect(21, 18, 50, 50);
//Change the Stroke color
myCanvas.stroke("#ffff00");
//Change the stroke weight
myCanvas.strokeWeight(10);
//Change the line cap
myCanvas.lineCap("round");
//Draw a line
myCanvas.line(350, 30, 250, 80);
//Draw a corner
myCanvas.corner("miter", 50, 135, 100, 185, 100, 110);
//Enable the Coordinates
myCanvas.enableCoordinates();
//Clear a space from the canvas
myCanvas.clearArea(6, 245, 100, 100);
//Set the Shadow Color
myCanvas.shadowColor("black");
//Set the shadow Blur
myCanvas.shadowBlur(20);
//Set the shadow offset
myCanvas.shadowOffset(10, 0);
//Set the stroke
myCanvas.noStroke();
//Set the fill color
myCanvas.fill("orange");
//Draw a rectangle
myCanvas.rect(268, 167, 30, 30);
//Remove the shadow
myCanvas.noShadow();
//Test if the rectangle is colliding with a specific point
if (myCanvas.rectIsColliding(268, 167, 30, 30, 290, 170)) {
myCanvas.fill("green");
myCanvas.rect(358, 0, 50, 46);
}
else {
myCanvas.fill("yellow");
myCanvas.rect(362, 0, 50, 46);
}
//Test if a line is colliding with a certain point
console.log("function returned: " + myCanvas.lineIsColliding(358, 277, 365, 268, 362, 271));
if (myCanvas.lineIsColliding(358, 277, 365, 268, 362, 271)) {
console.log("line is colliding!");
myCanvas.line(358, 277, 365, 268);
}
else {
console.log("line is not colliding!");
}
myCanvas.line(0, 0, 50, 10);
//Color stop used for creating gradients
var colorStop = function(location, color) {
this.location = location;
this.color = color;
};
console.log(distance(0, 0, 50, 10));
myCanvas.fill(myCanvas.linearGradient(259, 77, 359, 127, [new colorStop(0.0, "green"), new colorStop(1.0, "blue")]));
myCanvas.rect(259, 87, 100, 50);
myCanvas.fill(myCanvas.radialGradient(309, 225, 10, 309, 225, 50, [new colorStop(0.0, "yellow"), new colorStop(1.0, "green")]));
myCanvas.rect(259, 200, 100, 50);
myCanvas.fill(myCanvas.pattern(testImage, "repeat"));
myCanvas.rect(132, 81, 50, 100);
}
You have to run your code after your image is loaded.
testImage.addEventListener('load', start)
But better way to is to create image from javascript, so you can sure that image onload has't yet emitted.
var image = new Image();
image.addEventListener('load', start);
image.addEventListener('error', errorfn);
image.src = 'Images/test.png'
There is working Sample. I don't see rendered pattern image, but either no errors in console and that image definitely loads.
Related
I have a canvas on which I can place circles wherever I click. I want to detect when any two circle intersect, so i am storing my coordinates in an array.
The radius of every circle is 30, so that is just hardcoded into my formula. That said, even when I place two on top of each other, it's not triggering my little filltext to let me know that it's working. I've tried many things. If someone could tell me why this isn't working, that would be appreciable. The parts where I place the dots works just fine; I just need to detect overlap.
window.onload = init;
function init() {
var canvas = document.getElementById("testCanvas");
var context = canvas.getContext("2d");
var newPath = false;
var circles = [];
canvas.onmousedown = function(e) {
newPath = true;
x = e.clientX - e.target.offsetLeft;
y = e.clientY - e.target.offsetTop;
context.moveTo(x, y);
context.beginPath();
context.arc(x, y, 30, 0, 2 * Math.PI, true);
var nextColor = randomColor();
context.fillStyle = nextColor;
context.fill();
var aCircle = [x, y];
function isIntersect(aCircle, circle) {
return Math.sqrt((aCircle[0]-circle.x) ** 2 + (aCircle[1] - circle.y) ** 2) < 30;
};
circles.forEach(circle => {
if (isIntersect(aCircle, circle)) {
context.fillText('INTERSECTED', 60, 160);
}
});
circles.push(aCircle);
context.closePath();
}
}
Multiply the radius by 2 since each circle has one...
window.onload = init;
function init() {
var canvas = document.getElementById("testCanvas");
var context = canvas.getContext("2d");
var newPath = false;
var circles = [];
canvas.onmousedown = function(e) {
newPath = true;
x = e.clientX - e.target.offsetLeft;
y = e.clientY - e.target.offsetTop;
context.moveTo(x, y);
context.beginPath();
context.arc(x, y, 30, 0, 2 * Math.PI, true);
var nextColor = '#123123' //randomColor();
context.fillStyle = nextColor;
context.fill();
var aCircle = [x, y];
function isIntersect(aCircle, circle) {
var radius = 30;
var dist = Math.hypot(aCircle[0]-circle[0], aCircle[1]-circle[1]);
return dist <= (radius * 2)
};
circles.forEach(circle => {
if (isIntersect(aCircle, circle)) {
console.log("intresected");
//context.fillText('INTERSECTED', 0, 0);
}
});
circles.push(aCircle);
context.closePath();
}
}
<canvas id='testCanvas'></canvas>
In an html document, I have a canvas with a background image, and am drawing a shape and storing it as an object, and then reinserting it as an element of the canvas. As it stands now, the act of reinserting the shape as a canvas element imposes part of the canvas over the background, so that the shape is surrounded by the color of the canvas instead of being drawn over the canvas background. Is there a way to avoid this but still be able to insert the image where I would like? (I know I could save the image as a png and then make its background transparent, but I'd like to do it in the html itself.) I've seen a few things tangential to this, but none that I've found address it directly.
Fiddle: https://jsfiddle.net/ZLevine/h3mo50w8/4/
I used sample code from https://msdn.microsoft.com/en-us/library/gg589516(v=vs.85).aspx to draw the shape.
Code in fiddle:
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>
canvas {
border:1px solid #d3d3d3;
background-image: white;
}
</style>
<head>
<body onload="startGame()">
<script>
var ship = new Image();
var myBackground;
function startGame() {
myBackground = new component(656, 541, "http://wallpapercave.com/wp/PU5vVEd.jpg", 0, 0, "background");
myScore = new component("30px", "Consolas", "white", 280, 40, "text");
myGameArea.start();
makeShip();
}
var myGameArea = {
canvas : document.createElement("canvas"),
start : function() {
this.canvas.width = 480;
this.canvas.height = 540;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop : function() {
clearInterval(this.interval);
}
}
function makeShip() {
ctx = myGameArea.context
// Draw saucer bottom.
ctx.beginPath();
ctx.moveTo(28.4, 16.9);
ctx.bezierCurveTo(28.4, 19.7, 22.9, 22.0, 16.0, 22.0);
ctx.bezierCurveTo(9.1, 22.0, 3.6, 19.7, 3.6, 16.9);
ctx.bezierCurveTo(3.6, 14.1, 9.1, 11.8, 16.0, 11.8);
ctx.bezierCurveTo(22.9, 11.8, 28.4, 14.1, 28.4, 16.9);
ctx.closePath();
ctx.fillStyle = "rgb(222, 103, 0)";
ctx.fill();
// Draw saucer top.
ctx.beginPath();
ctx.moveTo(22.3, 12.0);
ctx.bezierCurveTo(22.3, 13.3, 19.4, 14.3, 15.9, 14.3);
ctx.bezierCurveTo(12.4, 14.3, 9.6, 13.3, 9.6, 12.0);
ctx.bezierCurveTo(9.6, 10.8, 12.4, 9.7, 15.9, 9.7);
ctx.bezierCurveTo(19.4, 9.7, 22.3, 10.8, 22.3, 12.0);
ctx.closePath();
ctx.fillStyle = "rgb(51, 190, 0)";
ctx.fill();
// Save ship data.
ship = ctx.getImageData(0, 0, 30, 30);
}
function component(width, height, color, x, y, type) {
this.type = type;
if (type == "image" || type == "background") {
this.image = new Image();
this.image.src = color;
}
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
if (this.type == "text") {
ctx.font = this.width + " " + this.height;
ctx.fillStyle = color;
ctx.fillText(this.text, this.x, this.y);
} if (type == "image" || type == "background") {
ctx.drawImage(this.image,
this.x,
this.y,
this.width, this.height);
if (type == "background") {
ctx.drawImage(this.image, this.x,
this.y - this.height,
this.width, this.height);
}
}
else {
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
if (this.type == "background") {
if (this.y == (this.height)) {
this.y = 0;
}
}
}
}
function updateGameArea() {
myGameArea.clear();
myBackground.speedY = 1;
myBackground.newPos();
myBackground.update();
myGameArea.frameNo += 1;
ctx.putImageData(ship, 200, 200);
}
function everyinterval(n) {
if ((myGameArea.frameNo / n) % 1 == 0) {return true;}
return false;
}
</script>
</div>
</body>
</html>
The problem is that using putImageData() will draw verbatim what you stored as ImageData when using getImageData(). As the ship is surrounded by transparent pixels also these are copied to the canvas overriding what is there.
As markE mentions in his answer, drawing to a temporary canvas is a better solution. All you need to do is to change a couple of places in the code:
// Save ship data.
//ship = ctx.getImageData(0, 0, 30, 30);
ship = document.createElement("canvas"); // create canvas
ship.width = ship.height = 30; // set size
var shipCtx = ship.getContext("2d"); // temp. context
shipCtx.drawImage(ctx.canvas, 0, 0); // draw ship to canvas
Then when you want to draw it back:
function updateGameArea() {
myGameArea.clear();
myBackground.speedY = 1;
myBackground.newPos();
myBackground.update();
myGameArea.frameNo += 1;
//ctx.putImageData(ship, 200, 200);
ctx.drawImage(ship, 200, 200); // draw ship from canvas
}
Updated fiddle
An alternative approach is to draw the shape directly onto the canvas, but using a intermediate canvas will give performance benefits.
Draw the ship on a second in-memory canvas:
var memCanvas=document.createElement('canvas');
... draw your ship on the second canvas
Then use the second canvas to draw your ship on the main canvas:
canvas.drawImage(memCanvas,x,y);
I am facing the issue on filling the background image within the polygon selected at roof top.
I have successfully created the polygon, now I want that as soon as the image is selected from the number of slides present, that image should get filled within the selected polygon.
I am using Raphael js for doing the same, if possible with any other js then please advise.
Below is the code for testing purpose:
// Creates canvas 320 × 200 at 10, 50
var paper = Raphael(10, 50, 320, 200);
//draw triangle
var t = paper.path("M0 0L250 0L100 100L 0");
// Sets the fill attribute of the circle to red (#f00)
t.attr("fill", "url('http://interlock.renoworks.com/en/data/exterior/Slate/~Interlock-01-SlateRoofing/~swatch1-400.jpg')");
// Sets the stroke attribute of the circle to white
t.attr("stroke", "#f00");
Here is the demo url: http://jsfiddle.net/hxez863d/5/
You can clip a path in HTML5 without a library. Draw on the canvas after to put things in the clip region.
var can = document.getElementById('can');
var ctx = can.getContext('2d');
var noise = makeNoise(300,200);
var squares = makeSquares(300, 200, 10, "#CCCCCC", "#999999");
// Draw background image.
ctx.drawImage(noise, 0, 0);
ctx.save();
//var paper = Raphael(10, 50, 320, 200);
ctx.translate(10, 50);
ctx.save();
//draw triangle
//var t = paper.path("M0 0L250 0L100 100L 0");
clipPath(ctx, [
[0,0],
[250, 0],
[100, 100],
[0, 0]
]);
// Draw with clip.
ctx.drawImage(squares, 0, 0);
ctx.fillRect(90, 70, 30, 30);
ctx.restore(); // <-- removes clip
// Draw without clip.
ctx.fillStyle = "red";
ctx.fillRect(100, 80, 30, 30);
ctx.restore(); // <-- removes translate
function clipPath(ctx, coords) {
ctx.beginPath();
ctx.moveTo(coords[0][0], coords[0][1]);
for (var i = 1; i < coords.length; i++) {
ctx.lineTo(coords[i][0], coords[i][1]);
}
ctx.clip();
}
function makeNoise(w, h) {
var can = document.createElement('canvas');
can.width = w;
can.height = h;
var ctx = can.getContext('2d');
var imageData = ctx.getImageData(0, 0, w, h);
var d = imageData.data;
var len = d.length;
for (var i = 0; i < len; i+=4) {
d[i] = d[i+1] = d[i+2] = Math.floor(Math.random() * 256);
d[i+3] = 255;
}
ctx.putImageData(imageData, 0, 0);
return can;
}
function makeSquares(w, h, size, color1, color2) {
var can = document.createElement('canvas');
var ctx = can.getContext('2d');
can.width = w;
can.height = h;
for (var y = 0; y < h; y+= size) {
for (var x = 0; x < w; x += size*2) {
ctx.fillStyle = color1;
ctx.fillRect(x, y, size, size);
ctx.fillStyle = color2;
ctx.fillRect(x + size, y, size, size);
}
var temp = color1;
color1 = color2;
color2 = temp;
}
return can;
}
<canvas id="can" width="300" height="200"></canvas>
I am trying to draw a donut pie chart using Canvas. Which is almost done but facing some issue in adjusting position of slices.
Current:
Expected:
enter code here
http://jsfiddle.net/RgLAU/1/
I want 1.) yellow/blue to draw from top 2.) want to write some text inside the donut.
Here is my work upto now:
http://jsfiddle.net/RgLAU/1/
arc() method starts from an horizontal line, on the right of your shape, at middle y position of the shape's height.
You will need to add an offset to each of your start and end angle value.
For your text, I'm not sure what it should display, but setting the context's textAlign = "center" and textBaseline = "middle" will make it easy to position anywhere.
A rough uncleaned dump of your modified code :
var canvas = document.getElementById("chart");
var chart = canvas.getContext("2d");
function drawdountChart(canvas) {
// text options
chart.textAlign = "center";
chart.textBaseline = "middle";
chart.font = "25px sans-serif";
// where is our arc start angle
var offset = 1.5 * Math.PI;
this.x, this.y, this.radius, this.lineWidth, this.strockStyle, this.from, this.to = null;
this.set = function(x, y, radius, from, to, lineWidth, strockStyle) {
this.x = x;
this.y = y;
this.radius = radius;
this.from = from;
this.to = to;
this.lineWidth = lineWidth;
this.strockStyle = strockStyle;
}
this.draw = function(data) {
canvas.beginPath();
canvas.lineWidth = this.lineWidth;
canvas.strokeStyle = this.strockStyle;
canvas.arc(this.x, this.y, this.radius, this.from + offset, this.to + offset);
canvas.stroke();
var numberOfParts = data.numberOfParts;
var parts = data.parts.pt;
var colors = data.colors.cs;
var df = 0;
for (var i = 0; i < numberOfParts; i++) {
canvas.beginPath();
canvas.strokeStyle = colors[i];
canvas.arc(this.x, this.y, this.radius, df + offset, df + (Math.PI * 2) * (parts[i] / 100) + offset);
canvas.stroke();
df += (Math.PI * 2) * (parts[i] / 100);
}
chart.fillStyle = 'white'
chart.fillText('hello', this.x, this.y);
}
}
var data = {
numberOfParts: 4,
parts: {
"pt": [20, 30, 25, 25]
}, //percentage of each parts
colors: {
"cs": ["red", "green", "blue", "yellow"]
} //color of each part
};
var drawDount = new drawdountChart(chart);
drawDount.set(150, 150, 100, 0, Math.PI * 2, 30, "#fff");
drawDount.draw(data);
<canvas id="chart" width="500" height="500" style="background-color:black"> </canvas>
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"/>