I'm trying to drawn a rect on canvas, but I want that canvas has lightly transparent background, but that drawn rect has no background.
What I will is something as follows:
I have code as follows:
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
function init() {
img.addEventListener('load', function(){
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas(){
if(update){
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
function drawRect(){
ctx.setTransform(1,0,0,1,rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.beginPath();
ctx.rect(-rect.w/2, -rect.h/2, rect.w, rect.h);
/* ctx.fill(); */
ctx.stroke();
}
// clears canvas sets filters and draws rectangles
function drawCanvas(){
// restore the default transform as rectangle rendering does not restore the transform.
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX : e.offsetX,
startY : e.offsetY,
w : 1,
h : 1,
rotate : 0,
};
drag = true;
}
function mouseUp() { drag = false; buttons_shown = true; update = true; }
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
update = true;
}
}
init();
.hide{
display: none !important;
}
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display:inline-block;
background:rgba(0,0,0,0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg"/>
<canvas id="canvas"></canvas>
</div>
<div id="buttons" class="hide"></div>
In my example I set the background of the canvas to what I will but I cannot remove that background for the drawn rect, it has the same color as the canvas.
Here is the fiddle.
Any idea how to solve it?
This could be achieved in several ways (compositing, clip-path...) but the easiest for such a simple path is probably to use the "evenodd" fill-rule parameter of fill() method which will allow us to draw this rectangle with a hole.
The process is simply to draw a first rect the size of the canvas, then, in the same path declaration, draw your own smaller rectangle. The fill-rule will then exclude this smaller inner rectangle from the bigger one.
function drawRect() {
ctx.beginPath(); // a single path
// the big rectangle, covering the whole canvas
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
// your smaller, inner rectangle
ctx.setTransform(1, 0, 0, 1, rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.rect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
// set the fill-rule to evenodd
ctx.fill('evenodd');
// stroke
// start a new Path declaration
ctx.beginPath
// redraw only the small rect
ctx.rect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
ctx.stroke();
}
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
function init() {
img.addEventListener('load', function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
// set our context's styles here
ctx.fillStyle = 'rgba(0,0,0,.5)';
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas() {
if (update) {
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
// clears canvas sets filters and draws rectangles
function drawCanvas() {
// restore the default transform as rectangle rendering does not restore the transform.
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX: e.offsetX,
startY: e.offsetY,
w: 1,
h: 1,
rotate: 0,
};
drag = true;
}
function mouseUp() {
drag = false;
buttons_shown = true;
update = true;
}
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
update = true;
}
}
init();
.hide {
display: none !important;
}
canvas {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: inline-block;
background: rgba(0, 0, 0, 0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg" />
<canvas id="canvas"></canvas>
</div>
<div id="buttons" class="hide"></div>
Try filling your rect before calling ctx.stroke(), like this:
ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
ctx.fill();
This will produce similar effect to what you have shown in your question. Now the inside of rectangle has both css and fillStyle effect, so it's not ideal - for even better effect you would have to fill outside of rect with desired style instead of setting background in css.
first, draw a full canvas with semi-transparent background, like
ctx.fillStyle = 'rgba(32, 32, 32, 0.7)';
ctx.fillRect(0, 0, width, height);
Then just clear your rectangular form this canvas
ctx.clearRect(x, y, mini_width, mini_height);
Related
I have a mouse image and a cheese image with line in-between. Works fine. Does nothing except look good.
I added a ball that follows the user mouse and it cleared all my images. I tried to add layers(I am a total newbie).
I want the mouse user to be able to lead just the ball on top of the mouse image to the cheese. After I get that working, I will have the cheese turn into a separate image.
The mouse user can lead the ball anywhere. It does not have to just be on the line in-between the two images.
<!DOCTYPE html>
<title>Mouse Event</title>
<style>
canvas {
border: #333 10px solid;
}
</style>
<div style = "position: relative;">
<canvas id = "layer1" width="600px" height="600px"
style="position: absolute; left: 0; top: 0; z-index: 0;></canvas>
<canvas id = "layer2" width="600px" height="600px"
style="position: absolute; left: 0; top: 0; z-index: 1;></canvas>
<script>
var canvas1 = document.querySelector("#layer1");
var context = canvas1.getContext("2d");
//Get the mouse position
//First, listen for the mouse event & call setMousePosition
//This function assigns the current horizontal and vertical mouse
//position to the mouseX,Y properties, it relies on the clientX
//and Y properties that the MouseEvent-based event arument object provides
var canvasPos = getPosition(canvas);
var mouseX = 500;
var mouseY = 500;
canvas1.addEventListener("mousemove", setMousePosition, false);
function setMousePosition(e) {
mouseX = e.clientX - canvasPos.x;//now stores the position returned by the getPosition function
mouseY = e.clientY - canvasPos.y;
}
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);//clears earlier positions
context.beginPath();
context.arc(mouseX, mouseY, 50, 0, 2 * Math.PI, true);
context.fillStyle = "LightSeaGreen";
context.lineWidth = 5;
context.strokeStyle = "yellow";
context.fill();
context.stroke();
requestAnimationFrame(update);
}
//Get the Exact Mouse Position
function getPosition(el) {
var xPosition = 0;
var yPosition = 0;
while (el) {
xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft);
yPosition += (el.offsetTop - el.scrollTop + el.clientTop);
el = el.offsetParent;
}
return {
x: xPosition,
y: yPosition
};
}
update();
var canvas2 = document.querySelector("#layer2");
var context = canvas2.getContext("2d");
//draw connecting line
context.moveTo(30,30);
context.bezierCurveTo(-50,600, 500, 0, 300,500);
context.lineWidth = 15;
context.strokeStyle = "teal";
context.stroke();
context.fillStyle = "#ff6600";
context.font = "bold 35px 'Book Antiqua'";
context.fillText("Help Me", 300,100);
context.fillText("find the cheese,", 300, 135);
context.fillText("Please!", 300, 170);
//load cheeseImage
var cheeseImage = new Image();
cheeseImage.src = "images/transparentCheese.png";
cheeseImage.addEventListener("load", loadImage, false);
//load mouse image
var mouseImage = new Image();
mouseImage.src = "images/mouse.png";
mouseImage.addEventListener("load", loadImage, false);
function loadImage(e) {
context.drawImage(cheeseImage,10,10);
context.drawImage(mouseImage,210,400);
}
</script>
</div>
[mouse][1]cheese
The images are gone because the update function clears the canvas each frame of the animation (context.clearRect), but you want the canvas to clear so don't delete that.
You need to put all the code to draw the things you would like on the canvas inside the update function, or functions the update code calls.
Also its best to wait until both images have loaded before calling the update() function for the first time.
I have modified your code and added some comments, hopefully it makes sense...
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);//clears earlier positions
context.beginPath();
context.arc(mouseX, mouseY, 50, 0, 2 * Math.PI, true);
context.fillStyle = "LightSeaGreen";
context.lineWidth = 5;
context.strokeStyle = "yellow";
context.fill();
context.stroke();
// Call functions to draw text and images on the canvas.
drawText();
drawCheese();
drawMouse();
requestAnimationFrame(update);
}
//Get the Exact Mouse Position
function getPosition(el) {
var xPosition = 0;
var yPosition = 0;
while (el) {
xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft);
yPosition += (el.offsetTop - el.scrollTop + el.clientTop);
el = el.offsetParent;
}
return {
x: xPosition,
y: yPosition
};
}
var canvas2 = document.querySelector("#layer2");
var context = canvas2.getContext("2d");
function drawText() {
//draw connecting line
context.moveTo(30,30);
context.bezierCurveTo(-50,600, 500, 0, 300,500);
context.lineWidth = 15;
context.strokeStyle = "teal";
context.stroke();
context.fillStyle = "#ff6600";
context.font = "bold 35px 'Book Antiqua'";
context.fillText("Help Me", 300,100);
context.fillText("find the cheese,", 300, 135);
context.fillText("Please!", 300, 170);
}
// Draw cheese image on canvas
function drawCheese() {
context.drawImage(cheeseImage,10,10);
}
// Draw mouse image on canvas.
function drawMouse() {
context.drawImage(mouseImage,210,400);
}
//load cheeseImage
var cheeseImage = new Image();
cheeseImage.src = "images/transparentCheese.png";
cheeseImage.addEventListener("load", loadImage, false);
//load mouse image
var mouseImage = new Image();
mouseImage.src = "images/mouse.png";
mouseImage.addEventListener("load", loadImage, false);
var imagesLoaded = 0;
// Called when image is loaded.
function loadImage(e) {
// Increment the number of images loaded
imagesLoaded += 1;
// If both images have loaded call the update function for the first time.
if (imagesLoaded == 2) {
update();
}
}
I am in the process of learning javascript for game design, and wanted to make a separate achievement page that the user can navigate to that will allow them to check their achievements on various games. (at the moment I am not concerned with implementing localstorage/cookies etc, I just want to work on the page for now)
So the requirements of my base idea is as follows:
Able to drag around the viewport/page to view all the achievement categories as they will likely not all be in view on smaller screens
Able to click on a category to open a small box containing all achievements belonging to that game/category
Able to mouse over all achievements in the boxes to get text descriptions of what they are
OPTIONAL: have lines connecting each box on the "overworld" to show users where nearby boxes are if they are off screen
At first, I thought I would need canvas to be able to do this. I learned a bit about it and got decently far until I realized that canvas has a lot of restrictions like not being able to do mouseover events unless manually implementing each one. Here is the current progress I was at in doing a test-run of learning canvas, but it's not very far:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function resize()
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(panning.offset.x, panning.offset.y);
draw();
}
window.addEventListener("resize", resize);
var global = {
scale : 1,
offset : {
x : 0,
y : 0,
},
};
var panning = {
start : {
x : null,
y : null,
},
offset : {
x : 0,
y : 0,
},
};
var canvasCenterWidth = (canvas.width / 2);
var canvasCenterHeight = (canvas.height / 2);
function draw() {
ctx.beginPath();
ctx.rect(canvasCenterWidth, canvasCenterHeight, 100, 100);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.beginPath();
ctx.arc(350, 250, 50, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
}
draw();
canvas.addEventListener("mousedown", startPan);
function pan() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(panning.offset.x, panning.offset.y);
draw();
}
function startPan(e) {
window.addEventListener("mousemove", trackMouse);
window.addEventListener("mousemove", pan);
window.addEventListener("mouseup", endPan);
panning.start.x = e.clientX;
panning.start.y = e.clientY;
}
function endPan(e) {
window.removeEventListener("mousemove", trackMouse);
window.removeEventListener("mousemove", pan);
window.removeEventListener("mouseup", endPan);
panning.start.x = null;
panning.start.y = null;
global.offset.x = panning.offset.x;
global.offset.y = panning.offset.y;
}
function trackMouse(e) {
var offsetX = e.clientX - panning.start.x;
var offsetY = e.clientY - panning.start.y;
panning.offset.x = global.offset.x + offsetX;
panning.offset.y = global.offset.y + offsetY;
}
body{
overflow: hidden;
}
canvas {
top: 0;
left: 0;
position: absolute; }
<canvas id="canvas"></canvas>
So I guess my question is now: what is the best way to implement this? Is it feasable to do it with canvas, or should I just scrap that and try to figure out something with div movement? Should I be concerned with performance issues and should that affect how I implement it?
I'm having some issues with an effect I'm trying to achieve using canvas and the CSS blur filter.
Essentially I need a canvas that is 100% of the height and width of the window, which can be erased to show elements sitting beneath. I am using the blur so the shapes/drawing looks blurred (this is needed).
My issue is that despite oversizing the canvas, there is still a transparent edge around the corners, which shows the elements beneath (i.e. the body with a blue background). I have tried multiple negative margin/overflow hacks, but can't seem to get around it?
I need this canvas to be the full width of the screen, blurred, and not crop in at all, but maybe this is just how CSS filters render, only what is visible?
(function() {
// make canvas larger than window
var largeWidth = window.innerWidth * 3;
var largeHeight = window.innerHeight * 3;
function createCanvas(parent, width, height) {
var canvas = {};
canvas.node = document.createElement('canvas');
canvas.context = canvas.node.getContext('2d');
canvas.node.width = largeWidth;
canvas.node.height = largeHeight;
parent.appendChild(canvas.node);
return canvas;
}
function init(container, width, height, fillColor) {
var canvas = createCanvas(container, 3000, 3000);
var ctx = canvas.context;
ctx.fillCircle = function(x, y, radius, fillColor) {
this.fillStyle = fillColor;
this.beginPath();
this.moveTo(x, y);
this.arc(x, y, radius, 0, Math.PI * 2, false);
this.fill();
};
ctx.clearTo = function(fillColor) {
ctx.fillStyle = fillColor;
ctx.fillRect(0, 0, width, height);
};
ctx.clearTo(fillColor || "#ddd");
canvas.node.onmousemove = function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
var radius = 100; // or whatever
var fillColor = '#ff0000';
ctx.globalCompositeOperation = 'destination-out';
ctx.fillCircle(x, y, radius, fillColor);
};
canvas.node.onmousedown = function(e) {
canvas.isDrawing = true;
};
}
var container = document.getElementById('canvas');
init(container, largeWidth, largeHeight, '#fff');
})();
/* CSS: */
body {
background: blue;
}
#canvas {
z-index: 1;
top: 0;
left: 0;
position: fixed;
filter: blur(10px);
-webkit-filter: blur(10px);
}
<div id = "canvas"></div>
Please see my fiddle
Thanks
Solution 1 - AKA "Ready Today"
Don't blur your canvas but blur what you put inside it; ex:
// After filling circle
context.shadowColor = color;
context.shadowBlur = 16;
context.stroke();
Here is a fiddle
Solution 2 - AKA "Tomorrow .. maybe"
Webkit is working on such feature. But I'm not sure it will work as you intend with canvas, maybe it will considere the whole canvas as "blur mask" so it will blur underneath event if content inside canvas is erased. Here is the feature :
backdrop-filter: blur(10px);
Here is some doc
ps :
I'm not sure it's necessary to wrap canvas as you did .. juste create one and edit its properties directly !
Ouch ! if you make element size 3 times bigger but don't set offset it will only be able overflow on right and bottom sides
You can remove the filter from the canvas and use a gradient brush to achieve the same.
(function() {
// make canvas larger than window
var largeWidth = window.innerWidth * 3;
var largeHeight = window.innerHeight * 3;
function createCanvas(parent, width, height) {
var canvas = {};
canvas.node = document.createElement('canvas');
canvas.context = canvas.node.getContext('2d');
canvas.node.width = largeWidth;
canvas.node.height = largeHeight;
parent.appendChild(canvas.node);
return canvas;
}
function init(container, width, height, fillColor) {
var canvas = createCanvas(container, 3000, 3000);
var ctx = canvas.context;
ctx.fillCircle = function(x, y, radius, fillColor) {
this.fillStyle = fillColor;
this.beginPath();
this.moveTo(x, y);
this.arc(x, y, radius, 0, Math.PI * 2, false);
this.fill();
};
ctx.clearTo = function(fillColor) {
ctx.fillStyle = fillColor;
ctx.fillRect(0, 0, width, height);
};
ctx.clearTo(fillColor || "#ddd");
canvas.node.onmousemove = function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
var radius = 100; // or whatever
var fillColor = '#ff0000';
var radgrad = ctx.createRadialGradient(x,y,0,x,y,radius);
radgrad.addColorStop(0, 'rgba(255,0,0,1)');
radgrad.addColorStop(0.6, 'rgba(228,0,0,.6)');
radgrad.addColorStop(1, 'rgba(228,0,0,0)');
ctx.globalCompositeOperation = 'destination-out';
ctx.fillCircle(x, y, radius, radgrad);
};
canvas.node.onmousedown = function(e) {
canvas.isDrawing = true;
};
}
var container = document.getElementById('canvas');
init(container, largeWidth, largeHeight, '#fff');
})();
/* CSS: */
body {
background: blue;
}
#canvas {
z-index: 1;
top: 0;
left: 0;
position: fixed;
}
<div id = "canvas"></div>
you should change your css as below
(function() {
var largeWidth = ( window.innerWidth * 3)+30;
var largeHeight = (window.innerHeight * 3)+30;
function createCanvas(parent, width, height) {
var canvas = {};
canvas.node = document.createElement('canvas');
canvas.context = canvas.node.getContext('2d');
canvas.node.width = largeWidth;
canvas.node.height = largeHeight;
parent.appendChild(canvas.node);
var overlay = document.getElementById("overlay");
overlay.style.width=(largeWidth -30) +"px";
overlay.style.height =( largeHeight-30)+"px";
return canvas;
}
function init(container, width, height, fillColor) {
var canvas = createCanvas(container, 3000, 3000);
var ctx = canvas.context;
ctx.fillCircle = function(x, y, radius, fillColor) {
this.fillStyle = fillColor;
this.beginPath();
this.moveTo(x, y);
this.arc(x, y, radius, 0, Math.PI * 2, false);
this.fill();
};
ctx.clearTo = function(fillColor) {
ctx.fillStyle = fillColor;
ctx.fillRect(0, 0, width, height);
};
ctx.clearTo(fillColor || "#ddd");
canvas.node.onmousemove = function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
var radius = 100; // or whatever
var fillColor = '#ff0000';
ctx.globalCompositeOperation = 'destination-out';
ctx.fillCircle(x, y, radius, fillColor);
};
canvas.node.onmousedown = function(e) {
canvas.isDrawing = true;
};
}
var container = document.getElementById('canvas');
init(container, largeWidth, largeHeight, '#fff');
})();
body {
background: blue;
}
#canvas {
margin: -15px;
filter: blur(10px);
-webkit-filter: blur(10px);
position: relative;
}
#overlay {
overflow:hidden;
position: fixed;
top: 0px;
left: 0px;
z-index: 1;
filter: unset;
-webkit-filter: unset;
}
<div id="overlay">
<div id="canvas">
</div>
</div>
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
I made a small program that:
changes the mouse cursor inside the canvas to a black square
gives the black square a nice trail that fades away over time (the point of the program)
Here's the code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.style.cursor = 'none'; // remove regular cursor inside canvas
function getMousePos(canvas, e) {
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function fadeCanvas() {
ctx.save();
ctx.globalAlpha = 0.1; // the opacity (i.e. fade) being applied to the canvas on each function re-run
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, canvas.width, canvas.height); // area being faded (whole canvas)
ctx.restore();
requestAnimationFrame(fadeCanvas); // animate at 60 fps
}
fadeCanvas();
function draw(e) {
var pos = getMousePos(canvas, e);
ctx.fillStyle = "black";
ctx.fillRect(pos.x, pos.y, 8, 8); // the new cursor
}
addEventListener('mousemove', draw, false);
Here's a live example: https://jsfiddle.net/L6j71crw/2/
Problem
However the trail does not fade away completely, and leaves a ghosting trail.
Q: How can I remove the ghosting trail?
I have tried using clearRect() in different ways, but it just clears the entire animation leaving nothing to display. At best it just removes the trail and only fades the square cursor alone, but it still doesn't make the cursor completely transparent when the fading process is completed. I have tried finding posts about it, but I found nothing that gave a definitive answer and—most importantly—no posts with a working example.
Any ideas?
Try having a list of positions, this won't leave a ghost trail!
my code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var Positions = [];
var maxlength = 20;
canvas.style.cursor = 'none'; // remove regular cursor inside canvas
var V2 = function(x, y){this.x = x; this.y = y;};
function getMousePos(canvas, e) {
// ctx.clearRect(0, 0, canvas.width, canvas.height);
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function fadeCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var e = 0; e != Positions.length; e++)
{
ctx.fillStyle = ctx.fillStyle = "rgba(0, 0, 0, " + 1 / e + ")";
ctx.fillRect(Positions[e].x, Positions[e].y, 8, 8);
}
if(Positions.length > 1)
Positions.pop()
//ctx.save();
//ctx.globalAlpha = 0.5; // the opacity (i.e. fade) being applied to the canvas on each function re-run
//ctx.fillStyle = "#fff";
//ctx.fillRect(0, 0, canvas.width, canvas.height); // area being faded (whole canvas)
//ctx.restore();
requestAnimationFrame(fadeCanvas); // animate at 60 fps
}
fadeCanvas();
function draw(e) {
var pos = getMousePos(canvas, e);
Positions.unshift(new V2(pos.x, pos.y));
if(Positions.length > maxlength)
Positions.pop()
//ctx.fillStyle = "black";
//ctx.fillRect(pos.x, pos.y, 8, 8); // the new cursor
}
addEventListener('mousemove', draw, false);
JSFiddle: https://jsfiddle.net/L6j71crw/9/
Edit: made the cursor constant.