Chapter 6 of 7. Prerequisite: Chapter 5: you have a radial visualiser reading the bass band. This chapter builds the layered composite around it: a static radial mask that shapes a rotating light-ray fan, sitting over an audio-reactive base, plus the alpha-video route for when the transparency belongs to a moving clip.
Search "remotion alpha mask transparent video compositing" and the results split two ways. One half tells you to render a WebM with a real alpha channel and stack it over a background. The other half never gets to compositing at all, and stops at drawing bars. Neither shows the thing a music visualiser actually needs: a static radial mask that shapes an audio-reactive layer and sits in the right place in the stack. That is this chapter. The tool is a CSS mask-image set to a radial-gradient, and the whole game is what the mask contains and which layer you put it on.
The radial alpha mask, in code
A CSS mask hides parts of an element based on the alpha channel of a mask image. The MDN mask-image reference states it plainly: the property hides sections of the element "based on the alpha channel of the mask image." Where the mask is opaque black, the element shows. Where the mask is transparent, the element is cut away. A radial-gradient that runs from black at the centre to transparent at the edge therefore turns any full-frame layer into a soft-edged circle.
Here is the real mask from the build. It is a static constant, applied to a rotating conic light-ray fan so the rays fade to nothing before they reach the frame edge:
// TrackVisualiser.tsx:127-128
WebkitMaskImage: 'radial-gradient(closest-side, black 0%, rgba(0,0,0,0.5) 28%, transparent 66%)',
maskImage: 'radial-gradient(closest-side, black 0%, rgba(0,0,0,0.5) 28%, transparent 66%)',
The mask string never changes. It is not rebuilt per frame and it carries no interpolated value. Three details in it matter. closest-side sizes the gradient so its edge lands on the nearest box side, which keeps the circle proportional regardless of the element's aspect ratio. The middle stop, rgba(0,0,0,0.5) 28%, is the trick that makes the edge read as soft rather than as a hard disc: half-alpha at 28% means the layer is already half-cut a third of the way out, so the falloff has a long shoulder instead of a crisp rim. And the layer being masked is a conic-gradient whose rotation is time-driven, rayRot = frame * 0.02 (TrackVisualiser.tsx:80), so the fixed mask converts a spinning full-frame conic into a contained radial burst.
What the audio actually drives here
This is the part worth being precise about, because it is easy to assume the beat opens or closes the mask. It does not. The mask geometry is a hard-coded constant. What reacts to the music is the layers, not the mask shape.
From the previous chapters you already have the bands extracted per frame, and they arrive already normalised to the 0 to 1 range:
// TrackVisualiser.tsx:55-56 (bands, already 0..1 from visualizeAudio)
sub = clamp(avg(smooth, 0, 3) * 3.2); // bins 0-2, the sub-bass envelope
kick = clamp(avg(raw, 0, 2) * 3.6); // bins 0-1, the raw transient
Those band values sit in 0 to 1 because visualizeAudio() returns "an array of values describing the amplitude of each frequency range. Each value is between 0 and 1." Raw Web Audio does not hand you that. The MDN getByteFrequencyData() docs note that FFT frequency bins arrive as "integers on a scale from 0 to 255." Remotion normalises that for you, so a band value drops straight into a layer property without a divide-by-255 first.
That normalised sub value is what breathes the frame. It grades the base image, scale = kb + sub*0.02 + kick*0.015 and brightness = 1 + sub*0.22 + kick*0.12 (TrackVisualiser.tsx:63-68), and it drives the masked ray fan's opacity and a slight scale so the burst pulses with the low end. The mask holds the shape steady while the layer inside it brightens and swells. Motion and light do the reacting; the mask is the fixed aperture they happen through.
Which layer owns the effect decides the read
The mask maths can be byte-identical and the shot can still read as two different things depending on where the masked layer sits in the stack. Compositing is stacking, and the order is the design.
From back to front, the finished shot in TrackVisualiser is: the base image (<Img>), then a screen-blended celestial bloom (TrackVisualiser.tsx:115-118) positioned at the photo's light source, then the masked conic ray fan (:122-130), then a vignette applied as an inset boxShadow (:137), then the brand lockup that springs in on its own timer (:71). The audio touches the base grade and the ray-fan intensity. The mask and the vignette are fixed. The lockup is time-driven, not audio-driven.
The lesson generalises past this one visualiser. When a masked layer is not reading the way you want, the fix is usually not the mask geometry or the easing. It is the stack. A soft-edged burst placed above a screen-blend bloom reads as light coming through the frame. The same burst placed below the vignette reads as contained and framed. The mask does not carry that meaning by itself; its position in the order does. Decide what sits above and below the masked layer before you touch its gradient.
When you want a real transparent video instead
The CSS mask covers most visualiser needs because both the front and back layers are still images and the reveal is a shape, not a matte with per-pixel transparency baked into a moving clip. But there is a genuinely different case: you have a source video that already carries an alpha channel, a rendered logo sting or a particle overlay exported with transparency, and you want to composite it over the visualiser without re-deriving a mask.
For that, Remotion supports alpha-channel video directly. The transparent-videos docs state that "Chrome and Firefox support WebM videos with alpha channels," so you can embed a transparent clip and let the browser composite it. The source has to be a WebM carrying an alpha channel, not an MP4/H.264, which has no alpha channel to carry.
You mount that clip with <OffthreadVideo>, which during rendering "extracts the exact frame from the video and displays it in an <Img> tag," so the compositing is frame-accurate rather than dependent on a live <video> element's playback timing. In this build <OffthreadVideo muted> is already the workhorse for the video comps, once for the main plate and once for a blurred, screen-blended bloom copy stacked over it:
// ReactiveFinish.tsx:74-90 (main plate, then a blurred screen-blend bloom copy)
<OffthreadVideo src={staticFile(video)} muted style={{ /* main */ }} />
<OffthreadVideo src={staticFile(video)} muted
style={{ filter: 'blur(...)', mixBlendMode: 'screen', /* bloom */ }} />
The video is always muted. The clean WAV loaded separately is the single source of both sound and the analysis that drives the layers, which keeps audio and picture from drifting. That decision was covered in Chapter 4, and it is why the compositing here never fights the sync.
Putting the layers in order
Compositing is just stacking, and the order is the whole design. From back to front, the finished shot is the base image, then the screen-blend bloom, then the masked ray fan, then the vignette, then the brand lockup. The audio touches two of those, the base grade and the ray-fan intensity, and everything else is static or time-driven. That small number of contact points is why the effect stays legible: the eye reads a steady frame with light moving inside it, not a shape flailing on the beat.
One honest limitation to carry forward. Because the mask is static, the burst always occupies the same footprint no matter how quiet the track goes. On a near-silent passage the ray fan dims via its opacity but its outline stays put, which can read as slightly too present. Fading the ray-fan layer's opacity all the way to zero on low sub, rather than to a floor, tightens that up. It is a property tune on the layer, not a change to the mask, which is the payoff of keeping the mask fixed and pointing the audio at a number instead.
Chapter 7 takes this finished, layered comp and gets the frames out to a file: local CLI rendering at concurrency 4, the memory budget that decides how many full-res plates you dare mount per frame, and an honest account of why this build stays off Lambda rather than pretending it shipped there.
Composite a fixed aperture over a reactive base
Recreate the layered shot in your own project with your own image and track. Stack, from back to front: a base image graded by the bass, a screen-blended glow positioned at a light source in the picture, a slowly rotating conic fan shaped by a static radial-gradient mask with both the prefixed and unprefixed mask properties set, and a vignette. Point the audio only at cheap layer properties such as opacity, brightness and scale, and leave the mask string as a constant. Then reorder two layers and describe how the read changes.
Expected behaviour
- The conic fan fades to nothing before the frame edge, shaped by a mask that never changes between frames
- Both WebkitMaskImage and maskImage are set to the same gradient on the masked element
- The bass visibly drives layer properties while the mask geometry stays fixed on every frame
- Moving the masked layer above or below the vignette produces a describable change in how the shot reads
- On a near-silent passage the fan dims via its opacity rather than the mask closing
PROVE IT Freeze one frame during a loud hit and one during silence and show that the mask footprint is identical in both while the light inside it differs.
In this build, what does the beat actually animate on the masked ray fan?
A transparent video overlay in Remotion must ship in which container format to carry an alpha channel?
Why can the same masked layer read as light coming through the frame in one stack order and as contained decoration in another?
Show answer
Compositing is stacking and the order is the design. A soft-edged burst placed above a screen-blend bloom reads as light coming through the frame, while the same burst placed below the vignette reads as contained and framed. The mask does not carry that meaning by itself; its position in the order does, so decide what sits above and below it before touching the gradient.
↺ re-read: “Which layer owns the effect decides the read”