HTML5 Canvas positioning mouse clicking broken - javascript

I have one canvas element that when positioned with css, fails to register mouse clicks and move elements drawn within canvas. If you do not position the canvas the mouse clicks register and selected elements will get moved but I need to be able to position this canvas layer with left and top so I can put another canvas underneath yet retain my mouse click and move ability.
This is what I am using to get my x/y click coordinates
var br = canvas.getBoundingClientRect(); //bounding rectangle
var mousex = parseInt(e.clientX-br.left);
var mousey = parseInt(e.clientY-br.top);
I have changed clientX and clientY to use pageX and pageY as others have suggested but the issue still remains, even if I place the canvas in another div and position accordingly. I've also subtracted the amount I'm moving the container over from mousex but without success. Even positioning with flex breaks the functionality.
Is this simply a limitation of canvas?
** Clarification, if it wasn't clear before, as detecting the mouse click is NOT the issue, the issue is when I position the canvas inside the DOM, the clicks are no longer registered. **

You can do it using offsetLeft and offsetTop. Here is a demo:
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
targetPos = {x: 150, y: 50};
// Initial render
render();
// Whenever the user clicks on the canvas, update the position
canvas.addEventListener('click', updateTargetPos);
function updateTargetPos(e) {
targetPos = {
x: e.pageX - canvas.offsetLeft,
y: e.pageY - canvas.offsetTop
};
render();
}
function render() {
renderBackground();
renderTarget();
}
function renderBackground() {
ctx.fillStyle = "#333";
ctx.fillRect(0, 0, 300, 200);
}
function renderTarget() {
var size = 10;
ctx.fillStyle = "#f00";
ctx.fillRect(targetPos.x - size/2, targetPos.y - size/2, size, size);
}
body {
font-family: Arial, Helvetica, sans-serif;
}
#canvas {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -150px;
}
<p>Click on this absolutely positioned canvas:</p>
<canvas id="canvas" width="300" height="100"></canvas>

