Skip to content

Conversation

Zyie
Copy link
Member

@Zyie Zyie commented Jun 24, 2025

Adds the ability to split Text and BitmapText objects into characters, words, and lines.
This provides a more granular control over text elements, allowing for advanced animations and effects.

We now have two new classes SplitText and SplitBitmapText. I decided to split these up to allow for as much tree shaking to be preserved as possible. If we went with the one class approach you would always be bundling Text and BitmapText.

These classes also support dynamic updates to styles and text. Changing text does mean we destroy all the children and make them again. Changes to style are safe as we just update the position/style without needing to create a new instance.

examples

Split Existing Text

const result = SplitBitmapText.from(myBitmapText);
const result = SplitBitmapText.from(myBitmapText, {...));

New Instance

const result = new SplitBitmapText({
  text: 'Hello World',
  style: {...},
  lineAnchor: 0.5,  // sets origin to be centered for easy animations
  wordAnchor: 0.5,
  charAnchor: 0.5,
  autoSplit: true
});

Working with Results

const result = SplitBitmapText.from(myBitmapText);

// Animate characters
result.chars.forEach((char, i) => {
  char.alpha = 0;
  // Fade in each character sequentially
  gsap.to(char, {
    alpha: 1,
    delay: i * 0.1
  });
});

demo: https://codesandbox.io/p/sandbox/pixi-js-sandbox-forked-chgp88
demo2: https://codesandbox.io/p/devbox/pixi-js-sandbox-forked-yly7k3
demo3: https://codesandbox.io/p/devbox/pixi-js-sandbox-forked-j4swyl
demo4: https://codesandbox.io/p/devbox/pixi-js-sandbox-forked-sfny96
demo5: https://codesandbox.io/p/devbox/pixi-js-sandbox-forked-cctmf8

Copy link

codesandbox-ci bot commented Jun 24, 2025

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit c7bf3bf:

Sandbox Source
pixi.js-sandbox Configuration

@bigtimebuddy
Copy link
Member

This is a very cool idea and well documented! Excellent bar of quality as usual @Zyie.

Would you mind creating a quick demo (maybe with animation) to showcase this?

I have one general concern which is about destroying lifecycle. Since the split result is a generic object, I don't see an easy way to destroy the extra text elements and containers. Our contract generally is: if Pixi instantiates it, Pixi destroys it. How would a user do that here? Also replace removes it from the display tree, but does not destroy, right?

Another API approach might be to introduce a new display class to manage the element more directly (maybe that extends Container). Something like TextAnimator, TextSegmenter. This gives us more flexibility in the future and maintain the current contract with objects created and objects destroyed.

const myText = new Text({
  text: 'Hello World',
  style: {},
});

const seg = new TextSegmenter({
  element: myText,
  replace: true,
});

seg.chars.forEach((char, i) => {
  char.alpha = 0;
  // Fade in each character sequentially
  gsap.to(char, {
    alpha: 1,
    delay: i * 0.1
  });
});

seg.destroy();

@Zyie
Copy link
Member Author

Zyie commented Jun 25, 2025

Would you mind creating a quick demo (maybe with animation) to showcase this?

Here is a demo link:
https://codesandbox.io/p/sandbox/pixi-js-sandbox-forked-chgp88
https://codesandbox.io/p/devbox/pixi-js-sandbox-forked-yly7k3

I have one general concern which is about destroying lifecycle. Since the split result is a generic object, I don't see an easy way to destroy the extra text elements and containers. Our contract generally is: if Pixi instantiates it, Pixi destroys it. How would a user do that here?

So the result from Text.split returns an object like this:

interface Result {
  container: Container;
  lines: Container[];
  words: Container[];
  chars: Text[];
}

So to destroy the new text you would use result.container.destroy(true).

Also replace removes it from the display tree, but does not destroy, right?

That is correct. It doesn't destroy the original text. My thinking was that you might want to split the text up for an animation, then swap it back for the original.

Another API approach might be to introduce a new display class to manage the element more directly (maybe that extends Container). Something like TextAnimator, TextSegmenter. This gives us more flexibility in the future and maintain the current contract with objects created and objects destroyed.

const seg = new TextSegmenter({
  element: myText,
  replace: true,
});
seg.destroy();

I'm not against this approach either. It aligns with what i had in mind originally before going down the current path.
@GoodBoyDigital any thoughts?

Also just as a note that this is all inspired by the gsap plugin that does this for html text: https://gsap.com/docs/v3/Plugins/SplitText/

@Zyie
Copy link
Member Author

Zyie commented Jun 25, 2025

Me and Mat had a quick chat about this today and agree that we should go down the TextSegmenter route. I'll update the PR accordingly!

@bigtimebuddy
Copy link
Member

Cool. The benefit of an explicit class destroy is that it will also handle all the dereferencing for lines, words, chars arrays.

Zyie added 2 commits June 26, 2025 12:26
Adds an 'origin' property to the container, providing a way to specify the point around which the container rotates and scales without affecting its position.

This allows for more flexible and intuitive control over container transformations, particularly when rotating or scaling around a specific point.
@Zyie Zyie changed the base branch from dev to feat/origin June 27, 2025 08:59
@Zyie
Copy link
Member Author

Zyie commented Jun 27, 2025

@bigtimebuddy ready for another review

Copy link
Member

@bigtimebuddy bigtimebuddy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. I love the naming choices here. This experimental feature is enough functionality to get some feedback from developers.

Base automatically changed from feat/origin to dev July 3, 2025 08:44
Copy link
Member

@GoodBoyDigital GoodBoyDigital left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good!

Zyie and others added 2 commits July 3, 2025 15:24
@Zyie Zyie added this pull request to the merge queue Jul 3, 2025
Merged via the queue into dev with commit 6b2c304 Jul 3, 2025
5 checks passed
@Zyie Zyie deleted the feat/split-text branch July 3, 2025 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants