HTML画布聚光灯效果

时间:2015-09-07 15:01:18

标签: javascript html canvas

我们说我有以下代码。



// 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();
});
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>Test</div>
&#13;
&#13;
&#13;

对mousemove产生以下影响:

Here you can see the above scripted effect

在绘制聚光灯之前,如何用黑色填充画布?我已经尝试过注释掉的代码块中的内容,但它会将所有内容描绘成白色。

编辑:我不想在图像上使用此效果。相反,我想将Canvas放在整个网页上。另外,我希望Canvas始终是黑色的,鼠标会在其位置上生成Spotlight,以便在图片中看到Canvas下的内容,或者在div中将div放置在一个空的html页面中的片段中&#34;试验&#34;在里面。

3 个答案:

答案 0 :(得分:3)

您可以使用合成来制作手电筒效果:

  • 清除画布
  • 创建径向渐变以用作显示。
  • 填充径向渐变。
  • 使用source-atop合成来绘制背景图像。图像仅显示在径向渐变内。
  • 使用destination-over合成以黑色填充画布。黑色将填补&#34;背后&#34;现有的径向渐变图像。

enter image description here enter image description here

以下是示例代码和演示:

&#13;
&#13;
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);

}
&#13;
body{ background-color: ivory; }
#canvas{border:1px solid red; }
&#13;
<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>
&#13;
&#13;
&#13;

如果你的聚光灯半径永远不会改变,这里的方法要快得多:

通过将聚光灯缓存到第二个画布然后再获得速度......

  1. 在画布上绘制图像。
  2. 在画布上绘制聚光灯。
  3. 使用fillRect将聚光灯外的4个矩形遮挡。
  4. 示例代码和演示:

    &#13;
    &#13;
    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);
    }
    &#13;
    body{ background-color: ivory; }
    #canvas{border:1px solid red; }
    &#13;
    <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>
    &#13;
    &#13;
    &#13;

答案 1 :(得分:1)

您可以通过将画布直接放在图像上来实现聚光灯效果。使画布与图像大小相同,并将合成操作设置为xor,以便在同一位置绘制的两个黑色像素相互抵消。

context.globalCompositeOperation = 'xor';

现在,您可以将画布涂成黑色并在鼠标光标周围填充黑色圆圈。结果是黑色表面上有一个洞,显示下面的图像。

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

要制作具有模糊边缘的聚光灯,请定义以鼠标位置为中心的径向渐变,并在其周围填充正方形。

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);

以下代码段演示了使用纯JavaScript的两种方法。要从清晰的聚光灯变为模糊边缘的聚光灯,请单击图像上方的复选框。

&#13;
&#13;
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();
    }
  };
};
&#13;
* {
  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;
}
&#13;
<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>
&#13;
&#13;
&#13;

答案 2 :(得分:0)

此代码适用于我:

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();

似乎这一行弄乱了它context.globalCompositeOperation = "destination-out";

在填充路径之前,在填充rect和fill()函数之前,代码中也存在无意义的行,如beginnig路径