I was editing this in devtools on the fly which was causing issues with canvas and being able to get the correct x/y of the mouse click. When I would adjust positioning in dev tools then attempt to move the square it would not work. Modifying my stylesheet prior to loading into the dom in this instance yielded the correct result as the calculations would need to be run again after manipulating the canvas positioning.
For those that needed a visual, here is some code. To replicate the problem you can try to add justify-content: center to the .flex class then attempt to move the green square to see that you can't move the square, or rather the spot that is defined to be clickable is not located over the box.
var canvas = document.getElementById("canvasArea");
var ctx = canvas.getContext("2d");
var canvas2 = document.getElementById("canvas2");
var ctx2 = canvas2.getContext("2d");
var x = canvas.width/2;
var y = canvas.height-10;
var dx = 2;
var dy = -2;
var boundingRectangle = canvas.getBoundingClientRect();
var moveBox = false;
var selectedBox;
function box(x, y) {
this.color = "green";
this.xPos = x;
this.yPos = y;
this.width = 50;
this.height = 50;
}
box.prototype.drawBox = function() {
ctx.fillStyle = this.color;
ctx.fillRect(this.xPos, this.yPos, this.width, this.height);
};
var abox = new box(x, 30);
canvas.addEventListener("mousedown", function(e) {
e.preventDefault();
e.stopPropagation();
var mousex = parseInt(e.clientX-boundingRectangle.left);
var mousey = parseInt(e.clientY-boundingRectangle.top);
if(mousex > abox.xPos && mousex < abox.xPos + abox.width && mousey > abox.yPos && mousey < abox.yPos + abox.height) {
moveBox = true;
selectedBox = "abox"
}
}, true);
canvas.addEventListener("mousemove", function(e) {
e.preventDefault();
e.stopPropagation();
if(moveBox) {
if(selectedBox == "abox") {
abox.xPos = e.offsetX;
abox.yPos = e.offsetY;
}
}
})
canvas.addEventListener("mouseup", function(e) {
e.preventDefault();
e.stopPropagation();
moveBox = false;
})
function bg(ctx2) {
var alpha = ctx2.globalAlpha;
ctx2.save();
ctx2.beginPath();
ctx2.moveTo(142.5, 23.7);
ctx2.lineTo(85.9, 0.0);
ctx2.lineTo(0.0, 204.8);
ctx2.lineTo(56.6, 228.5);
ctx2.lineTo(142.5, 23.7);
ctx2.closePath();
ctx2.fillStyle = "rgb(31, 155, 215)";
ctx2.fill();
ctx2.beginPath();
ctx2.moveTo(235.1, 23.7);
ctx2.lineTo(178.5, 0.0);
ctx2.lineTo(92.6, 204.8);
ctx2.lineTo(149.2, 228.5);
ctx2.lineTo(235.1, 23.7);
ctx2.closePath();
ctx2.fillStyle = "rgb(77, 75, 159)";
ctx2.fill();
ctx2.beginPath();
ctx2.moveTo(330.5, 23.7);
ctx2.lineTo(273.9, 0.0);
ctx2.lineTo(188.0, 204.8);
ctx2.lineTo(244.6, 228.5);
ctx2.lineTo(330.5, 23.7);
ctx2.closePath();
ctx2.fillStyle = "rgb(176, 67, 152)";
ctx2.fill();
ctx2.beginPath();
ctx2.moveTo(435.4, 23.7);
ctx2.lineTo(378.8, 0.0);
ctx2.lineTo(292.9, 204.8);
ctx2.lineTo(349.5, 228.5);
ctx2.lineTo(435.4, 23.7);
ctx2.closePath();
ctx2.fillStyle = "rgb(69, 174, 77)";
ctx2.fill();
ctx2.beginPath();
ctx2.moveTo(541.4, 23.7);
ctx2.lineTo(484.7, 0.0);
ctx2.lineTo(398.9, 204.8);
ctx2.lineTo(455.5, 228.5);
ctx2.lineTo(541.4, 23.7);
ctx2.closePath();
ctx2.fillStyle = "rgb(237, 127, 34)";
ctx2.fill();
ctx2.restore();
}
function render() {
requestAnimationFrame(render);
ctx.clearRect(0, 0, canvas.width, canvas.height);
abox.drawBox();
}
render();
bg(ctx2)
.flex {
width: 100%;
height: 100%;
display: flex;
}
canvas#canvasArea {
position: absolute;
display: inline-block;
}
.container {
position: relative;
left: 150px;
top: -20px;
z-index: 999999;
}
.container {
position: relative;
}
.canvasbg {
position: absolute;
width: 200px;
height: 150px;
margin: 20px 0;
border: none;
background: #000000;
}
<div class="flex">
<div class="container">
<div class="canvasbg">
<canvas id="canvasArea" width="220" height="150">
Your browser does not support HTML5 canvas.
</canvas>
</div>
</div>
<canvas id="canvas2" width="540" height="177"></canvas>

Related

How to show a rectangle while drawing it on mousemove html canvas

