blitz-dom: `get_client_bounding_rect` scrolls `position: fixed` boxes with the viewport
Generated from repros/blitz-dom-bounding-rect-fixed-scroll/README.md when the site was built at 7aa23e2.
What niso does
Section titled “What niso does”crates/niso-core/src/api.rs:226-252: Dom::fixed_to_viewport walks the containing-block chain looking for a
position: fixed box whose containing block is the root element, and Dom::rect adds viewport_scroll() back
for such boxes and everything inside them.
Expected vs actual
Section titled “Expected vs actual”BaseDocument::get_client_bounding_rect (packages/blitz-dom/src/document.rs:2451-2468) subtracts
viewport_scroll() from every box. A box fixed to the viewport (and its descendants) does not move when the
viewport scrolls, so its client rect must not change. Blitz’s own paint code already handles this
(hoisted_child_position in layout/paint_tree.rs:39-51 cancels the viewport scroll for fixed boxes); the
geometry query does not.
Viewport 800x600, body { height: 3000px }, a position: fixed; top: 10px box with a child, viewport scrolled
to y = 100:
#fixed: expected y = 10, actual y = -90#inner: expected y = 10, actual y = -90CARGO_TARGET_DIR=~/.capy/work/repro-target cargo runExit status 1 while the bug reproduces, 0 once fixed. On Ubuntu, blitz needs sudo apt-get install -y libfontconfig-dev pkg-config.
Upstream main
Section titled “Upstream main”Reproduces. Blitz main was at be60597f67b3cece0456d26e8d7ff5a88600bbc5 when tested (2026-11-08, git ls-remote https://github.com/DioxusLabs/blitz HEAD), the same commit niso pins, so the pinned result is also the main result.
Existing issue or PR
Section titled “Existing issue or PR”None found. Searched DioxusLabs/blitz issues and PRs for getBoundingClientRect, get_client_bounding_rect,
position fixed, scroll. Related but different: #874 (merged, unrounded positions for gBCR), #922 (merged,
out-of-flow hoisting, fixed the paint side), #841 (fixed modal makes the viewport scrollable).
Issue text
Section titled “Issue text”Title: get_client_bounding_rect subtracts the viewport scroll from position: fixed boxes
Body:
A position: fixed box (contained by the viewport) keeps its client rect when the viewport scrolls, as do its
descendants. BaseDocument::get_client_bounding_rect subtracts viewport_scroll() from every box, so fixed boxes
move up by the scroll offset. Paint already cancels this in hoisted_child_position.
use blitz_dom::{DocumentConfig, Point};use blitz_html::HtmlDocument;use blitz_traits::shell::{ColorScheme, Viewport};
const HTML: &str = r#"<!DOCTYPE html><html><body style="margin:0;height:3000px"><div id="fixed" style="position:fixed;top:10px;left:0;width:50px;height:20px"><div id="inner" style="height:5px"></div></div></body></html>"#;
fn main() { let config = DocumentConfig { viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)), ..Default::default() }; let mut doc = HtmlDocument::from_html(HTML, config); doc.resolve(0.0); doc.set_viewport_scroll(Point { x: 0.0, y: 100.0 }); doc.resolve(0.0); for id in ["fixed", "inner"] { let node = doc.get_element_by_id(id).unwrap(); println!("#{id}: {}", doc.get_client_bounding_rect(node).unwrap().y); // expected 10, actual -90 }}Tested at be60597f67b3cece0456d26e8d7ff5a88600bbc5. A fix: in get_client_bounding_rect (or physical_unrounded_geometry), skip the viewport scroll
when the containing-block chain reaches a fixed box whose containing block is the root, the same test
hoisted_child_position uses.