Recommended Free Tools
Curtains.js lets you turn ordinary HTML images, videos, and canvases into textured WebGL planes, then animate them with GLSL shaders while keeping them aligned with the page’s DOM layout. The basic setup is a canvas container, a Curtains instance, a DOM element, and a Plane with vertex and fragment shaders.
What Curtains.js does
Curtains.js is a small vanilla JavaScript WebGL library for converting HTML elements that contain images or videos into textured planes that can be animated with shaders. Rather than positioning every visual in a separate WebGL scene, you style the element normally with CSS; Curtains.js keeps its WebGL geometry aligned with the element’s size and position. The project describes this approach in its official project documentation.
This is useful for image distortions, hover effects, animated transitions, and video treatments that should follow normal page layout. The HTML element remains the layout anchor; the plane and shaders provide the visual effect.
Build a basic shader effect
1. Add the canvas and media element
Place a canvas container in the page and an element to use as the texture. The getting-started example uses a #canvas wrapper and a .plane element. The plane can contain an image, video, or canvas texture.
#1 Best Overall
<div id="canvas"></div>
<div id="plane">
<img src="image.jpg" crossorigin="anonymous" alt="">
</div>
Set the element’s dimensions and position with CSS, and ensure the canvas container covers the area in which the WebGL output should appear. Add the crossorigin attribute to image and video elements as the official performance guidance recommends; otherwise, media may be loaded a second time when it is uploaded to the GPU.
2. Install or load Curtains.js
For an npm project, the getting-started guide gives this install command:
npm i curtainsjs
You can also load an ES module or UMD distribution. The project mentions react-curtains for React integrations; check the package documentation and version compatibility before using it. For vanilla JavaScript, the install guide and examples are at Curtains.js: Getting started.
Rank #2
3. Create the Curtains instance and Plane
With the library available to your script, create a Curtains instance associated with the container and pass the target DOM element to a Plane:
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →const curtains = new Curtains({ container: "canvas" });
const planeElement = document.querySelector("#plane");
const plane = new Plane(curtains, planeElement);
The Plane links the WebGL mesh to the selected element. Curtains.js uses the element’s layout information to keep the mesh positioned and sized appropriately; its matrices carry that information into the vertex shader.
4. Provide the shader interface
A basic vertex shader needs the position and texture-coordinate attributes aVertexPosition and aTextureCoord, along with the matrices uMVMatrix, uPMatrix, and uTextureMatrix0. Pass the vertex and texture coordinates to the fragment shader as varyings. Use the shader structure and required declarations in the official getting-started guide; names and bindings must match the Curtains.js version in your project.
The fragment shader samples the plane’s texture. A simple effect changes the sampling coordinates or color before returning the pixel. For example, Curtains.js’ post-processing example offsets the x texture coordinate with sine and cosine functions controlled by a time uniform, producing a moving distortion rather than changing the underlying HTML element.
5. Animate with a uniform
Update a time or interaction uniform from the render callback so the shader receives changing input. Keep this callback lean: onRender() runs on each draw call, so substantial per-frame JavaScript can undermine rendering performance. Prefer calculating only the values the shader needs and let GLSL perform the per-pixel work. The getting-started guide includes the shader and callback conventions for the version documented there.
Choose the right Curtains.js building block
Use the simplest architecture that meets the visual requirement. A Plane is usually enough for an effect on one image or video. Post-processing and iterative simulations add render work and complexity.
Rank #4
| Component | What it does | Use it when |
|---|---|---|
Plane |
A DOM-linked textured mesh. | You want to animate or distort an image, video, or canvas element. |
ShaderPass |
A full-canvas-style post-processing mesh; passes run in the order they are created. | You need an effect over a group of planes or the rendered scene. |
RenderTarget |
A framebuffer texture that can be consumed by a later plane or shader pass. | A later rendering stage needs to sample an earlier result. |
PingPongPlane |
Swaps between two render targets for iterative rendering. | You need feedback or simulation behavior, such as a flowmap or fluid-like effect. |
FXAAPass |
A fast approximate anti-aliasing pass. | You need to restore edge smoothing after using render targets or shader passes that disable default WebGL antialiasing. |
These components and their relationships are covered in the official Curtains.js documentation. A ShaderPass is not simply another DOM-tied Plane: it is part of the rendering pipeline and can affect multiple planes or the overall scene. A RenderTarget supplies an intermediate texture, while PingPongPlane alternates two such targets to carry state between iterations.
Keep effects responsive, especially on mobile
The official performance tips offer practical ways to reduce work, but they do not publish device-specific frame-rate guarantees. Measure the result on the phones and tablets your audience uses, particularly when combining video, large textures, and multiple passes.
- Use one global animation loop and one scroll listener. Avoid creating redundant per-plane loops or listeners.
- Stop drawing when the scene is idle. If no animation or interaction needs updates, pause scene rendering rather than continuously drawing unchanged frames.
- Consider a rendering scale below 1. Lower resolution can reduce GPU work, at the cost of some visual sharpness.
- Size media to the plane. Large source images and videos have a greater performance impact; scale them to the maximum size the plane needs.
- Manage canvas texture updates. Canvas textures update every frame. Set
shouldUpdatetofalsewhile a canvas texture is hidden or finished, and switch it back on before showing it again. - Keep shader passes and callbacks under control. Passes add rendering work, and per-frame JavaScript in
onRender()adds CPU work. The Curtains.js guide cautions that “Shader passes could be expensive, use them with caution.”
These recommendations are from the Curtains.js performance tips. They are optimization directions, not a substitute for testing the complete effect on target devices.
Best Value
Account for antialiasing and version differences
Render targets and shader passes disable default WebGL antialiasing. If edges look jagged after adding post-processing, set Curtains’ antialiasing to false and add an FXAAPass or another FXAA/MSAA implementation where appropriate. FXAA is a fast approximate option; it is not identical to hardware multisample antialiasing.
The official download page lists curtains.umd.js v8.1 and curtains.umd.min.js v8.1. It gives their distribution sizes as 346Ko and 122Ko, respectively; these are file sizes, not performance measurements. Check the version actually installed before copying examples: the documentation notes a major class refactor and versioned APIs, so code from an older release may not work unchanged. See the official download page and the relevant migration notes for your release.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