I'm trying to let users draw rectangles on a canvas using the mouse and i've been able to get it to work to some extent.The users can draw the rectangles using the mouse but it only shows after the mouseup event but i also want the users to also see the rectangles while drawing it on mousemove event. How can i achieve this and at the same time let users draw multiple rectangles which currently works but i want them to see it while drawing the rectangles.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
#container {
/*background-color: lime;*/
width: 150px;
height: 150px;
cursor: pointer;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
#heatmapContainer {
border: 1px solid red;
}
</style>
</head>
<body>
<div class="heatmapWrapper">
<div id="heatmapContainer" style="height: 4205px; width: 1278px">
<div id="heatmap1" class="heatmapTile" style="height: 4205px; position: relative">
<canvas id="myCanvas" class="heatmap-canvas" width="1278" height="4205" style="position: absolute; left: 0px; top: 0px"></canvas>
</div>
</div>
</div>
<script>
var canvas = document.getElementById('myCanvas'),
elemLeft = canvas.offsetLeft,
elemTop = canvas.offsetTop,
context = canvas.getContext('2d');
let start = {};
var mouseDown = false;
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect(),
scaleX = canvas.width / rect.width,
scaleY = canvas.height / rect.height;
return {
x: (evt.clientX - rect.left) * scaleX,
y: (evt.clientY - rect.top) * scaleY,
};
}
function startRect(e) {
start = getMousePos(canvas, e);
mouseDown = true;
}
window.addEventListener('mousedown', startRect);
function endRect(e) {
let {
x,
y
} = getMousePos(canvas, e);
context.strokeStyle = 'blue';
context.strokeRect(start.x, start.y, x - start.x, y - start.y);
mouseDown = false;
}
function drawSquare(e) {
if (!mouseDown) return;
// creating a square
var width = Math.abs(start.x - canvas.getBoundingClientRect().offsetLeft);
var height = Math.abs(start.y - canvas.getBoundingClientRect().offsetLeft);
context.beginPath();
context.rect(start.x, start.y, width, height);
context.strokeStyle = 'blue';
context.stroke();
}
window.addEventListener('mouseup', endRect);
window.addEventListener('mousemove', drawSquare);
/**End Drawing a rectangle on the canvas **/
</script>
</body>
</html>
We need to make something different, instead of drawing on the fly we need to add the rectangles to an array, then clean the canvas and draw them on demand, that way we can also draw additional elements, on the sample below I draw a red rectangle while moving and blue ones once we release the mouse
var canvas = document.getElementById('myCanvas')
var context = canvas.getContext('2d')
let start = {}
let rects = [{x: 9, y: 9, width: 50, height: 30}]
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect()
return {
x: (evt.clientX - rect.left) * canvas.width / rect.width,
y: (evt.clientY - rect.top) * canvas.height / rect.height,
};
}
function startRect(e) {
start = getMousePos(canvas, e);
}
function endRect(e) {
let { x, y } = getMousePos(canvas, e)
rects.push({x: start.x, y: start.y, width: x - start.x, height: y - start.y})
start = {}
draw()
}
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height)
rects.forEach(rect => {
context.beginPath();
context.rect(rect.x, rect.y, rect.width, rect.height);
context.strokeStyle = 'blue';
context.stroke();
});
}
function drawMove(e) {
if (start.x) {
draw()
let { x, y } = getMousePos(canvas, e)
context.beginPath();
context.rect(start.x, start.y, x - start.x, y - start.y);
context.strokeStyle = 'red';
context.stroke();
context.beginPath();
context.arc(start.x, start.y, 5, 0, 2 * Math.PI);
context.fill();
context.beginPath();
context.arc(x, y, 5, 0, 2 * Math.PI);
context.fill();
}
}
draw()
window.addEventListener('mouseup', endRect)
window.addEventListener('mousedown', startRect)
window.addEventListener('mousemove', drawMove)
canvas { border: solid }
<canvas id="myCanvas" width="400" height="400"></canvas>

Canvas: create semi-transparent overlay over entire canvas except one window

