@propagande/utils
v0.0.45
Published
PROPAGANDE utils.
Readme
Propagande Utils Documentation
This documentation covers the core utilities and Vue composables provided by the @propagande/utils repository.
Installation
On npm, the public package is published under the propagande organization:
npm install @propagande/utils vue gsapimport { P } from "@propagande/utils";
import { useFrame } from "@propagande/utils/voir";
import { gsap } from "@propagande/utils/gsap";GitHub Packages remains available as @propagande-studio/utils for existing projects.
Table of Contents
- Shared Package (
@propagande/utils/shared) - Voir Package (
@propagande/utils/voir) - GSAP Package (
@propagande/utils/gsap)
Shared Package
Accessible via import { P } from "@propagande/utils".
Math Utilities
| Function | Description | Usage |
| --------------------------------- | --------------------------------------------------- | -------------------------------- |
| Lerp(xi, xf, t) | Linear interpolation between two values. | P.Lerp(0, 100, 0.5) // 50 |
| rLerp(start, end, alpha) | Rotational interpolation (useful for angles). | P.rLerp(0, Math.PI, 0.1) |
| Damp(x, y, lambda, delta) | Smooth damping effect (framerate independent). | P.Damp(current, target, 8, dt) |
| Clamp(x, min, max) | Constrains a number between a min and max value. | P.Clamp(150, 0, 100) // 100 |
| map(x, s1, e1, s2, e2, options) | Maps a value from one range to another. | P.map(0.5, 0, 1, 0, 100) // 50 |
| Round(x, decimal) | Rounds a number to a specific decimal precision. | P.Round(1.234, 2) // 1.23 |
| mod(n, m) | Correct modulo operator (handles negative numbers). | P.mod(-1, 5) // 4 |
Array Utilities
| Function | Description | Usage |
| ----------------------- | ------------------------------------------------ | ---------------------------------------- |
| Arr.create(length) | Creates an array of N elements (range 0 to N-1). | P.Arr.create(5) // [0, 1, 2, 3, 4] |
| Arr.shuffle(arr) | Shuffles an array in place. | P.Arr.shuffle([1, 2, 3]) |
| Arr.rand(arr) | Returns a random element from an array. | P.Arr.rand([1, 2, 3]) |
| Arr.shift(arr, idx) | Rotates array so it starts at idx. | P.Arr.shift([1, 2, 3], 1) // [2, 3, 1] |
| Arr.spliceNth(arr, n) | Removes and returns every Nth element. | P.Arr.spliceNth([1, 2, 3, 4], 2) |
Random Utilities
| Function | Description | Usage |
| ---------------------- | ----------------------------------------- | ------------------------ |
| Rand.int(max) | Random integer between 0 and max-1. | P.Rand.int(10) |
| Rand.int(min, max) | Random integer between min and max-1. | P.Rand.int(5, 10) |
| Rand.range(min, max) | Random float between min and max. | P.Rand.range(0.5, 1.5) |
Class Utilities
| Function | Description | Usage |
| ------------------------ | -------------------- | ------------------------------- |
| Class.add(el, name) | Adds a CSS class. | P.Class.add(div, 'active') |
| Class.remove(el, name) | Removes a CSS class. | P.Class.remove(div, 'active') |
| Class.toggle(el, name) | Toggles a CSS class. | P.Class.toggle(div, 'active') |
Ticker
The Ticker provides a centralized requestAnimationFrame loop.
getTicker(): Returns the ticker instance.ticker.add(fn, priority, once): Add a callback.ticker.start() / ticker.stop(): Manage the loop.
Viewport
A singleton to track window size, device info, and breakpoints.
getViewport(): Returns the viewport instance.- Properties:
viewport.width,height,isMobile,iOS. viewport.add(fn, priority, noThrottle, immediate): Listen for resizes.
Throttle & Debounce
Useful for limiting executions on high-frequency events.
throttle(delay, callback, options)debounce(delay, callback, options)
Voir Package
Composables designed for Vue 3 projects, handling everything from physical animations to client-side lifecycle safety.
Animation & Motion
useDampedValue
- What it does: Smoothly interpolates a value towards a target using framerate-independent damping.
- When to use: Smooth transitions for numbers, like cursor tracking or progress bars.
- Example:
<script setup>
import { ref } from "vue";
import { useDampedValue } from "@propagande/utils/voir";
const target = ref(0);
const { damped } = useDampedValue({ target, lambda: 8 });
// When you change target.value, damped.value will smoothly follow.
</script>useSpring
- What it does: Simulates physical spring motion with stiffness and damping.
- When to use: Playful, bouncy UI elements or realistic physics.
- Example:
<script setup>
const target = ref(0);
const { damped: position, velocity } = useSpring({
target,
stiffness: 100,
damping: 10,
});
</script>useFrame
- What it does: A hook that executes a callback on every ticker update (usually 60fps).
- When to use: Custom Canvas/WebGL rendering or manual DOM animations.
- Example:
<script setup>
import { useFrame } from "@propagande/utils/voir";
useFrame(({ et, dt }) => {
console.log(`Elapsed: ${et}, Delta: ${dt}`);
});
</script>[!TIP] You can pass
observedElementtouseFrameto only run the animation when the element is visible on screen.
GSAP Integration
useGSAPContext
- What it does: Wraps GSAP animations in a context that is automatically cleaned up on unmount.
- When to use: Every time you create a GSAP animation in a component.
- Example:
<script setup>
import { useGSAPContext } from "@propagande/utils/voir";
import { gsap } from "@propagande/utils/gsap";
useGSAPContext((ctx) => {
gsap.to(".box", { x: 100 });
});
</script>useGSAPMatchMedia
- What it does: Responsive GSAP animations using global breakpoints.
- Example:
<script setup>
useGSAPMatchMedia(
(ctx) => {
const { isLg } = ctx.conditions;
gsap.to(".box", { x: isLg ? 500 : 100 });
},
{ conditions: ["isLg"] },
);
</script>Pass only the conditions the animation uses. The callback then reruns when one of those conditions changes, without rebuilding at unrelated breakpoints.
isPointerFine can be included for hover-dependent animations. Omitting conditions keeps the existing behavior of watching every configured breakpoint and
pointer capability. The second argument can still be a boolean to control context revert on unmount, or an options object with revert and conditions.
Viewport & Resize
| Composable | Description | Simple Example |
| -------------------- | ------------------------------------- | ------------------------------------- |
| useSize() | Reactive window dimensions. | const { width, height } = useSize() |
| useScroll() | Reactive window scroll position. | const scroll = useScroll() |
| useIsLg() | Boolean for "Large" breakpoint. | const isMobile = !useIsLg().value |
| useIsPointerFine() | Check if user has a mouse (vs touch). | const canHover = useIsPointerFine() |
Lifecycle & Utilities
useSafeClient
- What it does: Ensures a callback only runs when the browser/DOM is available.
- When to use: SSR apps (Nuxt) to prevent "window is not defined" errors.
const result = useSafeClient(() => {
return new SomeBrowserPlugin();
});createSharedComposable
- What it does: Shares composable state in the browser until the last subscriber's scope is disposed. On the server, each call creates its own state so separate SSR requests cannot share cached values.
- When to use: Tracking browser data like window size or scroll position while avoiding redundant event listeners. Use request-scoped state for authentication and user sessions.
- Example:
// sharedComposable.js
import { onScopeDispose, ref } from "vue";
import { createSharedComposable, useSafeClient } from "@propagande/utils/voir";
export const useSharedScroll = createSharedComposable(() => {
const scrollY = ref(0);
useSafeClient(() => {
const onScroll = () => {
scrollY.value = window.scrollY;
};
window.addEventListener("scroll", onScroll, { passive: true });
onScopeDispose(() => window.removeEventListener("scroll", onScroll));
});
return scrollY;
});
// ComponentA.vue & ComponentB.vue
// In the browser, both receive the same scrollY ref.
const scrollY = useSharedScroll();During SSR, calls receive independent refs. This helper does not provide per-request session storage.
useWatchOnce
- What it does: A
watchthat automatically stops itself after the first trigger. - Example:
useWatchOnce(someRef, (val) => {
console.log("Triggered only once!");
});GSAP Package
Accessible via @propagande/utils/gsap, which exports GSAP, its plugins, and animation helpers.
SplitTextAnimator
SplitTextAnimator(target, config) creates one GSAP SplitText instance and adds animate() and clearAnimations() methods.
Use it when the same text needs several animations, such as an entrance and an exit in a timeline, while keeping those animations attached to the current split elements.
When SplitText splits text again, it replaces the generated line, word, or character elements. Animations created against the previous elements would keep targeting those old elements.
SplitTextAnimator kills and rebuilds each registered animation with the new elements, reinserts it at its original position in its parent timeline, and preserves its paused state.
It always enables autoSplit; with type including "lines", SplitText automatically re-splits when the element's width changes or fonts finish loading.
Create animations and add them to a timeline
Run this in the browser after the target element exists. Register the SplitText plugin before creating the helper.
import { gsap, SplitText, SplitTextAnimator } from "@propagande/utils/gsap";
gsap.registerPlugin(SplitText);
const split = SplitTextAnimator(".headline", {
type: "words,lines",
mask: "lines",
});
const timeline = gsap.timeline({ paused: true });
timeline.add(
split.animate((self) => {
return gsap.from(self.words, {
yPercent: 100,
opacity: 0,
duration: 0.6,
stagger: 0.05,
ease: "power2.out",
});
}),
);
timeline.add(
split.animate((self) => {
return gsap.to(self.words, {
yPercent: -100,
opacity: 0,
duration: 0.4,
stagger: 0.03,
ease: "power2.in",
});
}),
"+=1",
);
timeline.play();Each animate(create) callback must return a GSAP tween or timeline. It runs immediately and again after each split, so always read self.words, self.lines, or self.chars inside the callback.
Create the animation there instead of caching split elements outside it. animate() returns the initial animation so it can be added to a timeline; the helper manages its replacements after subsequent splits.
Other SplitText options can be passed through config. A supplied onSplit(self) callback runs before the registered animations are rebuilt, which is useful for preparing styles on the new elements.
For TypeScript consumers, the return type is also exported as SplitTextAnimatorInstance.
Replace animations or clean up
clearAnimations() kills all registered animations and removes their rebuild callbacks while keeping the split text alive. Use it before registering a new transition on the same instance:
split.clearAnimations();
split.animate((self) => gsap.to(self.words, { opacity: 1, duration: 0.3 }));When disposing the text animation, stop its parent timeline, clear the registered animations, and restore the original markup:
timeline.kill();
split.clearAnimations();
split.revert();License
Copyright (c) 2026 PROPAGANDE. All rights reserved. This package is proprietary; see LICENSE.
Development
Use the Bun version declared in packageManager and install with bun install --frozen-lockfile --ignore-scripts.
Run bun run test and bun run build to verify changes.
Use bun run audit to check dependencies when needed.
