The createSplitClones() function from griffo/helpers creates clone layers from split text output. This is useful for advanced reveal effects, swap animations, and layered text transitions.
Import
import { createSplitClones } from "griffo/helpers";
Basic usage
import { splitText } from "griffo";
import { createSplitClones } from "griffo/helpers";
import { animate, stagger } from "motion";
const split = splitText(element, { type: "chars", mask: "chars" });
const layers = createSplitClones(split, { unit: "chars", wrap: true });
// Animate originals and clones
animate(
layers.originals,
{ y: ["0%", "-100%"] },
{ delay: stagger(0.02) }
);
animate(
layers.clones,
{ y: ["100%", "0%"] },
{ delay: stagger(0.02) }
);
Function signature
function createSplitClones(
split: SplitTextResult,
options: CreateSplitClonesOptions
): CreateSplitClonesResult
Parameters
The result object from a splitText() call.const split = splitText(element, { type: "chars" });
const layers = createSplitClones(split, { unit: "chars" });
options
CreateSplitClonesOptions
required
Configuration for clone creation.
Options
options.unit
'chars' | 'words' | 'lines'
required
Which split unit to clone.createSplitClones(split, { unit: "chars" })
createSplitClones(split, { unit: "words" })
createSplitClones(split, { unit: "lines" })
Whether to wrap each original element in a track container.createSplitClones(split, {
unit: "chars",
wrap: true // Creates track wrappers with position: relative
})
When true, each original is wrapped in a <span> with position: relative, and clones are positioned absolutely within these tracks.
options.display
'auto' | 'inline-block' | 'block'
default:"'auto'"
Display style for track wrappers (only applies when wrap: true).
"auto" - Uses inline-block for chars/words, block for lines
"inline-block" - Force inline-block
"block" - Force block
createSplitClones(split, {
unit: "words",
wrap: true,
display: "block"
})
Position offset for clones relative to originals.createSplitClones(split, {
unit: "chars",
wrap: true,
cloneOffset: {
axis: "y", // "x" | "y"
direction: "start", // "start" | "end"
distance: "100%" // Any CSS length
}
})
Properties:
axis - Offset direction ("x" horizontal, "y" vertical)
direction - Offset side ("start" negative, "end" positive)
distance - Offset amount (CSS length)
Defaults: { axis: "y", direction: "start", distance: "100%" }
CSS class for track wrappers. Can be a string or function.// Static
trackClassName: "clone-track"
// Function
trackClassName: ({ index, unit, original }) =>
`track-${index}`
CSS class for clone elements.cloneClassName: ({ index }) => `clone clone-${index}`
Inline styles for track wrappers.// Static
trackStyle: { overflow: "hidden" }
// Function
trackStyle: ({ index }) => ({
overflow: "hidden",
color: colors[index]
})
Inline styles for clone elements.cloneStyle: { color: "blue" }
Return value
The unit type that was cloned.
Array of original split elements.
Array of cloned elements.
Array of track wrapper elements (empty if wrap: false).
Array of objects containing:{
index: number;
original: HTMLSpanElement;
clone: HTMLSpanElement;
track: HTMLSpanElement | null;
}
Function to remove clones and tracks.layers.cleanup(); // Remove clones only
layers.cleanup({ revertSplit: true }); // Remove clones and revert split
Examples
Vertical reveal
import { splitText } from "griffo";
import { createSplitClones } from "griffo/helpers";
import { animate, stagger } from "motion";
const split = splitText(element, { type: "words", mask: "words" });
const { originals, clones, cleanup } = createSplitClones(split, {
unit: "words",
wrap: true,
cloneOffset: {
axis: "y",
direction: "start",
distance: "100%"
}
});
// Slide originals up and out
animate(originals, { y: ["0%", "-100%"] }, { delay: stagger(0.03) });
// Slide clones up into view
animate(clones, { y: ["100%", "0%"] }, { delay: stagger(0.03) }).finished.then(() => {
cleanup({ revertSplit: true });
});
Horizontal swap
const split = splitText(element, { type: "chars" });
const { originals, clones, cleanup } = createSplitClones(split, {
unit: "chars",
wrap: true,
cloneOffset: {
axis: "x",
direction: "end",
distance: "100%"
},
cloneStyle: { color: "blue" }
});
animate(originals, { x: ["-100%"], opacity: [0] }, { delay: stagger(0.02) });
animate(clones, { x: ["100%", "0%"], opacity: [0, 1] }, { delay: stagger(0.02) });
Custom styling per clone
const colors = ["red", "orange", "yellow", "green", "blue"];
const layers = createSplitClones(split, {
unit: "chars",
wrap: true,
cloneStyle: ({ index }) => ({
color: colors[index % colors.length]
})
});
Type definitions
type SplitUnit = "chars" | "words" | "lines";
type StyleInput =
| Partial<CSSStyleDeclaration>
| ((ctx: {
index: number;
unit: SplitUnit;
original: HTMLSpanElement;
}) => Partial<CSSStyleDeclaration>);
type ClassInput =
| string
| ((ctx: {
index: number;
unit: SplitUnit;
original: HTMLSpanElement;
}) => string | undefined);
interface CreateSplitClonesOptions {
unit: SplitUnit;
wrap?: boolean;
display?: "auto" | "inline-block" | "block";
cloneOffset?: {
axis?: "x" | "y";
direction?: "start" | "end";
distance?: string;
};
trackClassName?: ClassInput;
cloneClassName?: ClassInput;
trackStyle?: StyleInput;
cloneStyle?: StyleInput;
}
interface SplitCloneItem {
index: number;
original: HTMLSpanElement;
clone: HTMLSpanElement;
track: HTMLSpanElement | null;
}
interface CreateSplitClonesResult {
unit: SplitUnit;
originals: HTMLSpanElement[];
clones: HTMLSpanElement[];
tracks: HTMLSpanElement[];
items: SplitCloneItem[];
cleanup: (options?: { revertSplit?: boolean }) => void;
}
Notes
- Clones are inserted as siblings of the originals
- When
wrap: true, both original and clone are positioned within the track wrapper
- The
cleanup() function should be called when animations complete to remove clones and tracks
- Pass
{ revertSplit: true } to cleanup to also revert the original split