# VFX-JS attaches a WebGL pipeline to an ordinary img tag

> VFX-JS is a TypeScript library that gives a page WebGL powered image and video effects, attached to normal elements rather than to a canvas you manage yourself. The core package is small, the effects come from a separate package, and the whole thing is a pnpm and turbo monorepo with changesets for releases.

**fand/vfx-js** — WebGL effects made easy.

- Repository: https://github.com/fand/vfx-js
- Website: https://amagi.dev/vfx-js
- Stars: 1,168 · Forks: 38
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/fand-vfx-js

## Three lines to give an image a shader

VFX-JS is a JavaScript library to add WebGL powered effects to your website, and the pitch in the project description is that you can easily attach it to normal img and video elements.

The mechanism matters here. A typical WebGL effect implementation asks you to create a canvas, copy a texture into it, manage a render loop, and keep the canvas sized to the element. VFX-JS inverts that. You keep your img tag, you keep your CSS, your alt text and your layout, and the library takes over the rendering of that element. The element stays in the document flow and stays accessible, and the pixels change.

The usage in the documentation is three lines of real code. You import the VFX class, you select the element, you create a VFX object and add the element to it with a shader name and an option:

```js
import { VFX } from '@vfx-js/core';

const img = document.querySelector('#img');

const vfx = new VFX();
vfx.add(img, { shader: "glitch", overflow: 100 });
```

The options object carries a shader name and a numeric parameter, and the naming of overflow suggests the effect can push pixels outside the element bounds, which is what a glitch effect needs. There is also a separate package of React bindings, @vfx-js/react, for teams who do not want to touch the DOM directly.

The audience is front end developers and creative coders who want a shader on a hero image or a glitch transition on a video, and who would rather not build the WebGL plumbing themselves. The topic list for the repository is a good summary of the territory, with glsl, shaders, post-processing, image effects, video effects, creative-coding and react all present.

## The core package and the effects package are separate

The distribution is split into packages, and the split is a design decision rather than an accident of publishing.

@vfx-js/core is the engine. It contains the VFX class and the mechanism for attaching an effect to an element, and it is the only package you need if you are going to write your own GLSL. The install command in the documentation is simply:

```bash
npm i @vfx-js/core
```

@vfx-js/effects is a separate package of prebuilt effects that you compose on top of the core. The documentation describes it as a place to compose prebuilt effects from, and the example uses two of them, a pixelate effect and a bloom effect:

```js
import { VFX } from '@vfx-js/core';
import { BloomEffect, PixelateEffect } from '@vfx-js/effects';

const vfx = new VFX();
vfx.add(img, {
    effect: [new PixelateEffect({ size: 10 }), new BloomEffect({ intensity: 5 })],
});
```

Two things are readable from that snippet. The effects take constructor options, a size for the pixelate effect and an intensity for the bloom effect, so they are configured objects rather than named presets with a fixed look. And the effect property takes an array, which means effects compose in order, and the order in the array is the order they are applied.

The third package, @vfx-js/react, provides React bindings, so a React team can use the same engine through components.

The reason to split them is straightforward. A team that wants one custom GLSL shader should not pull in a library of prebuilt effects, and a team that wants a set of ready made effects should not have to write GLSL. The core is the dependency; the effects and the React bindings sit on top of it.

## Effects are attached per element, and the same instance can hold several

The API shape is worth understanding because it determines how you structure the code.

A VFX object is the owner. You create it once, and you add elements to it. The name of the method, add, suggests a one to many relationship, and the options are per element, so two elements added to the same instance can carry different shaders and different parameters. That is the pattern for a page with a hero image that gets a glitch effect and a thumbnail grid that gets a pixelate effect.

Two option styles appear in the documentation. The first is a shader name string plus a numeric option, which is the shorthand for a single built in or named effect. The second is an effect array of constructed effect objects, which is the general form. Both are passed as the second argument to add, so the same method covers both cases.

What the documentation does not tell you is as informative as what it does. There is no mention of removing an element, of pausing an effect, of the render loop and how it is driven, of what happens on a device that cannot create a WebGL context, of whether the original element is replaced in the DOM or rendered into an overlay, or of how the library behaves with CSS transforms on the parent. The documentation points to the VFX-JS website for basic examples and to the examples page for more advanced use cases, which is where those details are meant to live.

So the honest reading is that the public surface is intentionally small and the reference material is on the site rather than in the repository README. If you need to know the lifecycle rules, that is the first place to look, not the readme.

## A pnpm and turbo monorepo with changesets and biome

The repository is a monorepo, and the tooling choices are modern and worth reading if you plan to contribute.

The root package.json is private, is named vfx-js-monorepo, and pins the package manager as pnpm@10.33.2. The workspace is described in pnpm-workspace.yaml with a pnpm-lock.yaml at the root, and the packages live under a packages directory, with the documentation site under docs and a packages/docs folder referenced by the og image in the readme. The task graph is described in turbo.json, and the scripts are thin wrappers over it: build runs turbo run build, dev runs turbo run dev with the tui interface, and there are matching lint, test and clean tasks.

Releases are handled with changesets. The prepare-release script runs changeset, then turbo run build lint test, then changeset version, and the release script runs changeset publish. The changesets CLI is a dependency of the root package, and the .changeset directory at the top of the repository is where the pending changes are recorded. That is a clean setup: a release only happens after the build, the lint and the test tasks pass, and the version bump is generated rather than hand edited.

