Understanding Phantom window.resize Events in iOS

Abstract image used to represent Understanding Phantom window.resize Events in iOS
Image by Craig Whitehead.

In Brief

A page can receive resize events without somebody dragging a conventional browser window. Compare the dimensions your feature actually uses before doing expensive work. A widthonly check suits behaviour that depends on layout width; it is not a general reason to ignore height changes or keyboard and zoom interactions.

Something that I've been dealing with a lot recently whilst working on a web application that relies on displaying data on small screens is seemingly random or 'phantom' window.resize events from iOS devices. Unlike desktop browsers, where resize events are typically triggered by actual changes in window size, iOS Safari appears to exhibit a different behaviour, triggering resize events at random.

As web developers, we will often encounter unusual challenges like this in our line of work. It is fair to say that there must be some method to this madness: Apple isn't just triggering window.resize for fun!


Causes of phantom resize events

So, I did some experimenting, and here is what I think is causing these random events:

Dynamic Browser UI

I'm putting the main one first, because it's actually quite obvious when you think about it. In iOS Safari, the Browser interface is dynamic; the address bar and toolbars both react when a user scrolls the page, showing and hiding again to allow the user more screen real estate for the website itself.

On the iOS setup I was testing, I saw additional resize events on window as the browser interface changed.

Viewport Adjustments

I've found that Safari will often also trigger a resize event when the viewport is adjusted by for example the virtual keyboard being shown or hidden. Again though: that makes sense. What makes less sense is that it also triggers when you switch between tabs...

Orientation Changes

This one should be selfexplanatory: the screen size does change between landscape and portrait. You can listen for this specifically by adding a listener to window:

window.addEventListener('orientationchange', () => {
  console.warn(`The screen orientation is now ${window.orientation}`);
});

User Interactions

Pinchtozoom is another interaction to include when testing the resize behaviour of the Safari versions your application supports.


Mitigating Phantom resize events

For the most part, these additional events do not matter to the application. They become a problem when each one triggers expensive work, such as rebuilding an interface whose layout depends only on width. For that case, compare window.innerWidth with the previous value before doing the work. A feature that depends on height needs a different check.


Wrapping Up

The useful question is which dimension the feature needs, rather than whether an event feels like a genuine resize. A width check can avoid unnecessary layout work, but height changes may still matter to the person using the page. The later examples below add some more context.

Postscript

February 2019: React 16.8 introduced Hooks. The useState/useEffect excerpt below is a later example for a component rendered in the browser; import those Hooks from React. It was not part of the original September 2016 article.

This example compares window.innerWidth with the stored value before updating the component. Use that filter only for behaviour that depends on layout width:

const [windowWidth, setWindowWidth] = useState(window.innerWidth);

useEffect(() => {
  const handleResize = () => {
    const { innerWidth } = window;

    if (innerWidth !== windowWidth) {
      setWindowWidth(innerWidth);
      // The window has resized.  Do your resize stuff here
    }
    // This is a phantom resize event. Do nothing.
  };

  window.addEventListener('resize', handleResize);

  return () => window.removeEventListener('resize', handleResize);
}, [windowWidth])

The effect registers a resize listener and removes the same listener during cleanup. A width change updates windowWidth; a heightonly change leaves that state alone.

September 2019: Safari 13 added the Visual Viewport API. The layout viewport and the visible part of the page are separate: zooming or an onscreen keyboard can change what is visible without changing the layout width. window.visualViewport exposes its own resize event. Use that API, where supported, when the feature needs the visible viewport, and keep its listener lifecycle explicit.

Do not assume that keyboard, toolbar or zoom changes produce the same window events in every Safari release. Record the event target and the relevant dimensions on the versions you support. The visual viewport's resize event and window's resize event are separate signals.

September 2023: The later illustration below uses the iOS 17 Simulator, rather than the iOS 10 environment of the original article. Watch the bottom address bar collapse and return as the page scrolls. That illustrates changing browser chrome; the recording does not identify which viewport event fired.

Need a senior engineer involved?

I can work directly in the codebase, review the architecture, or support the team through delivery when the work needs more than extra hands.