The HTML content you're reading right now is overlaid with a full-screen <canvas> element. There is a fragment shader that defines opacity and color for each pixel of the <canvas>. Shader input values are scroll progress (aka animation progress), time, and resolution.
While time and window size (resolution) are super easy to gather, for animation progress I use GSAP ScrollTrigger plugin.
Once the inputs are prepared, we pass them as uniforms to the shader. The WebGL part of this demo is a basic JS boilerplate to render a fragment shader on the single full-screen plane. No extra libraries here.
The fragment shader is based on Fractal Brownian Motion (fBm) noise.
First, we create a semi-transparent mask to define a contour of burning paper. It is basically a low-scale fBm noise with scroll progress value used as a threshold.
Taking the same fBm noise with different thresholds we can:
(a) darken parts of the paper so each pixel gets darker before turning transparent
(b) define the stripe along the paper edge and use it as a mask for flames.
The fire is done as another two fBm based functions - one for shape and one for color. Both have a much higher scale and both are animated with time value instead of scroll progress.