The rest of the tooling is small and current. Biome handles formatting and linting through a single biome.json, husky installs the git hooks, and there are editor configurations for VS Code and Zed, a CLAUDE.md at the root, a plan.md, and a CONTRIBUTING.md.

The monorepo also means the packages version independently. That is what you want for a core library and an effects collection, and it is also the thing to check when you pin versions, because @vfx-js/core and @vfx-js/effects are released as their own versions and can drift apart.

## The release history is short and the versions are independent

The recent release list is small enough to read in full. @vfx-js/core 1.0.0 on 2026-05-17, then @vfx-js/core 1.1.0 and @vfx-js/effects 1.1.0 both on 2026-06-08. The repository is not archived and the last push was on 2026-10-19, so there is work on the branch that is not in a published package yet.

The pattern suggests a project that reached 1.0 in the spring of 2026 and has been adding to it since, with the first stable 1.x releases of both the core and the effects packages landing within three weeks of each other. The React bindings package is not in the release list at all, which means either it has not been published under a 1.x tag or it is versioned differently.

The licence is MIT, stated in the readme and in the LICENSE file at the root of the repository, and it is also the value in the license field of the root package.json.

For a consumer, the practical implications are simple. The library is young in version terms, which means the API may still move, and it is small, which means there is not much surface to learn. The upgrade path is a normal npm update, and because the packages are separate you can take a new core without taking a new effects package if the two are coupled in a way that matters to you.

The documentation is the part to budget time for. The readme is a summary, the site is where the examples live, and the examples page is explicitly where the advanced use cases are, so an evaluation that stops at the readme will miss most of what the library can do.

## Where a CSS filter would have been enough

The honest limitation is that this is a WebGL pipeline, and a WebGL pipeline is the wrong tool for a large class of tasks.

CSS filters, and the SVG filter primitives, already cover blur, brightness, contrast, grayscale, sepia and hue rotation with a single declaration and no JavaScript. If the effect you want is one of those, this library is more machinery than the job needs, and you pay for it in a GPU context, a render loop and a dependency.

The place the library earns its keep is where CSS cannot go, which is per pixel work driven by GLSL: the glitch effect in the readme example, the pixelate effect, and the bloom effect, all of which are effects on the contents of the image rather than a filter over it.

There is a second limitation to keep in mind, and it comes from the format of the project rather than from the code. This is a client side effect library, so it runs on the user's GPU. A page with many large images and video elements running live effects is asking the client device to do real work, and the readme does not discuss performance, frame rate or how the library behaves under load, so a team deploying this on a page of heavy media has no guidance from the project on what to expect.

The third limitation is the one you can read between the lines of the documentation. There is no documented fallback for a browser without WebGL, and there is no documented accessibility story for an effect that changes the visual content of an image, for instance for a user who needs the original photograph. Those are questions for the site documentation, and they are questions worth asking before you put a glitch transition on a page that carries meaning.

## Conclusion

VFX-JS is worth adopting for a marketing or editorial site that wants per pixel treatment on images and video, because the API is small enough to learn in an afternoon and it works on elements already in the page rather than requiring a canvas rewrite. It is the wrong choice if CSS filters would do, if you need to support browsers without WebGL, or if you want a general purpose 3D engine rather than an effect pipeline. Before you adopt it, check the package layout in the repository, because the effect, core and React code live in separate packages that are versioned and released independently, with @vfx-js/core at 1.1.0 and @vfx-js/effects at 1.1.0 as of 2026-06-08 while the repository itself was pushed to on 2026-10-19.

## FAQ

### What is VFX-JS and what does it do?

VFX-JS is a JavaScript and TypeScript library that adds WebGL powered effects to a website, attaching them to normal img and video elements rather than to a canvas you manage yourself. The VFX class owns the instance and elements are added to it with a shader name or a list of effect objects.

### How do I install VFX-JS and apply a shader to an image?

Run npm i @vfx-js/core, import the VFX class, select the element, then create a VFX instance and add the element to it. The readme example is vfx.add(img, { shader: "glitch", overflow: 100 }).

### What is the difference between @vfx-js/core and @vfx-js/effects?

The core package holds the VFX engine and is all you need for your own GLSL. The effects package contains prebuilt effects such as BloomEffect and PixelateEffect, which take constructor options and can be composed in an array passed to the effect property of the add call.

### Does VFX-JS have React support?

Yes. The documentation points to a separate package, @vfx-js/react, which provides React bindings for the same engine, for teams who would rather not manipulate the DOM directly.

### How are VFX-JS releases made?

The repository is a pnpm and turbo monorepo that uses changesets. The prepare-release script runs changeset, then turbo run build lint test, then changeset version, and the release script runs changeset publish, so a release only happens after the build, lint and test tasks pass.

## Sources

- [fand/vfx-js on GitHub](https://github.com/fand/vfx-js)
- [License: MIT](https://github.com/fand/vfx-js/blob/main/LICENSE)
- [Project website](https://amagi.dev/vfx-js)
- [README](https://github.com/fand/vfx-js/blob/main/README.md)
- [Releases](https://github.com/fand/vfx-js/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fand-vfx-js
