vaul icon indicating copy to clipboard operation
vaul copied to clipboard

iOS: Overflowing elements flash on drawer close

Open ParkerMJones opened this issue 1 year ago • 4 comments

Description of the issue

  • iOS only: Elements that exist below the height of the screen, and that overflow off the page horizontally, will flash when the drawer is closed
  • Sometimes on opening the drawer with these elements in view, the entire page will flash, and scroll position may be moved
  • Tested on Safari, Chrome, and Firefox with same results in each
  • Seems only to happen on real devices: the simulator behaves as expected (iPhone 15 Pro Max, iOS 17.2)
  • On Android (Galaxy S22, Android 14) and desktop web (Mac M2 OS 14.4.1), the elements behave as expected with no flash (although sometimes scroll position of the page is recalculated on drawer opening on Android)

Open in StackBlitz

Vercel Deployment

Environment

  • Remix 2.9.2
  • vaul 0.9.1
  • tested on: iPad (9th Gen) OS 17.4.1

Link to reproduction repository

Video Recording

https://github.com/ParkerMJones/vaul-flash-repro/assets/80937296/dab773e5-94c8-48ea-974e-a7623a07b17d

ParkerMJones avatar May 28 '24 19:05 ParkerMJones

Facing the same issue on iPhone 11

hauseyo avatar Jun 25 '24 11:06 hauseyo

Can confirm this, also happening on my PWA and Safari on Iphone 13. It also doesn't allow me to interact with buttons inside (remove, cancel), only Overlay. It also make a big layout shift on both opening and closing modal.

Like you said, only happening on real devices both in development and production, but doesn't affect desktop nor mobile device mode in DevTools.

I thought it has to do something with setting body position to fixed upon opening modals, but even with noBodyStyles it still occurs. The more I think about it probably it has to do something with locking the scroll behaviour because when I put modal prop to false, everything works fine but I don't think that should be the solution since it disables Overlay.

I hope fix comes soon, cuz Vaul is really big part of making my PWA UX native-like.

srkuleo avatar Jul 09 '24 13:07 srkuleo

Facing the same issue on 13 Pro Max

AdytZZa22 avatar Jul 15 '24 10:07 AdytZZa22

facing same issue on some android and ios devices

muhammadarsalanmanzoor avatar Aug 07 '24 18:08 muhammadarsalanmanzoor

I'm facing the same issue on an iOS device when using the Arc Search browser.

renekahr avatar Sep 01 '24 19:09 renekahr

In addition to the open and onOpenChange props, I also added disablePreventScroll and noBodyStyles to the root and that helped.

<ResponsiveDialogContext.Provider value={{ open, setOpen }}>
    <Drawer open={open} onOpenChange={setOpen} disablePreventScroll noBodyStyles>
        {children}
    </Drawer>
</ResponsiveDialogContext.Provider>

landry-fairwinds avatar Sep 05 '24 15:09 landry-fairwinds

@landry-fairwinds worked for me. Thanks for sharing the solution.

renekahr avatar Sep 06 '24 06:09 renekahr

In addition to the open and onOpenChange props, I also added disablePreventScroll and noBodyStyles to the root and that helped.

<ResponsiveDialogContext.Provider value={{ open, setOpen }}>
    <Drawer open={open} onOpenChange={setOpen} disablePreventScroll noBodyStyles>
        {children}
    </Drawer>
</ResponsiveDialogContext.Provider>

My bad, this actually fixes the problem. I think I mistaken it for another prop. Thanks!

srkuleo avatar Sep 06 '24 06:09 srkuleo

This should be fixed in #341

emilkowalski avatar Sep 07 '24 10:09 emilkowalski

@emilkowalski What should we adjust now, after the updates? I removed disablePreventScrolland noBodyStyles now the drawer scrolls to the top and has a very weird behavior. I also can't trigger nested drawers anymore.

renekahr avatar Sep 17 '24 21:09 renekahr

@landry-fairwinds disablePreventScroll doesn't exist on drawer root. image

Any idea how to fix this?

renekahr avatar Sep 17 '24 22:09 renekahr

I'm currently still experiencing this issue where the drawer flashes while it's dismissing. My suspicion is that state changes that happen mid-dismissal are causing it to flash

Image

tylerbecks avatar May 08 '25 06:05 tylerbecks

I found a workaround, inspired by this section in the Radix Dialog docs:

/**
 * Props for FilterDrawer component
 *
 * This component addresses a UI flash issue that occurs when state changes happen
 * while the drawer is animating closed:
 *
 * Problem: When filter state (e.g., price, score) updates while a drawer is animating closed,
 * the drawer content may flash briefly in incorrect positions, creating a jarring visual effect.
 *
 * Solution: Delay state changes until after drawer animation completes
 * 1. First close the drawer: setOpen(false)
 * 2. Set pendingAction to indicate what action should run after closing (APPLY or RESET)
 * 3. Wait for animation to complete (using timeout matching animation duration)
 * 4. Execute the appropriate action callback (applyAction or resetAction)
 *
 * Usage pattern in filter components:
 * - Define resetAction and applyAction callbacks
 * - On button click, set pendingAction and close the drawer
 * - Let FilterDrawer handle the timing of the action execution
 */
interface FilterDrawerProps {
  applyAction?: () => void;
  children: React.ReactNode;
  onOpenChange: (open: boolean) => void;
  open: boolean;
  pendingAction?: FilterAction | null;
  resetAction?: () => void;
  animationDuration?: number;
}

export function FilterDrawer({
  children,
  open,
  onOpenChange,
  pendingAction,
  resetAction,
  applyAction,
  animationDuration = DRAWER_ANIMATION_DURATION + 1,
  ...props
}: FilterDrawerProps) {
  const handleAnimationEnd = useCallback(() => {
    if (pendingAction === FilterActions.RESET) {
      resetAction?.();
    } else if (pendingAction === FilterActions.APPLY) {
      applyAction?.();
    }
  }, [pendingAction, resetAction, applyAction]);

  useEffect(() => {
    if (open) return;
    setTimeout(() => {
      handleAnimationEnd();
    }, animationDuration);
    // Intentionally ignore handleAnimationEnd
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, pendingAction, animationDuration]);

  return (
    <Drawer open={open} onOpenChange={onOpenChange} {...props}>
      {children}
    </Drawer>
  );
}

tylerbecks avatar May 09 '25 02:05 tylerbecks