【问题标题】:HTML canvas spotlight effectHTML 画布聚光灯效果
【发布时间】:2015-12-03 04:20:45
【问题描述】:

假设我有以下代码。

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

对 mousemove 产生以下效果:

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

编辑:我不希望在图像上产生这种效果。相反,我想将画布放在整个网页上。我还希望画布始终为黑色,并且鼠标在其位置上生成一个聚光灯,以查看画布下方的内容,就像您在图片中看到的那样,或者在片段中放置了一个 div 的空 html 页面“测试”在里面。

【问题讨论】:

  • 这里有几个问题。我们无法重现您的示例,例如 giveCanvasContext 不是天真的函数。注意用你不需要beginPath 或endPath 的黑色填充画布。
  • @ci_ 是的,我做到了,但似乎画布将被绘制为白色而不是再次填充黑色..

标签: javascript html canvas


【解决方案1】:

这段代码对我有用:

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 之前的 beginnig path 和填充路径之后的 fill() 函数

【讨论】:

  • 这段代码启用了聚光灯效果,是的,但它实际上不会显示我想要的画布下的内容
  • 嗨我有一个类似的问题,我有一个画布,上面有一些元素,我想让它们像黑屏和鼠标指针周围的圆形聚光灯一样隐藏起来,这样无论我在哪里移动鼠标的东西变得可见。问题是这些代码中的大多数都在图像作为背景上工作,但我没有图像背景我有一个画布,我想对它产生这种效果。你能帮忙吗?
【解决方案2】:

您可以使用合成来创建手电筒效果:

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

这是示例代码和演示:

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>

如果您的聚光灯半径永远不会改变,这里有一个更快的方法:

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

  1. 在画布上绘制图像。
  2. 在画布上绘制聚光灯。
  3. 使用fillRect 将聚光灯外的 4 个矩形涂黑。

示例代码和演示:

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>

【讨论】:

    【解决方案3】:

    您可以通过将画布直接放在图像上来实现聚光灯效果。使画布与图像大小相同,并将合成操作设置为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);
    

    以下 sn-p 演示了使用纯 JavaScript 的两种方法。要从边缘清晰的聚光灯更改为边缘模糊的聚光灯,请点击图片上方的复选框。

    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>

    【讨论】:

      猜你喜欢
      • 2015-05-02
      • 1970-01-01
      • 2013-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-09
      相关资源
      最近更新 更多