Skip to content

Masking

Masks use one clip as an alpha mask for another. Only the pixels where the mask is opaque are rendered on the target. Common use cases: shape masks (circle/star/polygon), text-shaped masks (video inside text), animated reveal transitions.

INFO

Masking is supported on video, image, gif, HTML text, and Lottie clips.

Add a Mask

Add a mask clip to its own layer, then reference it as a mask for another clip.

typescript
const target = await layer.addClip({
  type: "shape",
  shape: "rectangle",
  startTime: 0,
  duration: 5,
  style: { fillColor: "#6d6ff2", width: 1200, height: 700 },
});
target.style.setPosition(960, 540);

// The mask goes on its own layer so it can live independently.
const maskLayer = Engine.getInstance().getTimeline().createLayer();
maskLayer.setVisibility(false); // hide the raw mask from the preview

const mask = await maskLayer.addClip({
  type: "shape",
  shape: "circle",
  startTime: 0,
  duration: 5,
  style: { fillColor: "#FFFFFF", width: 500, height: 500 },
});
mask.style.setPosition(960, 540);

target.addClipMask(mask);
Idle

The target clip is rendered only where the mask has non-zero alpha. The mask itself is hidden from the preview because we marked its layer invisible.

Remove a Mask

removeClipMask takes the mask's Clip instance, not an id:

typescript
target.removeClipMask(mask);

Call target.getMasks() to get the list of masks currently applied.

Animating the Mask

Because the mask is a regular clip, it can be animated with the Property Animator. Animate the mask's scaleX and scaleY to create a reveal:

typescript
const target = await layer.addClip({
  type: "shape",
  shape: "rectangle",
  startTime: 0,
  duration: 4,
  style: { fillColor: "#FF4D6D", width: 1200, height: 700 },
});
target.style.setPosition(960, 540);

const maskLayer = Engine.getInstance().getTimeline().createLayer();
maskLayer.setVisibility(false);

const mask = await maskLayer.addClip({
  type: "shape",
  shape: "circle",
  startTime: 0,
  duration: 4,
  style: { fillColor: "#FFFFFF", width: 800, height: 800 },
});
mask.style.setPosition(960, 540);

// Grow the mask from nothing to full size in one second.
mask.propertyAnimator.addKeyframe("scaleX", 0, 0);
mask.propertyAnimator.addKeyframe("scaleX", 1, 1);
mask.propertyAnimator.addKeyframe("scaleY", 0, 0);
mask.propertyAnimator.addKeyframe("scaleY", 1, 1);

target.addClipMask(mask);
Idle

Text-Shaped Mask

Any clip can serve as a mask, including a text clip. This creates a "window" through the target that takes the shape of the text glyphs, a common effect for title reveals and branding moments.

typescript
// Target: a bright gradient-like rectangle that will only show through text.
const target = await layer.addClip({
  type: "shape",
  shape: "rectangle",
  startTime: 0,
  duration: 5,
  style: { fillColor: "#FF4D6D", width: 1600, height: 900 },
});
target.style.setPosition(960, 540);

// Mask: big bold text on its own hidden layer.
const maskLayer = Engine.getInstance().getTimeline().createLayer();
maskLayer.setVisibility(false);

const mask = await maskLayer.addClip({
  type: "text",
  text: "HELLO",
  startTime: 0,
  duration: 5,
  style: {
    fontSize: 400,
    color: "#FFFFFF",
    fontWeight: "900",
  },
});
mask.style.setPosition(960, 540);

target.addClipMask(mask);
Idle

Only the pixels where the text has non-zero alpha show the pink fill, everywhere else is transparent. Swap the target for an image, GIF, or video clip and you get the classic video-in-text effect.

Wrap Mode

Masks expose wrap modes that control what happens outside the mask bounds:

  • clamp (default): hide everything outside the mask.
  • repeat: tile the mask.
  • mirror: tile and mirror.

Set the wrap mode on the MaskFilter returned from getMasks().

Hiding the Source Mask Clip

You usually want the mask clip itself invisible, since it renders on its own layer too. Use setVisible(false) on the clip, or setVisibility(false) on its layer. Hidden clips and layers still participate in masking and do not affect the final export.

See Also