Skip to content

[Popper]: a lot of forced reflows and style recomputations when scrolling #2904

@teidesu

Description

@teidesu

🐛 Bug report

Image
Screen.Recording.2025-12-21.at.00.07.54.mov

when using a popper-based component (popover in my case) the use of css variables leads to very expensive css recomputations when there's a lot of content in the popover, that add up with the floating-ui's own reflows, making the overall experience very sluggish

💥 Steps to reproduce

  1. open repro below
  2. open the popover
  3. scroll the page down
  4. notice how sluggish the popover position updates are

💻 Link to reproduction

https://stackblitz.com/edit/ipjqylaw?file=src%2FPopover.tsx

🧐 Expected behavior

the popover updates smoothly

🧭 Possible Solution

below are the patches i had to apply for the scrolling to get smooth in my app:

simply removing the use of css variables (and a child z-index computation) already improves it a lot:

@@ -247,8 +247,7 @@ function getPlacementImpl(referenceOrVirtual, floating, opts = {}) {
     const win = getWindow(floating);
     const x = roundByDpr(win, pos.x);
     const y = roundByDpr(win, pos.y);
-    floating.style.setProperty("--x", `${x}px`);
-    floating.style.setProperty("--y", `${y}px`);
+    floating.style.transform = `translate3d(${x}px, ${y}px, 0)`
     if (options.hideWhenDetached) {
       const isHidden = pos.middlewareData.hide?.referenceHidden;
       if (isHidden) {
@@ -260,10 +259,6 @@ function getPlacementImpl(referenceOrVirtual, floating, opts = {}) {
       }
     }
     const contentEl = floating.firstElementChild;
-    if (contentEl) {
-      const styles = getComputedStyle(contentEl);
-      floating.style.setProperty("--z-index", styles.zIndex);
-    }
   };
@@ -336,7 +332,6 @@ function getPlacementStyles(options = {}) {
       top: "0px",
       left: "0px",
       // move off-screen if placement is not defined
-      transform: placement ? "translate3d(var(--x), var(--y), 0)" : "translate3d(0, -100vh, 0)",
       zIndex: "var(--z-index)"
     }
   };

i dont need --z-index so i removed it, but getComputedStyle(contentEl) should probably be inside a raf.

then made the getSizeMiddleware optional:

@@ -189,6 +189,7 @@ function getShiftMiddleware(opts) {
   });
 }
 function getSizeMiddleware(opts) {
+  if (opts.noSizeMiddleware) return;
   return size({
     padding: opts.overflowPadding,

and now scrolling is finally smooth:

Screen.Recording.2025-12-21.at.00.15.40.mov

🌍 System information

Software Version(s)
Zag Version 1.31.1
Browser Chrome 142
Operating System macOS

📝 Additional information

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions