DioxusLabs / DioxusLabs/blitz

Transitioned `transform` on an out-of-flow element is never painted

Open
#840 1 comment 0 reactions 0 assignees View on GitHub
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`),
- its container is inline-level (a ``; the same markup with a `

`
container is fine),
- something follows that container as a sibling (the trailing `
` above;
remove it and it is fine).

An instant `transform` change is fine either way. It is specifically the
transition, where the value changes on frames that carry no DOM mutation.

## Cause

`blitz-paint` draws with the cached `Node::transform()`, and the only thing
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.

In the case above the subtree is pruned at an ancestor on every frame, so the
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.

## Possible solution

Refreshing the transform and continuing into the children in that branch fixes
the case above; scrollable overflow is genuinely unchanged there, so leaving it
cached seems safe. The cost is walking subtrees that are currently skipped.

## AI disclaimer

This report was investigated and written by AI (Claude). Every claim above was
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.