Search code examples
javascriptcsslottie

Lottie animation as mask-image


Is it possible to use a lottie animation as a mask-image property on an image?

This is my code:

const newData = {
  "v": "5.10.2",
  "fr": 60,
  "ip": 0,
  "op": 61,
  "w": 500,
  "h": 500,
  "nm": "Zeichenfläche – 2",
  "ddd": 0,
  "assets": [],
  "layers": [{
    "ddd": 0,
    "ind": 1,
    "ty": 4,
    "nm": "211120_Birgit-Brandis_1216_AS72265r",
    "sr": 1,
    "ks": {
      "o": {
        "a": 0,
        "k": 100,
        "ix": 11
      },
      "r": {
        "a": 0,
        "k": 0,
        "ix": 10
      },
      "p": {
        "a": 0,
        "k": [250, 250, 0],
        "ix": 2,
        "l": 2
      },
      "a": {
        "a": 0,
        "k": [182.5, 181.999, 0],
        "ix": 1,
        "l": 2
      },
      "s": {
        "a": 0,
        "k": [100, 100, 100],
        "ix": 6,
        "l": 2
      }
    },
    "ao": 0,
    "shapes": [{
      "ty": "gr",
      "it": [{
        "ind": 0,
        "ty": "sh",
        "ix": 1,
        "ks": {
          "a": 1,
          "k": [{
            "i": {
              "x": 0.05,
              "y": 1
            },
            "o": {
              "x": 0.5,
              "y": 0
            },
            "t": 0,
            "s": [{
              "i": [
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [-1.361, -1.076],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0]
              ],
              "o": [
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 4.649],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0]
              ],
              "v": [
                [365, 247.614],
                [349.777, 152.992],
                [297.992, 152.991],
                [312.734, 84.458],
                [312.734, 0.162],
                [265.372, 0.162],
                [250.757, 0.162],
                [181.461, 0.152],
                [65.961, 0.002],
                [35.985, 0.001],
                [0, 0],
                [0, 126.637],
                [2.322, 136.401],
                [14.262, 145.847],
                [15.552, 154.496],
                [17.362, 166.638],
                [17.362, 166.64],
                [0.047, 166.64],
                [0.047, 286.072],
                [0.047, 362.438],
                [14.731, 362.697],
                [39.602, 363.136],
                [253.728, 363.836],
                [253.728, 363.635],
                [365, 363.999]
              ],
              "c": true
            }]
          }, {
            "t": 60,
            "s": [{
              "i": [
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0.303, -3.151],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [-3.261, -3.473],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0]
              ],
              "o": [
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 4.649],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0],
                [5.308, 5.652],
                [0, 0],
                [0, 0],
                [0, 0],
                [0, 0]
              ],
              "v": [
                [365, 247.614],
                [365.027, 153.242],
                [364.992, 152.991],
                [365.234, 85.958],
                [365.234, 50.412],
                [343.52, 32.444],
                [343.007, 24.162],
                [301.211, 24.162],
                [63.711, 24.25],
                [35.485, 0],
                [0, 0],
                [0, 126.637],
                [-0.053, 137.526],
                [0.012, 147.222],
                [0.052, 154.746],
                [0.237, 166.763],
                [0.112, 166.765],
                [0.047, 166.64],
                [0.047, 278.999],
                [22.297, 348.688],
                [27.011, 359.347],
                [39.602, 363.761],
                [253.727, 363.836],
                [325.977, 364.01],
                [365, 345.999]
              ],
              "c": true
            }]
          }],
          "ix": 2
        },
        "nm": "Path 1",
        "mn": "ADBE Vector Shape - Group",
        "hd": false
      }, {
        "ty": "fl",
        "c": {
          "a": 0,
          "k": [0, 0, 0, 1],
          "ix": 4
        },
        "o": {
          "a": 0,
          "k": 100,
          "ix": 5
        },
        "r": 1,
        "bm": 0,
        "nm": "Fill 1",
        "mn": "ADBE Vector Graphic - Fill",
        "hd": false
      }, {
        "ty": "tr",
        "p": {
          "a": 0,
          "k": [0, 0],
          "ix": 2
        },
        "a": {
          "a": 0,
          "k": [0, 0],
          "ix": 1
        },
        "s": {
          "a": 0,
          "k": [100, 100],
          "ix": 3
        },
        "r": {
          "a": 0,
          "k": 0,
          "ix": 6
        },
        "o": {
          "a": 0,
          "k": 100,
          "ix": 7
        },
        "sk": {
          "a": 0,
          "k": 0,
          "ix": 4
        },
        "sa": {
          "a": 0,
          "k": 0,
          "ix": 5
        },
        "nm": "Transform"
      }],
      "nm": "211120_Birgit-Brandis_1216_AS72265r",
      "np": 2,
      "cix": 2,
      "bm": 0,
      "ix": 1,
      "mn": "ADBE Vector Group",
      "hd": false
    }],
    "ip": 0,
    "op": 3600,
    "st": 0,
    "ct": 1,
    "bm": 0
  }],
  "markers": []
}