I have a rectangular canvas with an image painted on it. As the user moves the cursor over the canvas, I'd like to make the canvas semitransparent except for a small rectangle around the cursor, which I'd like to retain the underlying image content in full opacity.
So my question in brief is how to "mute" the canvas except for a small rectangle.
My first thought was to create a semitransparent overlay over the entire canvas, then just paint the rectangular region that should be full opacity again, but this makes the background disappear while I want to retain it at 0.2 opacity:
var elem = document.querySelector('canvas');
var ctx = elem.getContext('2d');
elem.width = 400;
elem.height = 300;
ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, elem.width, elem.height);
elem.addEventListener('mousemove', function(e) {
ctx.globalAlpha = 0.2;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, elem.width, elem.height);
var x = e.clientX;
var y = e.clientY;
var d = 30;
ctx.fillStyle = '#ff0000';
ctx.fillRect(x-d, y-d, d*2, d*2);
})
<canvas/>
Does anyone know the most performant way to mute the background to 0.2 opacity while retaining the rectangle around the cursor at full opacity? Any pointers would be super helpful!
Here's the two canvas method:
var elemA = document.querySelector('#a');
var elemB = document.querySelector('#b');
var ctx = elemA.getContext('2d');
var bctx = elemB.getContext('2d');
elemA.width = elemB.width = 400;
elemA.height = elemB.height = 300;
ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, elemA.width, elemA.height);
elemB.addEventListener('mousemove', function(e) {
bctx.clearRect(0, 0, elemB.width, elemB.height);
var x = e.clientX;
var y = e.clientY;
var x0 = x-10;
var x1 = x+10;
var y0 = y-10;
var y1 = y+10;
// draw boxes; origin is top left
bctx.globalAlpha = 0.8;
bctx.fillStyle = '#ffffff';
bctx.fillRect(0, 0, elemA.width, y0); // top
bctx.fillRect(0, y0, x0, elemB.height+20); // left
bctx.fillRect(x0, y1, elemB.width+20, elemB.height); // bottom
bctx.fillRect(x1, y0, elemA.width, y1-y0); // right
})
* {
margin: 0;
}
#c {
position: relative;
}
#a, #b {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
#b {
z-index: 1;
opacity: 0.5;
}
<div id='c'>
<canvas id='a'></canvas>
<canvas id='b'></canvas>
</div>

Change canvas background color to transparent

