Search code examples
fabricjsclip-path

FabricJS Problem with clipPath on a video


The video is not played correctly when a clipPath is attached to the object.

The video updates the frames only when you resize the object.

var canvas = new fabric.Canvas('canvas');
var videoEl = document.getElementById('video');

var clipText = new fabric.Text('My Custom Text', { 
    left: 'center',
    top: 'center', 
    fontSize: 40,
});

var video = new fabric.Image(videoEl, {
    left: 10,
    top: 10,
    angle: 0,
    originX: 0,
    originY: 0,
    objectCaching: false,
    clipPath: clipText,
    backgroundColor: '#ff0000'
});

canvas.add(video);
video.getElement().play();

fabric.util.requestAnimFrame(function render() {
  canvas.renderAll();
  fabric.util.requestAnimFrame(render);
});

Solution

  • Looks like fabric.js relies heavily on caching when Image object has a clipPath.

    During the render call, it checks this.needsItsOwnCache() which returns true because this.clipPath is present. Then it calls this.renderCache(), which only updates the cached canvas if this.isCacheDirty() returns true.

    For it to return true, you can utilize the object's statefullCache and cacheProperties setting a custom videoTime property with a value of video element's currentTime. Then you just make sure to update videoTime on each animation frame.

    const canvas = new fabric.Canvas("c");
    const clipText = new fabric.Text("My Custom Text", {
      left: "center",
      top: "center",
      fontSize: 36
    });
    const videoEl = document.querySelector("#video");
    const video = new fabric.Image(videoEl, {
      left: 10,
      top: 10,
      angle: 0,
      originX: 0,
      originY: 0,
      objectCaching: true,
      statefullCache: true,
      cacheProperties: ['videoTime'],
      clipPath: clipText,
      backgroundColor: "#ff0000"
    });
    canvas.add(video);
    video.getElement().play();
    fabric.util.requestAnimFrame(function render() {
      canvas.renderAll();
      video.videoTime = videoEl.currentTime;
      fabric.util.requestAnimFrame(render);
    });
    document.querySelector("#button").onclick = () => {
      video.clipPath = video.clipPath == null ? clipText : null;
    };
    body {
      background: ivory;
    }
    
    .row {
      display: flex;
      flex-direction: row;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.5.0/fabric.js"></script>
    <div class="row">
      <canvas id="c" width="300" height="200"></canvas>
      <video id="video" width="240" height="180" autoplay loop muted >
        <source src="https://html5demos.com/assets/dizzy.mp4">
      </video>
    </div>
    <button id="button">toggle</button>