'How to scratch reveal an image with Javascript canvas element

I created a canvas element where I can draw paths. There, I draw transparent paths and I "scratch" reveal the foreground. It works on both mobile and desktop.



Solution 1:[1]

A working example

// Animation frame
window.requestAnimFrame = (function (callback) {
  return window.requestAnimationFrame || 
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimaitonFrame ||
        function (callback) {
          window.setTimeout(callback, 1000/60);
        };
})();

const body = document.getElementById('body');

// Draw
const color = "#f70d8a";
const stroke = 25;

// Canvas
const canvas = document.getElementById("canvas");
let canvasWidth = canvas.offsetWidth;
let canvasHeight = canvas.offsetHeight;
const context = canvas.getContext("2d");
context.fillRect(0, 0, canvasWidth, canvasHeight);
context.strokeStyle = color;
context.lineWidth = stroke;
document.getElementById('canvas-background').innerHTML = "Oh<br>yeah";

// Mouse events
var drawing = false;
var mousePos = { x:0, y:0 };
var lastPos = mousePos;
canvas.addEventListener("mousedown", function (e) {
  drawing = true;
  lastPos = getMousePos(canvas, e);
}, false);
canvas.addEventListener("mouseup", function (e) {
  drawing = false;
}, false);
canvas.addEventListener("mousemove", function (e) {
  mousePos = getMousePos(canvas, e);
}, false);

// Touch events
canvas.addEventListener("touchstart", function (e) {
  mousePos = getTouchPos(canvas, e);
  var touch = e.touches[0];
  var mouseEvent = new MouseEvent("mousedown", {
    clientX: touch.clientX,
    clientY: touch.clientY
  });
  canvas.dispatchEvent(mouseEvent);
}, false);
canvas.addEventListener("touchend", function (e) {
  var mouseEvent = new MouseEvent("mouseup", {});
  canvas.dispatchEvent(mouseEvent);
}, false);
canvas.addEventListener("touchmove", function (e) {
  var touch = e.touches[0];
  var mouseEvent = new MouseEvent("mousemove", {
    clientX: touch.clientX,
    clientY: touch.clientY
  });
  canvas.dispatchEvent(mouseEvent);
}, false);

// Prevent default scrolling behavior on Touch events
document.body.addEventListener("touchstart", function (e) {
  if (e.target == canvas) {
    e.preventDefault();
  }
}, {passive:false});
document.body.addEventListener("touchend", function (e) {
  if (e.target == canvas) {
    e.preventDefault();
  }
}, {passive:false});
document.body.addEventListener("touchmove", function (e) {
  if (e.target == canvas) {
    e.preventDefault();
  }
}, {passive:false});

// Mouse position
function getMousePos(canvasDom, mouseEvent) {
  var rect = canvasDom.getBoundingClientRect();
  return {
    x: mouseEvent.clientX - rect.left,
    y: mouseEvent.clientY - rect.top
  };
}
 
// Touch position
function getTouchPos(canvasDom, touchEvent) {
  var rect = canvasDom.getBoundingClientRect();
  return {
    x: touchEvent.touches[0].clientX - rect.left,
    y: touchEvent.touches[0].clientY - rect.top
  };
}

// Draw
function renderCanvas() {
  if (drawing) {
    context.moveTo(lastPos.x, lastPos.y);
    context.lineTo(mousePos.x, mousePos.y);
    context.globalCompositeOperation = "destination-out";
    context.stroke();
    lastPos = mousePos;
  }
}

// Animation frame
(function drawLoop () {
  requestAnimFrame(drawLoop);
  renderCanvas();
})();
/*---- Global variables ----*/

:root {
  --black: #222;
  --yellow: #fff295;
  --purple: #fa0afa;
  --blue: #0daaf8;
  --green: #0df882;
  --red: #e64c4c;
  --pink: #f70d8a;
  --main-margin-top: 10vh;
  --canvas-width: 320px;
  --canvas-height: 320px;
}

/*---- Resetter ----*/

* {
    margin: 0;
    border: 0;
    padding: 0;
}

/*---- Default classes ----*/

body {
    background-color: var(--black);
  color: #fff;
    font-family: Futura, Arial, sans-serif;
    font-size: 1rem;
  line-height:1rem;
}

main {
  margin: var(--main-margin-top) auto;
  width: var(--canvas-width);
}

h1 {
  font-size: 2rem;
  font-weight: 400;
}

/*---- Generic classes ----*/

.center {
  text-align: center;
}

/*---- Custom classes ----*/

#canvas {
  z-index:9999;
  position:absolute;
  top:calc(var(--main-margin-top) + 2rem);
  left:calc(50vw - var(--canvas-width)/2);
  background-color: transparent;
}

#canvas-background {
  z-index: 999;
  position: absolute;
  width: inherit;
  line-height: 1em;
  font-size: 5rem;
  padding-top:calc(var(--canvas-height)/2 - 1em);
  text-align: center;
  color:var(--pink);
}
<body id="body">
  <main class="center">
    <h1 class="center">Scratch this bitch</h1>
    <canvas id="canvas" width="320px" height="320px">
        </canvas>
    <p id="canvas-background"></p>
  </main>
</body>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 jlanssie