I have a text under canvas, i want to show it when i am erasing background color of canvas. Now it is red, when i wrote transparent it does not work. I need to show that text when i draw with the mouse. I tried with rgba too, but is was not working.
please help me if you can
enter code here
var cont = document.getElementById("spots"), // UI elements
canvas = document.getElementById("canvas"),
alpha = document.getElementById("alpha"),
modes = document.getElementById("modes"),
ctx = canvas.getContext("2d"),
isDown = false, // defaults
color = "red";
// set up color palette using a custom "Spot" object
// This will use a callback function when it is clicked, to
// change current color
function Spot(color, cont, callback) {
var div = document.createElement("div");
div.style.cssText = "width:50px;height:50px;border:1px solid #000;margin:0 1px 1px 0;background:" + color;
div.onclick = function() {callback(color)};
cont.appendChild(div);
}
// add some spot colors to our palette container
new Spot(color, cont, setColor);
// this will set current fill style based on spot clicked
function setColor(c) {ctx.fillStyle = c}
// setup defaults
ctx.fillStyle = color;
ctx.globalAlpha = 1;
// events
canvas.onmousedown = function() {isDown = true};
window.onmouseup = function() {isDown = false};
window.onmousemove = function(e) {
if (!isDown) return;
var r = canvas.getBoundingClientRect(),
x = e.clientX - r.left,
y = e.clientY - r.top;
ctx.beginPath();
ctx.arc(x, y, 25, 0, 2*Math.PI);
ctx.fill();
};
.main-content{
position: relative;
width: 300px;
height: 300px;
}
.main-text{
position: absolute;
right: 0;
left: 0;
text-align: center;
z-index: 8;
font-size: 35px;
}
#canvas{
background-color: green;
position: absolute;
z-index: 9;
}
<div class="main-content">
<p class="main-text">You Won!!!</p>
<canvas id="canvas" width=300 height=300 style="border: 1px solid green;"></canvas>
<div id="spots"></div>
</div>
I think you would like to get something like the solution in the snippet below.
var cont = document.getElementById("spots"), // UI elements
canvas = document.getElementById("canvas"),
alpha = document.getElementById("alpha"),
modes = document.getElementById("modes"),
ctx = canvas.getContext("2d"),
isDown = false, // defaults
color = "green";
// set up color palette using a custom "Spot" object
// This will use a callback function when it is clicked, to
// change current color
function Spot(color, cont, callback) {
var div = document.createElement("div");
div.style.cssText = "width:50px;height:50px;border:1px solid #000;margin:0 1px 1px 0;background:" + color;
div.onclick = function() {
callback(color)
};
cont.appendChild(div);
}
// add some spot colors to our palette container
new Spot(color, cont, setColor);
// this will set current fill style based on spot clicked
function setColor(c) {
ctx.fillStyle = c
}
// setup defaults
ctx.fillStyle = color;
ctx.globalAlpha = 1;
// create a rectangle using canvas functions, not CSS
// background color.
const createRect = (ctx, width, height) => {
ctx.fillRect(0, 0, width, height)
}
createRect(ctx, 300, 300)
// events
canvas.onmousedown = function() {
isDown = true
};
window.onmouseup = function() {
isDown = false
};
window.onmousemove = function(e) {
if (!isDown) return;
var r = canvas.getBoundingClientRect(),
x = e.clientX - r.left,
y = e.clientY - r.top;
// you needed a bit more code here:
ctx.fillStyle = "rgba(255, 255, 255, 0.5)"
ctx.save();
ctx.globalCompositeOperation = 'destination-out';
ctx.beginPath();
ctx.arc(x, y, 25, 0, 2 * Math.PI, false);
ctx.fill();
ctx.restore();
};
.main-content {
position: relative;
width: 300px;
height: 300px;
}
.main-text {
position: absolute;
right: 0;
left: 0;
text-align: center;
z-index: 8;
font-size: 35px;
}
#canvas {
/*background-color: green;*/
position: absolute;
z-index: 9;
}
<div class="main-content">
<p class="main-text">You Won!!!</p>
<canvas id="canvas" width=300 height=300 style="border: 1px solid green;"></canvas>
<div id="spots"></div>
</div>
About canvas global compositions: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
You are halfway there, but there are a couple of things to change.
What you're trying to do is to change the appearance of a css property through the canvas, which does not work like that. You also can not alter the transparency of the canvas, however there are solutions to your case, and it is very simple.
You need to apply a background color to your canvas, then remove the pixels by using the exact same drawing function you have, then you set an extra property called globalCompositeOperation, which is basically the "BlendMode" of photoshop.
So here's your updated code:
var cont = document.getElementById("spots"), // UI elements
canvas = document.getElementById("canvas"),
alpha = document.getElementById("alpha"),
modes = document.getElementById("modes"),
ctx = canvas.getContext("2d"),
isDown = false, // defaults
color = "red";
// set up color palette using a custom "Spot" object
// This will use a callback function when it is clicked, to
// change current color
function Spot(color, cont, callback) {
var div = document.createElement("div");
div.style.cssText = "width:50px;height:50px;border:1px solid #000;margin:0 1px 1px 0;background:" + color;
div.onclick = function() {callback(color)};
cont.appendChild(div);
}
// add some spot colors to our palette container
//new Spot(color, cont, setColor);
// this will set current fill style based on spot clicked
function setColor(c) {ctx.fillStyle = c}
// setup defaults
ctx.fillStyle = color;
ctx.globalAlpha = 1;
// draw the background
ctx.fillRect(0, 0, 300, 300);
// add the 'blend mode effect'
ctx.globalCompositeOperation = 'destination-out';
// events
canvas.onmousedown = function() {isDown = true};
window.onmouseup = function() {isDown = false};
window.onmousemove = function(e) {
if (!isDown) return;
var r = canvas.getBoundingClientRect(),
x = e.clientX - r.left,
y = e.clientY - r.top;
ctx.beginPath();
ctx.arc(x, y, 25, 0, 2*Math.PI);
ctx.fill();
};
.main-content{
position: relative;
width: 300px;
height: 300px;
background: blue;
}
.main-text{
position: absolute;
right: 0;
left: 0;
text-align: center;
z-index: 8;
font-size: 35px
}
#canvas{
position: absolute;
z-index: 9;
}
<div class="main-content">
<p class="main-text">You Won!!!</p>
<canvas id="canvas" width=300 height=300 style="border: 1px solid green;"></canvas>
<div id="spots"></div>
</div>
Apart from that tiny change on the JS, I also changed the order of z-index on the CSS. Good luck

