visual-viewport

1 posts

daangn

Improving the iOS WebView Input Experience for (opens in new tab)

iOS WebViews can shift the entire page upward when an input receives focus, disrupting users as the virtual keyboard appears. The post describes four progressively refined approaches, ultimately using a single input whose opacity is temporarily set to zero before focus. This avoids iOS’s automatic scroll behavior, though it remains a fragile, version-dependent workaround best reserved for input-critical screens. ## Understanding the iOS WebView Problem - iOS uses two viewport concepts: - **Layout Viewport:** The CSS layout area, which does not shrink when the keyboard opens. - **Visual Viewport:** The area visible to the user, which becomes smaller. - Because the focused input must remain visible above the keyboard, iOS shifts the page upward. - The resulting movement can push important content off-screen, and WebView code cannot fully control this behavior. ## Attempt 1: Restore the Scroll Position - The first approach listened for `visualViewport` `resize` and `scroll` events. - It reduced the wrapper’s height by the keyboard height and called `window.scrollTo(0, 0)`. - This failed because iOS moved the page before the event handler could restore it. - Users saw flickering and shaking as multiple resize events triggered repeated corrections. ## Attempt 2: Follow `offsetTop` - Instead of undoing the movement, the implementation followed it. - `visualViewport.offsetTop` was used to adjust a fixed wrapper’s `top` position. - A short delay was required because iOS updates `offsetTop` asynchronously. - This reduced the visible jump but still caused subtle shaking while the keyboard animated. - Rapidly switching inputs or opening and closing the keyboard made the instability more noticeable. ## Attempt 3: Fake and Real Input Swap - This approach prevented iOS from scrolling to the focused input by focusing a real input positioned off-screen. - A visible, read-only “fake” input received the user’s tap, then focused the hidden real input. - Once the keyboard appeared, the two inputs were swapped so the real input became visible. - The page no longer experienced major upward movement. - However, two inputs required synchronization of values, selection, placeholders, textarea sizing, mentions, and emojis. - Swap timing, keyboard detection, and native bridge dependencies made the implementation increasingly complex. ## Attempt 4: Temporarily Hide the Input with Opacity - The final approach kept only one input. - On `touchstart`, the input’s `opacity` was set to `0` before calling `focus()`. - iOS appeared not to perform its automatic scroll-into-view behavior for an invisible input. - After detecting that the keyboard had opened—using the visual viewport and a keyboard-height threshold—the input’s opacity was restored to `1`. - This preserved the input’s state and avoided synchronization problems. - The technique is still a hack: behavior may change across iOS versions, and it can cause minor timing issues. The recommended compromise is to apply the opacity workaround selectively on pages where writing is central to the experience, rather than across the entire WebView application. It prioritizes uninterrupted user interaction despite the lack of a clean, standard iOS API.