Skip to content

feat: animation effects and @streamdown/effects (diffuse, scramble) - #626

Open
thedonmon wants to merge 12 commits into
vercel:mainfrom
thedonmon:feat/animation-effects
Open

thedonmon wants to merge 12 commits into
vercel:mainfrom
thedonmon:feat/animation-effects

Conversation

@thedonmon

@thedonmon thedonmon commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Description

Lets animated.animation take an effect object, and adds an @streamdown/effects package with two effects:

  • diffuse: words sharpen out of a blur, in scattered order.
  • scramble: words start as noise glyphs and resolve to the real text, in scattered order.
import { scramble } from "@streamdown/effects";
import "@streamdown/effects/styles.css";

<Streamdown smooth animated={{ animation: scramble }} isAnimating={isStreaming}>
  {markdown}
</Streamdown>

Type of Change

  • New feature (non-breaking change which adds functionality)

Related Issues

Depends on #619 and #625. This branch includes their commits, so it stays a draft until they merge. Without #619, a re-render resets words that are still animating, and smooth re-renders every few frames, so the effects never show.

Changes Made

  • Core: AnimationEffect is { name, duration?, scatter?, decorate? }. scatter adds a stable per-word delay for a scattered order. decorate adds data-* attributes while a word animates; other attribute names are dropped. String animation names work as before.
  • New @streamdown/effects package with no dependency on streamdown.
  • scramble keeps the real text in the DOM, so copy and screen readers get real words. It falls back to a fade with prefers-reduced-motion.
  • Docs and changesets.

Testing

Screendrop_2026-09-19-09-40-08_27B738.mp4
  • All existing tests pass
  • Added new tests for the changes
  • Manually tested the changes

Checked both effects in Chrome with smooth on headings, lists, tables, inline code, strikethrough and math.

Additional Notes

This adds a new published package. If you'd rather not, the two effects could live in core instead.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

@thedonmon is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

✅ All commits are signed

All 12 commit(s) in this pull request have a verified signature. Thanks!

Checked at 550c9cb.

rogersnm and others added 11 commits October 1, 2026 20:14
Some providers and relays deliver text in bursts (for example ~150
characters every ~500 ms), which renders as a jump followed by a pause,
with the caret jumping ahead. `smooth` measures the time between arrivals
and reveals each chunk a word at a time so it finishes about when the
next chunk is expected.

- Off by default; rendering is unchanged when off.
- Only appends are paced. Resets, edits and full-text replacements render
  immediately, as does content present on mount.
- Words split across chunks are held until complete. CJK characters are
  their own units.
- When `isAnimating` goes false the remainder drains within 250 ms, and
  Streamdown stays in streaming mode until then.
- Hidden tabs render immediately, since animation frames are paused there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`smooth` already spaces words out as it reveals them. The animate
plugin's default 40 ms per-word stagger then queued them a second time,
so animated text trailed the reveal and left blank space (with the caret)
where scheduled words had not faded in yet. Under `smooth`, `stagger` now
defaults to 0; an explicit `stagger` is still honored.

Also narrow `animated` by type instead of comparing the memo key to
"true", since the key now includes `smooth`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An `AnimationEffect` bundles a keyframes name with the per-word behavior
those keyframes need, so effects can ship as plugins instead of core
presets:

- `scatter`: extra delay per word, hashed from its position, for a stable
  scattered reveal order. Stored with the word's timing, so re-renders
  keep it.
- `decorate`: `data-*` attributes for each word while it animates, for
  effect styles to draw. Other attribute names are dropped, since the
  animate plugin runs after sanitization.
- `duration`: a default that `animated.duration` overrides.

String animation names are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffusion-style effects for `animated.animation`:

- `diffuse`: words sharpen out of a blur, in scattered order.
- `scramble`: words start as same-shape noise glyphs that change once and
  resolve to the real text. The real text stays in the DOM (painted
  transparent) and the glyphs are pseudo-elements with empty alt text,
  so copy and assistive tech get real words. Falls back to a fade under
  prefers-reduced-motion.

Effects are plain objects plus a stylesheet; the package has no
dependency on streamdown.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This repo reserves minor for marketing releases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolve pnpm-lock.yaml by taking main's lockfile and adding only the
@streamdown/effects importer. Match main's plugin packages by moving
@streamdown/effects to vitest ^5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolve conflicts with vercel#635's style-object word spans: makeSpan takes
the config plus vercel#619's explicit timing and effect attributes, and the
container div gets one callback ref shared by the animation drain and
the caret host.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@thedonmon
thedonmon force-pushed the feat/animation-effects branch from cd837e7 to 754349f Compare October 2, 2026 02:15

This branch has not been deployed

No deployments
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.

2 participants