javascript, controlling a ball's movement using mouse

I was following this MDN tutorial:
https://developer.mozilla.org/es/docs/Games/Workflows/Famoso_juego_2D_usando_JavaScript_puro/Mueve_la_bola
And I was wondering how could we control the ball's movement using mouse movement event.
I have seen that mousemove works with textarea and some inputs:
https://developer.mozilla.org/en-US/docs/Web/Events/mousemove
I thought if we could put a textarea behind the canvas, we could detect the event, get the mouse's position and let the user change the ball's movement with mouse movement.
I have read:
Dynamically resize textarea width and height to contain text
So I tried:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Juego</title>
<style>
* {
padding: 0;
margin: 0;
}
canvas {
background: #eee;
display: block;
margin: 0 auto;
}
</style>
</head>
<body>
<textarea name="ta" id="ta" cols="30" rows="10"></textarea>
<canvas id="myCanvas" width="480" height="320"></canvas>
<script>
const textarea = document.getElementById('ta');
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
textarea.style.height = canvas.height;
textarea.style.width = canvas.width;
let x = canvas.width / 2;
let y = canvas.height - 30;
const dx = 2;
const dy = -2;
let drawBall = function () {
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
};
function draw(e) {
console.log(e);
ctx.clearRect(0,0,canvas.width,canvas.height);
drawBall();
x += dx;
y += dy;
}
setInterval(draw, 10);
canvas.addEventListener('mousemove', draw);
</script>
</body>
</html>
The expected output would be to have the textarea using canvas' width and height, and being behind it; however it is smaller and put on top left:
Thank you for your help.
you don't need the textarea to capture the event, and it'll be complicated because the canvas will be on top and the textarea will never know the mouse is moving on top of it, and to make the ball moving with the mouse, you have to passe the x and y of the mouse to the draw(); while it's moving function
here's a fiddle : https://jsfiddle.net/vqdyLx5u/22/
let canvas = document.getElementById('myCanvas');
let ctx = canvas.getContext('2d');
function drawBall(x, y) {
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function draw(x, y) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall(x, y);
}
draw(canvas.height / 2 , canvas.width / 2); // initial draw
canvas.addEventListener('mousemove', function(e) {
draw(e.pageX, e.pageY); // the coordinates of the mouse
});
* {
padding: 0;
margin: 0;
}
canvas {
background: #eee;
display: block;
margin: 0 auto;
}
<canvas id="myCanvas" width="480" height="320"></canvas>

HTML canvas spotlight effect

