简体   繁体   中英

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...

  1. Draw the image on the canvas.
  2. Draw the spotlight on the canvas.
  3. 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM