Transitioned `transform` on an out-of-flow element is never painted
- Dominant language
- Rust
- Stars
- 4.1k
- Forks
- 203
- Avg merge
- 8h 58m
- Merged PRs (30d)
- 112
Description
## Problem
A CSS `transition` on `transform` runs to completion in the computed style, but
the element never moves on screen. I hit it on a toggle whose thumb slides
across a track.
## Environment
- blitz `main` (`a50cb897`)
- dioxus `0.8.0-alpha.1`, dioxus-native, `vello` renderer
- Arch Linux, kernel 7.1.9, Intel RPL-S, Mesa
## Minimal reproduction
```html
.box { position: relative; width: 42px; height: 22px; }
.dot { position: absolute; top: 0; left: 2px; width: 18px; height: 18px;
transition: transform 150ms ease; }
```
Change the dot's transform to `translateX(20px)` at runtime. The computed value
animates to 20px, the painted one never leaves 0.
Reading `Node::transform()` alongside the computed style each frame:
```
before computed TranslateX(0.0 px) painted None
t=0.05 computed TranslateX(12.318244 px) painted None
t=0.1 computed TranslateX(18.513643 px) painted None
t=1 computed TranslateX(20.0 px) painted None
```
Three things are needed together, and dropping any one of them makes it paint
correctly:
- the moving element is out of flow (`position: absolute`),
An instant `transform` change is fine either way. It is specifically the
## Cause `blitz-paint` draws with the cached `Node::transform()`, and the only thing
In the case above the subtree is pruned at an ancestor on every frame, so the
## Possible solution Refreshing the transform and continuing into the children in that branch fixes
## AI disclaimer This report was investigated and written by AI (Claude). Every claim above was
- its container is inline-level (a ``; the same markup with a `
container is fine),
- something follows that container as a sibling (the trailing `
remove it and it is fine).
transition, where the value changes on frames that carry no DOM mutation.
that refreshes it is `resolve_transforms` in `blitz-dom/src/resolve.rs`. That
function returns early for a node whose damage lacks `RECALCULATE_OVERFLOW`,
and the early return skips both the refresh and the recursion into that node's
children.
element's transform is never set at all, which is why it reads `None` rather
than a stale value. Which ancestor prunes depends on the surrounding markup,
which is presumably why the container's display type and the trailing sibling
matter.
the case above; scrollable overflow is genuinely unchanged there, so leaving it
cached seems safe. The cost is walking subtrees that are currently skipped.
measured against `a50cb897` rather than reasoned about, after a first version of
this report got the cause wrong. Happy to open a PR if you want it.
Contributor guide
Research direction
Start in blitz-dom/src/resolve.rs at resolve_transforms, then inspect how blitz-paint reads the cached Node::transform(). Run the supplied inline-span reproduction with the out-of-flow transitioning element and verify that the transform is refreshed during frames without DOM mutation and the dot paints at its final position.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- css, html, rust
- Domain
- computer-graphics, frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100