Let's say I have the following code.
// Find out window height and width
wwidth = $(window).width();
wheight = $(window).height();
// Place Canvas over current Window
$("body").append($("<canvas id='test' style='position:absolute; top:0; left:0;'></canvas>"));
var context = document.getElementById("test").getContext("2d");
context.canvas.width = wwidth;
context.canvas.height = wheight;
// Paint the canvas black.
context.fillStyle = '#000';
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
// On Mousemove, create "Flashlight" around the mouse, to see through the canvas
$(window).mousemove(function(event){
x = event.pageX;
y = event.pageY;
radius = 50;
context = document.getElementById("test").getContext("2d");
// Paint the canvas black. Instead it will draw it white?!
//context.fillStyle = '#000';
//context.clearRect(0, 0, context.canvas.width, context.canvas.height);
//context.fillRect(0, 0, context.canvas.width, context.canvas.height);
context.beginPath();
radialGradient = context.createRadialGradient(x, y, 1, x, y, radius);
radialGradient.addColorStop(0, 'rgba(255,255,255,1)');
radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
context.globalCompositeOperation = "destination-out";
context.fillStyle = radialGradient;
context.arc(x, y, radius, 0, Math.PI*2, false);
context.fill();
context.closePath();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>Test</div>
which generates the following effect on mousemove:
How do I refill the canvas with black before the spotlight is drawn? I have already tried with what is in the commented-out code block, but it paints everything white.
EDIT: I dont want this effect over an image. Instead i would like to place the Canvas over the whole Webpage. ALso I want the Canvas to be always black and the mouse generates a Spotlight over its position, to see what is under the Canvas just as u can see in the picture, or in the Snippet where a div was placed in an empty html page with "Test" in it.
You can use compositing to create your flashlight effect:
Clear the canvas
Create a radial gradient to use as a reveal.
Fill the radial gradient.
Use source-atop compositing to draw the background image. The image will display only inside the radial gradient.
Use destination-over compositing to fill the canvas with black. The black will fill "behind" the existing radial-gradient-image.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
$("#canvas").mousemove(function(e){handleMouseMove(e);});
var radius=30;
var img=new Image();
img.onload=function(){
draw(150,150,30);
}
img.src='https://dl.dropboxusercontent.com/u/139992952/multple/annotateMe.jpg'
function draw(cx,cy,radius){
ctx.save();
ctx.clearRect(0,0,cw,ch);
var radialGradient = ctx.createRadialGradient(cx, cy, 1, cx, cy, radius);
radialGradient.addColorStop(0, 'rgba(0,0,0,1)');
radialGradient.addColorStop(.65, 'rgba(0,0,0,1)');
radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
ctx.beginPath();
ctx.arc(cx,cy,radius,0,Math.PI*2);
ctx.fillStyle=radialGradient;
ctx.fill();
ctx.globalCompositeOperation='source-atop';
ctx.drawImage(img,0,0);
ctx.globalCompositeOperation='destination-over';
ctx.fillStyle='black';
ctx.fillRect(0,0,cw,ch);
ctx.restore();
}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
draw(mouseX,mouseY,30);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Move mouse to reveal image with "flashlight"</h4>
<canvas id="canvas" width=300 height=300></canvas>
If your spotlight radius will never change, here's a much faster method:
The speed is gained by caching the spotlight to a second canvas and then...
Draw the image on the canvas.
Draw the spotlight on the canvas.
Use fillRect to black out the 4 rectangles outside the spotlight.
Example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var radius=50;
var cover=document.createElement('canvas');
var cctx=cover.getContext('2d');
var size=radius*2+10;
cover.width=size;
cover.height=size;
cctx.fillRect(0,0,size,size);
var radialGradient = cctx.createRadialGradient(size/2, size/2, 1, size/2, size/2, radius);
radialGradient.addColorStop(0, 'rgba(0,0,0,1)');
radialGradient.addColorStop(.65, 'rgba(0,0,0,1)');
radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
cctx.beginPath();
cctx.arc(size/2,size/2,size/2,0,Math.PI*2);
cctx.fillStyle=radialGradient;
cctx.globalCompositeOperation='destination-out';
cctx.fill();
var img=new Image();
img.onload=function(){
$("#canvas").mousemove(function(e){handleMouseMove(e);});
ctx.fillRect(0,0,cw,ch);
}
img.src='https://dl.dropboxusercontent.com/u/139992952/multple/annotateMe.jpg'
function drawCover(cx,cy){
var s=size/2;
ctx.clearRect(0,0,cw,ch);
ctx.drawImage(img,0,0);
ctx.drawImage(cover,cx-size/2,cy-size/2);
ctx.fillStyle='black';
ctx.fillRect(0,0,cx-s,ch);
ctx.fillRect(0,0,cw,cy-s);
ctx.fillRect(cx+s,0,cw-cx,ch);
ctx.fillRect(0,cy+s,cw,ch-cy);
}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
drawCover(mouseX,mouseY);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Move mouse to reveal image with "flashlight"</h4>
<canvas id="canvas" width=300 height=300></canvas>
You can achieve the spotlight effect by positioning a canvas directly over the image. Make the canvas the same size as the image and set the compositing operation to xor so that two black pixels drawn in the same place cancel each other out.
context.globalCompositeOperation = 'xor';
Now you can paint the canvas black and fill a black circle around the mouse cursor. The result is a hole in the black surface, showing the image underneath.
// Paint the canvas black.
context.fillStyle = '#000';
context.clearRect(0, 0, width, height);
context.fillRect(0, 0, width, height);
// Paint a black circle around x, y.
context.beginPath();
context.arc(x, y, spotlightRadius, 0, 2 * Math.PI);
context.fillStyle = '#000';
context.fill();
// With xor compositing, the result is a circular hole.
To make a spotlight with blurry edges, define a radial gradient centered on the mouse position and fill a square around it.
var gradient = context.createRadialGradient(x, y, 0, x, y, spotlightRadius);
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(0.9, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
context.fillStyle = gradient;
context.fillRect(x - spotlightRadius, y - spotlightRadius,
2 * spotlightRadius, 2 * spotlightRadius);
The following snippet demonstrates both approaches using pure JavaScript. To change from a crisp-edged spotlight to a blurry-edged spotlight, click on the checkbox above the image.
function getOffset(element, ancestor) {
var left = 0,
top = 0;
while (element != ancestor) {
left += element.offsetLeft;
top += element.offsetTop;
element = element.parentNode;
}
return { left: left, top: top };
}
function getMousePosition(event) {
event = event || window.event;
if (event.pageX !== undefined) {
return { x: event.pageX, y: event.pageY };
}
return {
x: event.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft,
y: event.clientY + document.body.scrollTop +
document.documentElement.scrollTop
};
}
window.onload = function () {
var spotlightRadius = 60,
container = document.getElementById('container'),
canvas = document.createElement('canvas'),
image = container.getElementsByTagName('img')[0],
width = canvas.width = image.width,
height = canvas.height = image.height,
context = canvas.getContext('2d');
context.globalCompositeOperation = 'xor';
container.insertBefore(canvas, image.nextSibling);
container.style.width = width + 'px';
container.style.height = height + 'px';
var offset = getOffset(canvas, document.body);
clear = function () {
context.fillStyle = '#000';
context.clearRect(0, 0, width, height);
context.fillRect(0, 0, width, height);
};
clear();
image.style.visibility = 'visible';
canvas.onmouseout = clear;
canvas.onmouseover = canvas.onmousemove = function (event) {
var mouse = getMousePosition(event),
x = mouse.x - offset.left,
y = mouse.y - offset.top;
clear();
if (document.getElementById('blurry').checked) {
var gradient = context.createRadialGradient(x, y, 0, x, y, spotlightRadius);
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(0.875, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
context.fillStyle = gradient;
context.fillRect(x - spotlightRadius, y - spotlightRadius,
2 * spotlightRadius, 2 * spotlightRadius);
} else {
context.beginPath();
context.arc(x, y, spotlightRadius, 0, 2 * Math.PI);
context.fillStyle = '#000';
context.fill();
}
};
};
* {
margin: 0;
padding: 0;
}
.control {
font-family: sans-serif;
font-size: 15px;
padding: 10px;
}
#container {
position: relative;
}
#container img, #container canvas {
position: absolute;
left: 0;
top: 0;
}
#container img {
visibility: hidden;
}
#container canvas {
cursor: none;
}
<p class="control">
<input type="checkbox" id="blurry" /> blurry edges
</p>
<div id="container">
<img src="https://dl.dropboxusercontent.com/u/139992952/multple/annotateMe.jpg" />
</div>
this code works for me:
x = event.pageX;
y = event.pageY;
radius = 10;
context = canvas.getContext("2d");
context.fillStyle = "black";
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
context.beginPath();
var radialGradient= context.createRadialGradient(x,y,1,x,y,radius);
radialGradient.addColorStop(0,"rgba(255,255,255,1");
radialGradient.addColorStop(1,"rgba(0,0,0,1)");
//context.globalCompositeOperation = "destination-out";
context.fillStyle = radialGradient;
context.arc(x, y, radius, 0, Math.PI*2, false);
context.fill();
context.closePath();
it seems that this line was messing it context.globalCompositeOperation = "destination-out";
there were also pointless lines in your code like beginnig path before filling rect and fill() function after filling path

Categories