Search code examples
htmlcsscanvasborder

How to remove thin border on 2 sides of canvas element?


UPDATE: Link to JSFiddle with workable code

I'm making a website and have created a stack of two canvas elements: the top canvas context is a white rectangle that "erases" to reveal an image loaded into the bottom canvas context. The functionality works properly. My issue is that a thin grey border appears on the right and bottom sides of the canvas stack when I include a setInterval line of code.

It disappears when I remove this timer variable (see code below) but reappears if I add any type of state check like onmouseout to the canvas elements. Here is a screenshot:

enter image description here

Any idea why this is happening? Similar SO questions/solutions have not solved my problem.

window.onload = function() {
    var speaker = document.getElementById('speaker');
    //speaker.onload = MoveElement(speaker, "right", 1000);

    //Create canvases & contexts
    var canvas = document.getElementById('canvas');
    var ctxB = canvas.getContext('2d');
    var canvas2 = document.getElementById('canvas2');
    var ctxT = canvas2.getContext('2d');

    //Get waterfall image object
    var waterfall = document.getElementById('waterfall');

    //Set canvas w&h properties
    canvas.width = canvas2.width = .3*waterfall.width;
    canvas.height = canvas2.height = .3*waterfall.height;

    //Populate Bottom canvas with waterfall image
    ctxB.drawImage(waterfall, 0, 0, canvas.width, canvas.height);

    //Populate Top canvas with white rectangle
    ctxT.fillStyle = "white";
    ctxT.fillRect(0, 0, canvas2.width, canvas2.height);

    //Make Top canvas "erasable"
    canvas2.addEventListener('mousemove', event => {
        var x = event.offsetX;
        var y = event.offsetY;  
        const eraseSize = 15;
        ctxT.clearRect(x-eraseSize/2, y-eraseSize/2, eraseSize, eraseSize);
    });
}

//Set interval timer to repeatedly execute TransparencyCheck()
var timer = setInterval(TransparencyCheck, 500);

//Check that all pixel alpha values = 0
function TransparencyCheck() {
    var canvas2 = document.getElementById('canvas2');
    var ctxT = canvas2.getContext('2d');
    var imageDataTop = ctxT.getImageData(0, 0, canvas2.width, canvas2.height);
    var counter = 0;

    for (var i = 3; i < imageDataTop.data.length; i += 4) {
        if (imageDataTop.data[i] == 0) {
            counter++;
        }
    if (counter == imageDataTop.data.length/4) {
        canvas2.style.opacity = "0";
        }
    }
}
#stack {
    position: relative;
}
#stack canvas {
    position: absolute;
    display: block;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 150px;
}
            <img hidden src="https://sample-videos.com/img/Sample-jpg-image-50kb.jpg" alt="issue here" id="waterfall" />
      
        <div id="stack">
            <canvas id="canvas"></canvas>
            <canvas id="canvas2" onmouseout="TransparencyCheck()"></canvas>
        </div>
 


Solution

  • The problem is that the dimensions of the canvas are being calculated as a fraction (0.3) of the dimensions of the underlying image. This can result in a 'part pixel' problem. That is the system has to decide how to show a fraction of a CSS pixel, and on modern screens several screen pixels are used to show one CSS pixel. A screen pixwl can get 'left behind' (ie still showing) during this process.

    A slightly hacky way of getting round this (but I know of no other) is to decrease the size of the bottom canvas by a few pixels so that we are absolutely sure any left overs are under the white of the top canvas at the start.

    This snippet makes doubly sure by taking 2px off the width and height.

    Incdentally, I copied the code from the codepen pointed at by the question and it worked as an SO snippet OK. Here it is:

    window.onload = function() {
      var speaker = document.getElementById('speaker');
      //speaker.onload = MoveElement(speaker, "right", 1000);
    
      //Create canvases & contexts
      var canvas = document.getElementById('canvas');
      var ctxB = canvas.getContext('2d');
      var canvas2 = document.getElementById('canvas2');
      var ctxT = canvas2.getContext('2d');
    
      //Get waterfall image object
      var waterfall = document.getElementById('waterfall');
    
      //Set canvas w&h properties
      canvas.width = canvas2.width = .3 * waterfall.width;
      canvas.width = canvas.width - 2;
      canvas.height = canvas2.height = .3 * waterfall.height;
      canvas.height = canvas.height - 2;
    
    
      //Populate Bottom canvas with waterfall image
      ctxB.drawImage(waterfall, 0, 0, canvas.width, canvas.height);
    
      //Populate Top canvas with white rectangle
      ctxT.fillStyle = "white";
      ctxT.fillRect(0, 0, canvas2.width, canvas2.height);
    
      //Make Top canvas "erasable"
      canvas2.addEventListener('mousemove', event => {
        var x = event.offsetX;
        var y = event.offsetY;
        const eraseSize = 15;
        ctxT.clearRect(x - eraseSize / 2, y - eraseSize / 2, eraseSize, eraseSize);
      });
    }
    
    //Set interval timer to repeatedly execute TransparencyCheck()
    var timer = setInterval(TransparencyCheck, 5000);
    
    //Check that all pixel alpha values = 0
    function TransparencyCheck() {
      var canvas2 = document.getElementById('canvas2');
      var ctxT = canvas2.getContext('2d');
      var imageDataTop = ctxT.getImageData(0, 0, canvas2.width, canvas2.height);
      var counter = 0;
    
      for (var i = 3; i < imageDataTop.data.length; i += 4) {
        if (imageDataTop.data[i] == 0) {
          counter++;
        }
        if (counter >= imageDataTop.data.length / 4) {
          canvas2.style.opacity = "0";
          clearTimeout(timer);
          alert('all top canvas erased');
        }
      }
    }
    #stack {
      position: relative;
    }
    
    #stack canvas {
      position: absolute;
      display: block;
      left: 50%;
      transform: translateX(-50%);
      margin-top: 150px;
    }
    <img hidden src="https://sample-videos.com/img/Sample-jpg-image-50kb.jpg" alt="issue here" id="waterfall" />
    
    <div id="stack">
      <canvas id="canvas"></canvas>
      <canvas id="canvas2" onmouseout="TransparencyCheck()"></canvas>
    </div>