Examples
Copy these into a Script macro. Each filter page also ends with an example for that filter.
These examples demonstrate the API and pass parameter validation. Their exact appearance and long-running stability have not all been checked in Foundry. Some effects intentionally darken, replace or hide the original image. Use a local preview to judge an effect on your target before saving it. The filter pages record individual visual checks.
Fire, Electric and Fog have confirmed GPU precision defects in their inherited shaders. Those require the respective highp shader fixes; changing example values alone does not reliably fix black or transparent output.
Toggle an effect
Run once to add, again to remove.
const id = "ward";
for (const token of canvas.tokens.controlled) {
if (leanFX.hasFilterId(token, id)) await leanFX.deleteFilters(token, id);
else await leanFX.addFilters(token, [{
filterType: "glow", filterId: id, color: 0x66ccff, outerStrength: 4, distance: 12,
animated: {outerStrength: {animType: "syncCosOscillation", loopDuration: 3000, val1: 2, val2: 5}}
}]);
}
Targeted tokens
for (const token of game.user.targets) {
await leanFX.addUpdateFilters(token, [{
filterType: "outline", filterId: "marked", color: 0xff2222, thickness: 2, quality: 0.5,
animated: {thickness: {animType: "syncCosOscillation", loopDuration: 800, val1: 1, val2: 3}}
}]);
}
A one-shot hit flash
A red flash that fades in and out over 600 ms, then removes itself.
const token = canvas.tokens.controlled[0];
await leanFX.addUpdateFilters(token, [{
filterType: "adjustment", filterId: "hit",
onComplete: "delete",
animated: {
red: {animType: "halfCosOscillation", loopDuration: 300, loops: 2, val1: 1, val2: 3},
green: {animType: "halfCosOscillation", loopDuration: 300, loops: 2, val1: 1, val2: 0.4},
blue: {animType: "halfCosOscillation", loopDuration: 300, loops: 2, val1: 1, val2: 0.4}
}
}]);
With two loops, a half-wave goes to val2 and back to val1, so the token ends unchanged.
A burning aura with a shield
Two filters on one token. zOrder decides which applies first.
await leanFX.addUpdateFiltersOnSelected([
{filterType: "xglow", filterId: "fire-aura", auraType: 2, color: 0x903010, thickness: 9.8, scale: 4,
auraIntensity: 2, subAuraIntensity: 1.5, threshold: 0.4, discard: true,
animated: {time: {animType: "move", speed: 0.0027}}},
{filterType: "field", filterId: "shield", shieldType: 2, gridPadding: 2, color: 0xcc00cc, blend: 3,
lightAlpha: 0.5, lightSize: 0.5, animated: {time: {animType: "move", speed: 0.0015}}}
]);
Water on a Region
Select the Region on the Regions layer first. The effect follows its geometry and stays visible when you return to Token Controls. This example draws above tiles and drawings, below tokens at the same elevation. Leave editor Visibility layer-only; see Region rendering.
const region = canvas.regions.controlled[0];
await leanFX.addUpdateFilters(region, [{
filterType: "flood", filterId: "water", color: 0x0020bb, billowy: 0.43, tintIntensity: 0.72, glint: 0.31, scale: 70,
animated: {time: {animType: "move", speed: 0.0006}}
}], {placement: "aboveTiles"});
Fog on a Tile
const tile = canvas.tiles.controlled[0];
await leanFX.addUpdateFilters(tile, [{
filterType: "fog", filterId: "fog", color: 0x000000, density: 0.65,
animated: {time: {animType: "move", speed: 2.2}}
}]);
Hovering
A slow up-and-down float, for flying tokens.
await leanFX.addUpdateFiltersOnSelected([{
filterType: "transform", filterId: "hover", padding: 30,
animated: {translationY: {animType: "sinOscillation", loopDuration: 3000, val1: -0.04, val2: 0.04}}
}]);
Try before saving
Shows an effect only to you for five seconds.
const token = canvas.tokens.controlled[0];
const preview = leanFX.preview(token, [{
filterType: "electric", filterId: "try", color: 0x99ccff, intensity: 3,
animated: {time: {animType: "move", speed: 0.002}}
}]);
setTimeout(() => preview.clear(), 5000);
Change one parameter
Updates merge, so only color changes; the animation keeps running.
await leanFX.updateFiltersOnSelected([{filterType: "glow", filterId: "ward", color: 0xffaa00}]);
Remove all effects from the scene
for (const layer of [canvas.tokens, canvas.tiles, canvas.drawings, canvas.regions]) {
for (const placeable of layer.placeables) {
if (leanFX.getFilters(placeable).length) await leanFX.deleteFilters(placeable);
}
}
Check why an effect does not show
const token = canvas.tokens.controlled[0];
console.log(leanFX.getFilters(token)); // what is saved
console.log(leanFX.inspect(token)); // what is drawn, and errors (bad image path, shader failure, ...)
Use leanFX from a module
Hooks.once("ready", () => {
const fx = game.modules.get("leanFX")?.api;
if (!fx) return; // leanFX not active
Hooks.on("targetToken", (user, token, targeted) => {
if (user !== game.user || !token.document.isOwner) return;
const spec = {filterType: "outline", filterId: "my-module-target", color: 0xffcc00, thickness: 2};
if (targeted) fx.addUpdateFilters(token, [spec]);
else fx.deleteFilters(token, spec.filterId);
});
});
Prefix your filter IDs with your module's name so they don't collide with the user's own effects.