The script below draws an image on the left side of the screen and a selection box in the right. It then attempts to redefine the image drawn on the left on each new selection in the selection box on the right by making the imageID dependent on the selection. However, as you can see below, whatever number you select on the right the image remains the same (1) because whilst it might be redrawn, it is not redefined on selection. What I would like to happen is that on selection in the box on the right the number in the image changes with the selection box such that it always correlates with the selection. In other words, when you click on 2 the image changes to the 2nd image in images. I have found two ways of doing this but they are both flawed:
1: Define img in paint's render function. This works but it makes everything run very slowly and the hover animations on the image stop working as expected.
2: Define img in the makeSelectionInfo function. This also works but the hover animations completely stop working if this is done.
I apologise for the long code but I couldn't condense it any more. For the sake of brevity I have only included images for numbers between 1 & 5. Any help will be appreciated.
var c=document.getElementById('game'),
canvasX=c.offsetLeft,
canvasY=c.offsetTop,
ctx=c.getContext('2d');
images=['https://i.stack.imgur.com/KfN4z.jpg',
'https://i.stack.imgur.com/MyQS1.png',
'https://i.stack.imgur.com/3Vlfj.jpg',
'https://i.stack.imgur.com/u3NLH.jpg',
'https://i.stack.imgur.com/XnLwl.png'];
var curvedRect = function(text, x, y, w, h) {
this.text = text;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.hovered = false;
this.clicked = false;
}
curvedRect.prototype.makeCurvedRect = function() {
var delta=0, theta=0, yRotation=this.y;
if (this.hovered) {
delta = 3;
shadowColor = '#000000';
shadowBlur = 20;
shadowOffsetX = 5;
shadowOffsetY = 5;
theta = -0.01;
} else {
delta = 0;
theta = 0;
shadowColor = '#9F3A9B';
shadowBlur = 0;
shadowOffsetX = 0;
shadowOffsetY = 0;
}
var x = this.x-delta;
var y = yRotation-delta;
var w = this.w+(2*delta);
var h = this.h+(2*delta);
var img=new Image();
img.src=images[this.text];
ctx.rotate(theta);
ctx.beginPath();
ctx.lineWidth='8';
ctx.strokeStyle='white';
ctx.moveTo(x+10, y);
ctx.lineTo(x+w-10, y);
ctx.quadraticCurveTo(x+w, y, x+w, y+10);
ctx.lineTo(x+w, y+h-10);
ctx.quadraticCurveTo(x+w, y+h, x+w-10, y+h);
ctx.lineTo(x+10, y+h);
ctx.quadraticCurveTo(x, y+h, x, y+h-10);
ctx.lineTo(x, y+10);
ctx.quadraticCurveTo(x, y, x+10, y);
ctx.shadowColor = shadowColor;
ctx.shadowBlur = shadowBlur;
ctx.shadowOffsetX = shadowOffsetX;
ctx.shadowOffsetY = shadowOffsetY;
ctx.stroke();
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.drawImage(img, x+2.5, y+2.5, w-5, h-5);
ctx.rotate(-theta);
}
curvedRect.prototype.hitTest = function(x, y) {
return (x >= this.x) && (x <= (this.w+this.x)) && (y >= this.y) && (y <= (this.h+this.y));
}
var selectionForMenu = function(id, text, y) {
this.id = id;
this.text = text;
this.y = y;
this.hovered = false;
this.clicked = false;
this.lastClicked = false;
}
function makeTextForSelected(text, y) {
ctx.font='bold 12px Noto Sans';
ctx.fillStyle='white';
ctx.textAlign='center';
ctx.fillText(text, 200, y);
}
function makeSelectionInfo(text) {
makeTextForSelected(text, 375);
}
selectionForMenu.prototype.makeSelection = function() {
var fillColor='#A84FA5';
if (this.hovered) {
if (this.clicked) {
if (this.lastClicked) {
fillColor='#E4C7E2';
} else {
fillColor='#D5A9D3';
}
} else if (this.lastClicked) {
fillColor='#D3A4D0';
makeSelectionInfo(this.text);
} else {
fillColor='#BA74B7';
}
} else if (this.lastClicked) {
fillColor='#C78DC5';
makeSelectionInfo(this.text);
} else {
fillColor='#A84FA5';
}
ctx.beginPath();
ctx.fillStyle=fillColor;
ctx.fillRect(400, this.y, 350, 30)
ctx.stroke();
ctx.font='10px Noto Sans';
ctx.fillStyle='white';
ctx.textAlign='left';
ctx.fillText(this.text, 410, this.y+19);
}
selectionForMenu.prototype.hitTest = function(x, y) {
return (x >= 400) && (x <= (750)) && (y >= this.y) && (y <= (this.y+30)) && !((x >= 400) && (y > 450));
}
var Paint = function(element) {
this.element = element;
this.shapes = [];
}
Paint.prototype.addShape = function(shape) {
this.shapes.push(shape);
}
Paint.prototype.render = function() {
ctx.clearRect(0, 0, this.element.width, this.element.height);
for (var i=0; i<this.shapes.length; i++) {
try {
this.shapes[i].makeSelection();
}
catch(err) {}
try {
this.shapes[i].makeCurvedRect();
}
catch(err) {}
}
ctx.beginPath();
ctx.fillStyle='white';
ctx.fillRect(0, 0, 750, 25);
ctx.stroke();
for (var i=0; i<this.shapes.length; i++) {
try {
this.shapes[i].makeBox();
}
catch(err) {}
}
ctx.beginPath();
ctx.fillStyle='#BC77BA';
ctx.fillRect(0, 450, 750, 50);
ctx.stroke();
ctx.font='bold 10px Noto Sans';
ctx.fillStyle='#9F3A9B';
ctx.textAlign='center';
ctx.fillText('Phrase Practice', 365, 17);
for (var i=0; i<this.shapes.length; i++) {
try {
this.shapes[i].makeInteractiveButton();
}
catch(err) {}
}
}
Paint.prototype.setHovered = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].hovered = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.setClicked = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].clicked = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.setUnclicked = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
if (shape.constructor.name==this.shapes[i].constructor.name) {
this.shapes[i].clicked = false;
if (shape instanceof selectionForMenu) {
this.shapes[i].lastClicked = this.shapes[i] == shape;
}
}
}
this.render();
}
Paint.prototype.select = function(x, y) {
for (var i=this.shapes.length-1; i >= 0; i--) {
if (this.shapes[i].hitTest(x, y)) {
return this.shapes[i];
}
}
return null
}
imageID = 0;
var paint = new Paint(c);
var img = new curvedRect(imageID, 112.5, 100, 175, 175);
var selection = [];
for (i=0; i<=30; i++) {
selection.push(new selectionForMenu(i, i, 25+(i*30)));
}
paint.addShape(img);
for (i=0; i<30; i++) {
paint.addShape(selection[i])
}
paint.render();
var clickedShape=0;
var i=0;
function mouseDown(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
if (shape instanceof selectionForMenu) {
imageTextID = shape.id;
if (i==0) {
clickedShape=shape;
i=1;
} else if (i==1) {
i=0;
}
}
paint.setClicked(shape);
}
function mouseUp(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
if (clickedShape instanceof selectionForMenu) {
if (x>400 && y>25 && y<450) {
paint.setUnclicked(shape);
} else if (shape && !(shape instanceof selectionForMenu)) {
paint.setUnclicked(shape);
}
}
}
function mouseMove(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
paint.setHovered(shape);
}
c.addEventListener('mousedown', mouseDown);
c.addEventListener('mouseup', mouseUp);
c.addEventListener('mousemove', mouseMove);
canvas {
z-index: -1;
margin: 1em auto;
border: 1px solid black;
display: block;
background: #9F3A9B;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>uTalk Demo</title>
</head>
<body>
<canvas id="game" width = "750" height = "500"></canvas>
</body>
</html>
Fixed your code here https://jsfiddle.net/0wq0hked/2/
You can diff to see what I changed, but basically you weren't initializing and adding the multiple curvedRect to the Paint.shapes array. I also added the images as an attribute of curvedRect.
I also had to add a visible parameter to your shapes, as your mouse hover Paint.select function was not functioning properly. The way yours works, shapes that share the same (x,y) do not allow other shapes from being hovered even when they are not visible. Thus multiple shapes occupying the image area to the left stopped the hover from working properly. I suppose you could keep your Paint.select and instance/remove shapes when they are to be drawn, but you do not have shape removal functionality as far as I can tell.
Also, you call render on every event, this is a bad idea. Take a look at requestAnimationFrame and try drawing at the screen refresh rate rather than on user input.
Related
The attached code shows how to select (on click) a drawn object on canvas and then the object is moved with a double click to click position or deselected with a double click prior to/ post-movement.
I have tried for days but could not work out how to apply this function to all objects in a class or an array via looping (via class constructor + prototyping). I would like to be able to select or deselect any object on screen.
Help will be very much appreciated. Thank you.
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
canvas {
display: block;
margin: 0px;
}
body {
margin: 0px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<input id="click1" type ="button" value="x" style="position: fixed; top: 0px; left: 650px; position: absolute;"></input>
<input id="click2" type ="button" value="y" style="position: fixed; top: 0px; left: 750px; position: absolute;"></input>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
let strokeColor;
let color;
let mouse_x;
let mouse_y;
let x;
let y;
let w;
let h;
let selected = false;
x = 50;
y = 50;
w = 50;
h = 50;
color="green";
strokeColor = "green";
document.getElementById('canvas').addEventListener("mousemove",go);
document.getElementById('canvas').addEventListener("mouseup",mouseUp);
document.getElementById('canvas').addEventListener("dblclick",dblClick);
document.getElementById('canvas').addEventListener("dblclick",move);
function move(){
if(selected == true){
x = mouse_x;
y = mouse_y;
}
}
function mouseUp(){
if(mouse_x > x && mouse_x < x+w && mouse_y > y && mouse_y < y+w){
strokeColor = "black";
selected = true;
console.log(selected);
}
}
function dblClick(){
if(mouse_x > x && mouse_x < x+w && mouse_y > y && mouse_y < y+w){
color = "green";
strokeColor = color;
selected = false;
console.log(selected);
}
}
function go(e){
mouse_x = e.clientX;
mouse_y = e.clientY;
document.getElementById('click1').value = mouse_x;
document.getElementById('click2').value = mouse_y;
}
function draw(){
context.strokeStyle = strokeColor;;
context.fillStyle = color;
context.beginPath();
context.lineWidth = 3;
context.rect(x,y,w,h);
context.fill();
context.stroke();
}
function animate(){
context.clearRect(0,0,width,height);
context.save();
draw();
context.restore();
requestAnimationFrame(animate);
}
animate();
};
</script>
</body>
</html>
This should be easy to do, let's first talk about the logic that we need.
Have a list of items that we can draw.
Make those items have their own state.
Check if when we click we hit an item or not.
Move or toggle the selected item.
Handle Clear & Draw of the canvas.
Made this simple fiddle just to show you the idea behind it,
let context = $("canvas")[0].getContext("2d");
let elements = [
new element(20, 20, 20, 20, "green"),
new element(45, 30, 30, 30, "red"),
new element(80, 40, 50, 50, "blue"),
];
let mousePosition = {
x: 0,
y: 0,
};
let selected;
function element(x, y, height, width, color) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.color = color;
this.selected = false;
this.draw = function(context) {
context.strokeStyle = (this.selected ? "black" : "white");
context.fillStyle = this.color;
context.beginPath();
context.lineWidth = 2;
context.rect(this.x, this.y, this.height, this.width);
context.fill();
context.stroke();
}
this.move = function(x, y) {
this.x = x;
this.y = y;
}
}
//Select Function
function get_select(x, y) {
let found;
$.each(elements, (i, element) => {
if(x > element.x
&& x < element.x + element.width
&& y > element.y
&& y < element.y + element.height) {
found = element;
}
});
return (found);
}
// Handle selection & Movement.
$("canvas").click(function() {
let found = get_select(mousePosition.x, mousePosition.y);
Clear();
// Toggle Selection
if (found && !selected) {
found.selected = true;
selected = found;
} else if (found === selected) {
found.selected = false;
selected = null;
}
// Move
if (!found && selected) {
selected.move(mousePosition.x, mousePosition.y);
}
Draw();
});
// Record mouse position.
$("canvas").mousemove((event) => {
mousePosition.x = event.pageX;
mousePosition.y = event.pageY;
});
//Draw ALL elements.
function Draw() {
$.each(elements, (i, element) => {
element.draw(context);
});
}
function Clear() {
context.clearRect(0, 0, $("canvas")[0].width, $("canvas")[0].height);
}
// Start.
$(document).ready(() => {
Draw();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
<canvas></canvas>
</body>
</html>
Hope it helps you out!
I am trying to write a scroll-on-hover function in an HTML canvas by defining a hover variable, detecting mouse events over the designated hover area and (on doing so) adding or subtracting to this hover variable depending on which area is hovered over. This hover variable is connected to the position of a series of selection buttons which, for the purposes of this example, contain the numbers 0 to 30. When either end of this series of selection buttons is hovered over they all move up or down as if scrolled, but to make it act like a scroll you must keep the mouse moving as the canvas is only rendered on each new mousemove event.
My question is how can I trigger the event on mouseover such that if (lowerHoverBoxHitTest(x, y)) or (upperHoverBoxHitTest(x, y)) (i.e if the mouse is hovered over either of the hit boxes defined in the script below) the hover variable keeps being added to by the set increment (0.1) until the mouse leaves that area. I have tried replacing the if/else statement in the function mouseMove with a while loop (as it would seem this is logically akin to what I am asking) as so
while (lowerHoverBoxHitTest(x, y)) {
if (hover < 750) {
hover-=0.1;
}
}
while (upperHoverBoxHitTest(x, y)) {
if (hover > 0) {
hover+=0.1;
}
}
but this just causes the page to crash (presumably it triggers an infinite loop?). There isn't much on Stack Overflow about this besides this but this solution is not useful if you have a lot of other things in your canvas that you don't want to scroll (unless you were to define their position absolutely which I don't want to) which I do in my full project. Any help will be appreciated.
var c=document.getElementById('game'),
canvasX=c.offsetLeft,
canvasY=c.offsetTop,
ctx=c.getContext('2d');
var hover=0;
function upperHoverBoxHitTest(x, y) {
return (x >= 0) && (x <= 350) && (y >= 0) && (y <= 50);
}
function lowerHoverBoxHitTest(x, y) {
return (x >= 0) && (x <= 350) && (y >= 450) && (y <= 500);
}
var selectionForMenu = function(id, text, y) {
this.id = id;
this.text = text;
this.y = y;
}
selectionForMenu.prototype.makeSelection = function() {
ctx.beginPath();
ctx.fillStyle='#A84FA5';
ctx.fillRect(0, this.y+hover, 350, 30)
ctx.stroke();
ctx.font='10px Noto Sans';
ctx.fillStyle='white';
ctx.textAlign='left';
ctx.fillText(this.text, 10, this.y+hover+19);
}
var Paint = function(element) {
this.element = element;
this.shapes = [];
}
Paint.prototype.addShape = function(shape) {
this.shapes.push(shape);
}
Paint.prototype.render = function() {
ctx.clearRect(0, 0, this.element.width, this.element.height);
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].makeSelection();
}
}
var paint = new Paint(c);
for (i=0; i<30; i++) {
paint.addShape(new selectionForMenu(i+1, i, i*30));
}
paint.render();
function mouseMove(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
paint.render();
if (lowerHoverBoxHitTest(x, y)) {
hover+=1;
} else if (upperHoverBoxHitTest(x, y)) {
hover-=1;
}
}
c.addEventListener('mousemove', mouseMove);
canvas {
z-index: -1;
margin: 1em auto;
border: 1px solid black;
display: block;
background: #9F3A9B;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>uTalk Demo</title>
<link rel='stylesheet' type='text/css' href='wordpractice.css' media='screen'>
</head>
<body>
<canvas id="game" width = "350" height = "500"></canvas>
</body>
</html>
Animation via animation loops.
You need to have an animation loop that will increment/decrement the value if the conditions are met. This loop can be part of another if you have one (which is better than adding an animation loop for each animated object) or as its own function.
The animation loop does all the rendering, and only if needed (no point rendering something that is already rendered).
Demo
Demo is a copy of the OP's code with modifications to animate the scrolling and give a little user feed back. Though not complete as a scrolling selection box, it will need some tweaking to be useful.
var c = document.getElementById('game'),
canvasX = c.offsetLeft,
canvasY = c.offsetTop,
ctx = c.getContext('2d');
var hover = 0;
const overTypes = {
lower : 1,
raise : 2,
none : 0,
}
var overBox = 0;
var overDist = 0;
const maxSpeed = 4;
const shapeSize = 30;
const hoverScrollSize = 50;
const gradUp = ctx.createLinearGradient(0, 0, 0, hoverScrollSize);
const gradDown = ctx.createLinearGradient(0, ctx.canvas.height - hoverScrollSize, 0, ctx.canvas.height);
gradUp.addColorStop(0, `rgba(${0xA8},${0x4F},${0xB5},1)`);
gradUp.addColorStop(1, `rgba(${0xA8},${0x4F},${0xB5},0)`);
gradDown.addColorStop(1, `rgba(${0xB8},${0x5F},${0xB5},1)`);
gradDown.addColorStop(0, `rgba(${0xB8},${0x5F},${0xB5},0)`);
c.addEventListener('mousemove', mouseMove)
c.addEventListener('mouseout', () => {
overBox = overTypes.none
}); // stop scroll when mouse out of canvas
// start the first frame
requestAnimationFrame(() => {
paint.render(); // paint first frame
requestAnimationFrame(mainLoop); // start main loop
});
function mainLoop() {
if (overBox !== overTypes.none) {
hover += overDist / hoverScrollSize * (overBox === overTypes.lower ? maxSpeed : -maxSpeed);
var bottom = - (paint.shapes.length - ctx.canvas.height / shapeSize) * shapeSize;
hover = hover > 0 ? 0 : hover < bottom ? bottom : hover;
paint.render();
}
requestAnimationFrame(mainLoop); // wait for next animation frame
}
function mouseMove(event) {
var x = event.clientX - canvasX;
var y = event.clientY - canvasY;
if (lowerHoverBoxHitTest(x, y)) {
overBox = overTypes.lower;
} else if (upperHoverBoxHitTest(x, y)) {
overBox = overTypes.raise;
} else {
overBox = overTypes.none;
}
}
function upperHoverBoxHitTest(x, y) {
overDist = hoverScrollSize - y;
return (x >= 0) && (x <= 350) && (y >= 0) && (y <= hoverScrollSize);
}
function lowerHoverBoxHitTest(x, y) {
overDist = y - (ctx.canvas.height - hoverScrollSize);
return (x >= 0) && (x <= 350) && (y >= ctx.canvas.height - hoverScrollSize) && (y <= ctx.canvas.height);
}
var selectionForMenu = function (id, text, y) {
this.id = id;
this.text = text;
this.y = y;
}
selectionForMenu.prototype.makeSelection = function () {
ctx.beginPath();
ctx.fillStyle = '#A84FA5';
ctx.fillRect(0, this.y + hover, 350, shapeSize)
ctx.stroke();
ctx.font = '10px Noto Sans';
ctx.fillStyle = 'white';
ctx.textAlign = 'left';
ctx.fillText(this.text, 10, this.y + hover + 19);
}
var Paint = function (element) {
this.element = element;
this.shapes = [];
}
Paint.prototype.addShape = function (shape) {
this.shapes.push(shape);
}
Paint.prototype.render = function () {
ctx.clearRect(0, 0, this.element.width, this.element.height);
for (var i = 0; i < this.shapes.length; i++) {
this.shapes[i].makeSelection();
}
if (overBox !== overTypes.none) {
ctx.globalAlpha = 0.4 * (overDist / 50);
ctx.globalCompositeOperation = "lighter";
if (overBox === overTypes.raise) {
ctx.fillStyle = gradUp;
ctx.fillRect(0, 0, ctx.canvas.width, hoverScrollSize);
} else if (overBox === overTypes.lower) {
ctx.fillStyle = gradDown;
ctx.fillRect(0, ctx.canvas.height - hoverScrollSize, ctx.canvas.width, hoverScrollSize);
}
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
}
}
var paint = new Paint(c);
for (i = 0; i < 30; i++) {
paint.addShape(new selectionForMenu(i + 1, i, i * 30));
}
paint.render();
canvas {
z-index: -1;
margin: 1em auto;
border: 1px solid black;
display: block;
background: #9F3A9B;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>uTalk Demo</title>
<link rel='stylesheet' type='text/css' href='wordpractice.css' media='screen'>
</head>
<body>
<canvas id="game" width = "350" height = "150"></canvas>
</body>
</html>
The script below draws 4 curved rectangles and detects if they are hovered over or clicked on with mouseup and mousemove event listeners. When one of the rectangles is hovered over, I would like it to grow slightly over the course of a second - it should then shrink back when the cursor moves off the rectangle.
The problem I have is that the only way I can think of doing this is by writing a function inside the if (this.selected) { clause which plots a new version of the rectangle for each frame in an animation lasting a second, but this clause is inside the function that plots a new version of the rectangle itself! How, then, can I write a function outside of curvedRect.prototype.makeCurvedRect which only runs when the cursor is over one of the rectangles? Any help will be appreciated.
var c=document.getElementById('game'),
canvasX=c.offsetLeft,
canvasY=c.offsetTop,
ctx=c.getContext('2d');
var curvedRect = function(id, x, y, w, h) {
this.id = id;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.selected = false;
}
curvedRect.prototype.makeCurvedRect = function() {
ctx.beginPath();
ctx.lineWidth='8';
ctx.strokeStyle='white';
ctx.moveTo(this.x+10, this.y);
ctx.lineTo(this.x+this.w-10, this.y);
ctx.quadraticCurveTo(this.x+this.w, this.y, this.x+this.w, this.y+10);
ctx.lineTo(this.x+this.w, this.y+this.h-10);
ctx.quadraticCurveTo(this.x+this.w, this.y+this.h, this.x+this.w-10, this.y+this.h);
ctx.lineTo(this.x+10, this.y+this.h);
ctx.quadraticCurveTo(this.x, this.y+this.h, this.x, this.y+this.h-10);
ctx.lineTo(this.x, this.y+10);
ctx.quadraticCurveTo(this.x, this.y, this.x+10, this.y);
ctx.stroke();
if (this.selected) {
// BM67 edit By Blindman67 removed annoying alert and replaced it with console.log
// As I did not want to add elsewhere to the code to declare hoverMessageShown is safely declared here to stop the console repeating the message.
if(window["hoverMessageShown"] === undefined){
window["hoverMessageShown"] = true;
console.log('When hovered over, I would like this box to grow slightly over a short period of time (say a second). When the mouse is removed I would like it to shrink back.')
}
// BM67 end.
}
}
curvedRect.prototype.hitTest = function(x, y) {
return (x >= this.x) && (x <= (this.w+this.x)) && (y >= this.y) && (y <= (this.h+this.y));
}
var Paint = function(element) {
this.element = element;
this.shapes = [];
}
Paint.prototype.addShape = function(shape) {
this.shapes.push(shape);
}
Paint.prototype.render = function() {
this.element.w = this.element.w;
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].makeCurvedRect();
}
}
Paint.prototype.setSelected = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].selected = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.select = function(x, y) {
for (var i=this.shapes.length-1; i >= 0; i--) {
if (this.shapes[i].hitTest(x, y)) {
return this.shapes[i];
}
}
return null
}
var paint = new Paint(c);
var img1 = new curvedRect('1', 200, 55, 150, 150);
var img2 = new curvedRect('2', 375, 55, 150, 150);
var img3 = new curvedRect('3', 200, 230, 150, 150);
var img4 = new curvedRect('4', 375, 230, 150, 150);
paint.addShape(img1);
paint.addShape(img2);
paint.addShape(img3);
paint.addShape(img4);
paint.render();
function mouseUp(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
if (shape) {
alert(shape.id);
}
// console.log('selected shape: ', shape);
}
function mouseMove(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
paint.setSelected(shape);
}
c.addEventListener('mouseup', mouseUp);
c.addEventListener('mousemove', mouseMove);
canvas {
display: block;
margin: 1em auto;
border: 1px solid black;
background: #FF9900;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>uTalk Demo</title>
<link rel='stylesheet' type='text/css' href='game.css' media='screen'></style>
</head>
<body>
<canvas id="game" width = "750" height = "500"></canvas>
</body>
</html>
Change the makeCurvedRect method to something like this:
var c=document.getElementById('game'),
canvasX=c.offsetLeft,
canvasY=c.offsetTop,
ctx=c.getContext('2d');
var curvedRect = function(id, x, y, w, h) {
this.id = id;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.selected = false;
}
curvedRect.prototype.makeCurvedRect = function() {
var delta = this.selected?10:0 ;
var x = this.x - delta;
var y = this.y - delta;
var w = this.w + (2*delta);
var h = this.h + (2*delta);
ctx.beginPath();
ctx.lineWidth='8';
ctx.strokeStyle='white';
ctx.moveTo(x+10, y);
ctx.lineTo(x+ w -10, y);
ctx.quadraticCurveTo(x + w, y,x + w,y + 10);
ctx.lineTo( x + w, y + h-10);
ctx.quadraticCurveTo( x + w, y + h, x + w - 10, y + h);
ctx.lineTo(x + 10,y + h);
ctx.quadraticCurveTo( x, y + h, x, y+h-10);
ctx.lineTo(x, y+10);
ctx.quadraticCurveTo(x, y, x+10, y);
ctx.stroke();
}
curvedRect.prototype.hitTest = function(x, y) {
return (x >= this.x) && (x <= (this.w+this.x)) && (y >= this.y) && (y <= (this.h+this.y));
}
var Paint = function(element) {
this.element = element;
this.shapes = [];
}
Paint.prototype.addShape = function(shape) {
this.shapes.push(shape);
}
Paint.prototype.render = function() {
this.element.width = this.element.width;
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].makeCurvedRect();
}
}
Paint.prototype.setSelected = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].selected = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.select = function(x, y) {
for (var i=this.shapes.length-1; i >= 0; i--) {
if (this.shapes[i].hitTest(x, y)) {
return this.shapes[i];
}
}
return null
}
var paint = new Paint(c);
var img1 = new curvedRect('1', 200, 55, 150, 150);
var img2 = new curvedRect('2', 375, 55, 150, 150);
var img3 = new curvedRect('3', 200, 230, 150, 150);
var img4 = new curvedRect('4', 375, 230, 150, 150);
paint.addShape(img1);
paint.addShape(img2);
paint.addShape(img3);
paint.addShape(img4);
paint.render();
function mouseUp(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
if (shape) {
alert(shape.id);
}
// console.log('selected shape: ', shape);
}
function mouseMove(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
paint.setSelected(shape);
}
c.addEventListener('mouseup', mouseUp);
c.addEventListener('mousemove', mouseMove);
canvas {
display: block;
margin: 1em auto;
border: 1px solid black;
background: #FF9900;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>uTalk Demo</title>
<link rel='stylesheet' type='text/css' href='game.css' media='screen'></style>
</head>
<body>
<canvas id="game" width = "750" height = "500"></canvas>
</body>
</html>
I have a canvas, and I'm using geolocation, google maps and openweathermap to get the weather of where the user is. The weather will be taken and cause the game I'm making to also use that weather...
How can I check if two squares that are spawned onto the canvas overlap? This is the code for the rain, which looks nothing like rain at the moment...
function rectangle(x, y, w, h) {
var randomx = Math.floor(Math.random() * canvas.width - 50);
this.x = randomx || x || 0;
this.y = y || 0;
this.w = w || 0;
this.h = h || 0;
this.expired = false;
this.draw = function() {
cx.fillStyle = "blue";
cx.fillRect(this.x, this.y, this.w, this.h);
};
this.update = function() {
this.y++;
if (y > canvas.height) {
this.expired = true;
}
};
}
var rectangles = new Array();
function newRect() {
rectangles.push(new rectangle(window.randomx, window.y, 10, 10));
}
var timing = 0;
function loop() {
cx.clearRect(0, 0, canvas.width, canvas.height);
if (timing % 10 === 0) {
newRect();
}
for (var i = 0, l = rectangles.length; i < l; i++) {
rectangles[i].update();
rectangles[i].draw();
if (rectangles[i].expired) {
rectangles.splice(i, 1);
i--;
}
}
timing++;
requestAnimFrame(loop);
}
loop();
My assumption, is that I need to perform a 'hit test' to see if two rectangles in the array are in the same perimeter... I guess where my this.draw and this.update function are I should do something like...
this.hitTest = function () {
//Maths to check here
};
Then in the forloop do...
rectangles[i].hitTest();
But I'm not sure on the Maths or where to go from there...
Any help would be appreciated, thanks in advance!
You can extend your rectangle object like this:
function rectangle(x, y, w, h) {
... existing code here ...
}
rectangle.prototype.intersects = function(rect) {
return !( rect.x > (this.x + this.w) ||
(rect.x + rect.w) < this.x ||
rect.y > (this.y + this.h) ||
(rect.y + rect.h) < this.y);
}
Based on this code by Daniel Vassallo, adopted for your object.
Now simply call the function on the rectangle you want to compare with:
if ( rectangles[x].intersects(rectangles[y]) ) {
... they intersect ...
}
To check if a new rectangle intersects with an existing you can do:
function isOverlapping(myNewRect, rectangles) {
for(var i = 0, r; r = rectangles[i]; i++) {
if (myNewRect.intersect(r)) return true;
}
return false;
}
If I have a 600 by 400 grid, with 10 by 10 pixel squares like this:
/**
* draws grid to screen
*/
function drawgrid(context)
{
for(var x = 0.5; x < 600; x += 10)
{
context.moveTo(x, 0);
context.lineTo(x, 400);
}
for(var y = 0.5; y < 400; y += 10)
{
context.moveTo(0, y);
context.lineTo(600, y);
}
context.strokeStyle = "#eee";
context.stroke();
}
/**
* Creates a canvas element, loads images, adds events, and draws the canvas for the first time.
*/
function prepareCanvas()
{
var context = document.getElementById('canvas').getContext("2d");
drawgrid(context);
}
How would I mouseover the individual squares and highlight the square that the mouse is hovering over. Like make a red box highlighting the grid square the mouse is over.
See code below. Adjust coordinates for event
<body>
<canvas id=canvas height=400 width=600
onmousemove="over()" style="cursor:crosshair">
</canvas>
</body>
<script type="text/javascript">
<!--
var grid;
function prepareCanvasGrid()
{
var cnv = document.getElementById('canvas');
grid = new CanvasGrid(cnv.getContext("2d"),cnv.offsetLeft, cnv.offsetTop);
}
function CanvasGrid(context,x,y) {
this.sq = [];
this.dirty = [];
this.ctx = context;
this.x = x;
this.y = y;
this.init = function(){
for(var x = 0.5; x < 600; x += 50) {
for(var y = 0.5; y < 400; y += 50) {
var s = new square(x,y, context);
this.sq.push(s);
}
}
}
this.draw = function(){
this.ctx.clearRect(0,0,600,400);
for(var i=0; i < this.sq.length; i++)
this.sq[i].draw();
}
this.clean = function(){
for(var i=0; i < this.dirty.length; i++)
this.dirty[i].draw();
this.dirty = [];
}
this.over = function(ex,ey){
ex = ex - this.x;
ey = ey - this.y;
for(var i=0; i < this.sq.length; i++) {
if(this.sq[i].eleAtPoint(ex,ey)){
this.clean(); // clean up
this.dirty.push(this.sq[i]);
this.sq[i].over();
break;
}
}
}
this.init();
this.draw();
}
function square(x,y, ctx){
this.ctx = ctx;
this.x = x;
this.y = y;
this.h = 50;
this.w = 50;
this.draw = function(){
this.ctx.strokeStyle = "#eee";
this.ctx.strokeRect(this.x, this.y, this.w, this.w);
this.ctx.fillStyle = "#fff";
this.ctx.fillRect(this.x, this.y, this.w, this.w);
}
this.over = function() {
this.ctx.fillStyle = "red";
this.ctx.fillRect(this.x, this.y, this.w, this.w);
}
this.eleAtPoint = function(ex,ey){
if(ex < this.x + this.w && ex > this.x
&& ey > this.y && ey < this.y + this.h)
return true;
return false;
}
}
function over(){
var e = window.event;
grid.over(e.clientX ,e.clientY);
}
prepareCanvasGrid();
//-->
</script>
Updated code for better performance
Just to add to hungryMind's answer, if you don't want any boxes to remain highlighted when the mouse is no longer over the canvas, add these two things:
1) An else if
this.over = function(ex,ey){
ex = ex - this.x;
ey = ey - this.y;
for(var i=0; i < this.sq.length; i++) {
if(this.sq[i].eleAtPoint(ex,ey)){
this.clean(); // clean up
this.dirty.push(this.sq[i]);
this.sq[i].over();
break;
} else if (!this.sq[i].eleAtPoint(ex,ey)) {
this.sq[i].off();
}
}
}
2) A square off() method:
this.off = function() {
this.draw();
}