var params = {
  container: document.getElementById('lottie'),
  renderer: 'svg',
  loop: false,
  autoplay: false,
  animationData: newData,
  rendererSettings: {
    preserveAspectRatio: 'none',
    id: 'mask',
  },
};

var anim;

anim = bodymovin.loadAnimation(params);

let lottie = document.querySelector('#lottie')

function playLottie(direction) {
  if (direction == 'f') {
    anim.setDirection(1)
  } else {
    anim.setDirection(-1)
  }
  anim.play()
}
lottie.addEventListener('mouseover', function() {
  playLottie('f')
})
lottie.addEventListener('mouseout', function() {
  playLottie('b')
})
#container {
  display: flex;
}

#lottie {
  width: 300px;
  height: 500px;
}

#masked {
  mask-repeat: no-repeat;
  mask-image: url(#mask);
  mask-size: 100% 100%;
  mask-position: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.9.1/lottie.min.js"></script>
<div id="container">
  <img src="https://picsum.photos/300/500" />
  <div id="lottie"></div>
  <img id="masked" src="https://picsum.photos/300/500" />
</div>

Alternatively see this codepen: https://codepen.io/jacquesknie/pen/WNVGdWW

The first HTML-element is an image, the second element is a lottie-animation which starts its animation on mouseover, the third element is the same image with the lottie-animation as its mask-image. Unfortunately this doesn't work. The idea is to have an animated mask on the image on mouseover.

Is there any way to make this work? Maybe even with a very different approach?

Thanks in advance!


Solution

  • You can, but you need to ensure you get/create a SVG <mask> element.

    In short you can reference a mask via:

    • element ID: element must be <mask>
    • dataURL: you can add a complete SVG and choose whether luminance or opacity is defining transparency

    In this case you can move the dynamically created animated SVG to a new <mask> element.

    // add event listeners to image
    masked.addEventListener("mouseover", function() {
      playLottie("f");
    });
    masked.addEventListener("mouseout", function() {
      playLottie("b");
    });
    
    // get SVG
    let lottieSvg = lottie.querySelector('svg')
    
    //hide lottie svg
    lottieSvg.setAttribute('style', `width:0; height:0; position:absolute;`)
    let ns = 'http://www.w3.org/2000/svg';
    
    // create SVG mask
    let maskEl = document.createElementNS(ns, 'mask')
    lottieSvg.append(maskEl)
    
    // add mask bg
    let rect = document.createElementNS(ns, 'rect')
    rect.setAttribute('fill', '#000')
    rect.setAttribute('width', '100%')
    rect.setAttribute('height', '100%')
    maskEl.append(rect)
    maskEl.id = 'mask';
    
    // move outermost group to mask
    let gMain = lottie.querySelector('g')
    maskEl.append(gMain)
    gMain.removeAttribute('clip-path')
    
    let path = lottie.querySelector('path')
    path.setAttribute('fill', '#fff')
    body {
      background: #ccc
    }
    
    #masked {
      mask-repeat: no-repeat;
      mask-image: url(#mask);
      mask-size: 100% 100%;
      mask-position: center;
      mask-mode: luminance;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.9.1/lottie.min.js"></script>
    <div id="container">
      <div id="lottie"></div>
      <img id="masked" src="https://picsum.photos/id/237/300/500" />
    </div>
    
    <script>
      const newData = {
        v: "5.10.2",
        fr: 60,
        ip: 0,
        op: 61,
        w: 500,
        h: 500,
        nm: "Zeichenfläche – 2",
        ddd: 0,
        assets: [],
        layers: [{
          ddd: 0,
          ind: 1,
          ty: 4,
          nm: "211120_Birgit-Brandis_1216_AS72265r",
          sr: 1,
          ks: {
            o: {
              a: 0,
              k: 100,
              ix: 11
            },
            r: {
              a: 0,
              k: 0,
              ix: 10
            },
            p: {
              a: 0,
              k: [250, 250, 0],
              ix: 2,
              l: 2
            },
            a: {
              a: 0,
              k: [182.5, 181.999, 0],
              ix: 1,
              l: 2
            },
            s: {
              a: 0,
              k: [100, 100, 100],
              ix: 6,
              l: 2
            }
          },
          ao: 0,
          shapes: [{
            ty: "gr",
            it: [{
                ind: 0,
                ty: "sh",
                ix: 1,
                ks: {
                  a: 1,
                  k: [{
                      i: {
                        x: 0.05,
                        y: 1
                      },
                      o: {
                        x: 0.5,
                        y: 0
                      },
                      t: 0,
                      s: [{
                        i: [
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [-1.361, -1.076],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0]
                        ],
                        o: [
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 4.649],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0]
                        ],
                        v: [
                          [365, 247.614],
                          [349.777, 152.992],
                          [297.992, 152.991],
                          [312.734, 84.458],
                          [312.734, 0.162],
                          [265.372, 0.162],
                          [250.757, 0.162],
                          [181.461, 0.152],
                          [65.961, 0.002],
                          [35.985, 0.001],
                          [0, 0],
                          [0, 126.637],
                          [2.322, 136.401],
                          [14.262, 145.847],
                          [15.552, 154.496],
                          [17.362, 166.638],
                          [17.362, 166.64],
                          [0.047, 166.64],
                          [0.047, 286.072],
                          [0.047, 362.438],
                          [14.731, 362.697],
                          [39.602, 363.136],
                          [253.728, 363.836],
                          [253.728, 363.635],
                          [365, 363.999]
                        ],
                        c: true
                      }]
                    },
                    {
                      t: 60,
                      s: [{
                        i: [
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0.303, -3.151],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [-3.261, -3.473],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0]
                        ],
                        o: [
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 4.649],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [5.308, 5.652],
                          [0, 0],
                          [0, 0],
                          [0, 0],
                          [0, 0]
                        ],
                        v: [
                          [365, 247.614],
                          [365.027, 153.242],
                          [364.992, 152.991],
                          [365.234, 85.958],
                          [365.234, 50.412],
                          [343.52, 32.444],
                          [343.007, 24.162],
                          [301.211, 24.162],
                          [63.711, 24.25],
                          [35.485, 0],
                          [0, 0],
                          [0, 126.637],
                          [-0.053, 137.526],
                          [0.012, 147.222],
                          [0.052, 154.746],
                          [0.237, 166.763],
                          [0.112, 166.765],
                          [0.047, 166.64],
                          [0.047, 278.999],
                          [22.297, 348.688],
                          [27.011, 359.347],
                          [39.602, 363.761],
                          [253.727, 363.836],
                          [325.977, 364.01],
                          [365, 345.999]
                        ],
                        c: true
                      }]
                    }
                  ],
                  ix: 2
                },
                nm: "Path 1",
                mn: "ADBE Vector Shape - Group",
                hd: false
              },
              {
                ty: "fl",
                c: {
                  a: 0,
                  k: [0, 0, 0, 1],
                  ix: 4
                },
                o: {
                  a: 0,
                  k: 100,
                  ix: 5
                },
                r: 1,
                bm: 0,
                nm: "Fill 1",
                mn: "ADBE Vector Graphic - Fill",
                hd: false
              },
              {
                ty: "tr",
                p: {
                  a: 0,
                  k: [0, 0],
                  ix: 2
                },
                a: {
                  a: 0,
                  k: [0, 0],
                  ix: 1
                },
                s: {
                  a: 0,
                  k: [100, 100],
                  ix: 3
                },
                r: {
                  a: 0,
                  k: 0,
                  ix: 6
                },
                o: {
                  a: 0,
                  k: 100,
                  ix: 7
                },
                sk: {
                  a: 0,
                  k: 0,
                  ix: 4
                },
                sa: {
                  a: 0,
                  k: 0,
                  ix: 5
                },
                nm: "Transform"
              }
            ],
            nm: "211120_Birgit-Brandis_1216_AS72265r",
            np: 2,
            cix: 2,
            bm: 0,
            ix: 1,
            mn: "ADBE Vector Group",
            hd: false
          }],
          ip: 0,
          op: 3600,
          st: 0,
          ct: 1,
          bm: 0
        }],
        markers: []
      };
    
      var params = {
        container: document.getElementById("lottie"),
        renderer: "svg",
        loop: false,
        autoplay: false,
        animationData: newData,
        rendererSettings: {
          preserveAspectRatio: "none",
          id: "maskEl"
        }
      };
    
      var anim;
    
      anim = bodymovin.loadAnimation(params);
    
      let lottie = document.querySelector("#lottie");
    
      function playLottie(direction) {
        if (direction == "f") {
          anim.setDirection(1);
        } else {
          anim.setDirection(-1);
        }
        anim.play();
      }
    </script>

    You also need to bind the animation event with the masked element as the mask itself isn't rendered and therefore not introduce a hit-area.

    masked.addEventListener("mouseover", function() {
      playLottie("f");
    });
    

    I highly recommend to first test masks (or clip-paths) with simplified HTML output before combining it with complex framework/library created elements.