HTML canvas spotlight effect

前端 未结 3 1052
遇见更好的自我
遇见更好的自我 2020-12-03 23:56

Let\'s say I have the following code.

相关标签:
3条回答
  • 2020-12-04 00:02

    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>

    0 讨论(0)
  • 2020-12-04 00:09

    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 讨论(0)
  • 2020-12-04 00:23

    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>

    0 讨论(0)
提交回复
热议问题