最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - HTML canvas spotlight effect - Stack Overflow

programmeradmin6浏览0评论

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=".1.1/jquery.min.js"></script>
<div>Test</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./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 mented-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.

Share Improve this question edited Sep 8, 2015 at 7:51 Snackaholic asked Sep 7, 2015 at 15:01 SnackaholicSnackaholic 6109 silver badges28 bronze badges 3
  • 2 Did you read stackoverflow./questions/2142535/…? – ci_ Commented Sep 7, 2015 at 15:07
  • 1 Several problems here. We cannot reproduce your example, for example giveCanvasContext isn't a function naively. Note for filling the canvas with a black color you don't need beginPath or endPath. – Spencer Wieczorek Commented Sep 7, 2015 at 15:10
  • @ ci_ yes i did, but it seems like the canvas will be drawn white instead of filled up with black again.. – Snackaholic Commented Sep 8, 2015 at 6:30
Add a ment  | 

3 Answers 3

Reset to default 4

You can use positing to create your flashlight effect:

  • Clear the canvas
  • Create a radial gradient to use as a reveal.
  • Fill the radial gradient.
  • Use source-atop positing to draw the background image. The image will display only inside the radial gradient.
  • Use destination-over positing 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./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./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./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./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 positing 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 positing, 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./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

发布评论

评论列表(0)

  1. 暂无评论