Autoheight WebView

Craft WebView-based components which heights automatically and dynamically adapts to their page content heights, anytime the DOM changes.

Highlights

~ API Reference ~
useAutoheight
  • The width of the viewport will grow to the available horizontal space and won't need to be specified. You can override this behavior by setting explicit width as a parameter attribute of useAutoheight hook.

  • The height of the viewport will dynamically adapts to the content height, which has been constrained by the viewport width as we have just mentioned! It means that anytime the content changes or the viewport width changes, such as in screen rotations, the viewport height will get updated.

    Look at the screen and notice in the top console how viewport height closely follows content height updates.

  • This hook will rely on HandleHTMLDimensionsFeature to use the best API available in the browser and dynamically adapt viewport to content size. In order of preference, ResizeObserver, MutationObserver and finally, polling on a regular interval.

note

You can interchangeably replace "viewport size" with "WebView size" in the above description, if that makes more sense to you.

Basic Example

Invoke useAutoheight in a controlling component and pass returned props to a shell in order to benefit from autoheight behavior. As previously stated, it requires a minima injection of a HandleHTMLDimensionsFeature in the shell to fetch content dimensions:

MinimalAutoheightWebView.tsx

Caveats and their Workarounds

Mobile Virtual Viewport

In some circumstances, the mobile browser might use a virtual viewport much larger then the available width in the WebView, often around 980px for websites which have been built for desktop. For this autoheight component to be reliable, you must ensure that the content has a meta viewport element in the header.

solution

This can be enforced by setting responsive layout with ForceResponsiveViewportFeature.

const Webshell = makeWebshell(
WebView,
new HandleHTMLDimensionsFeature(),
new ForceResponsiveViewportFeature({ maxScale: 1 })
);

However, the result might be ugly and overflow on the horizontal axis if the page is not responsive (optimized for mobile). Always make sure the content is optimized for mobile, read more about this here.

Cyclic Size Constraints โˆž

Because the viewport height now depends on content heigh, you must never have a body element height depending on viewport height, such as:

evil.css
body {
height: 100vh;
}
or
evil.css
body {
height: 100%;
}

That is an evil cyclic dependency ready to cast an infinite loop! Wikipedia website has such styles. Watch the console on top of screen and notice how the shell is caught in an infinite loop shrinking the height inexorably.

solution

Body width and height can be forced to auto with ForceElementSizeFeature.

const Webshell = makeWebshell(
WebView,
new HandleHTMLDimensionsFeature(),
new ForceElementSizeFeature({
target: 'body',
heightValue: 'auto',
widthValue: 'auto'
})
);

Robust Example

In this example, we use the two tips listed in above Caveats section to provide a much resilient implementation.

ResilientAutoheightWebView.tsx

Integration with ScrollView

More often than not, an autoheight feature is needed to embed the WebView along with content of unpredictable length, and a ScrollView works just fine. This section summarize best practices and tricks for a perfect integration.

Recommended ScrollView Props Values

PropValueRationale
pinchGestureEnabledfalseEnabling pinch (the default) will conflict with the pinch-to-zoom feature provided by ForceResponsiveViewportFeature maxScale option. When maxScale is above 1, it will allow for pinch to zoom gestures up to the provided zoom level.
horizontalfalseWhen the Web page is ill-designed (not-responsive), the content might overflows horizontally. We suggest to avoid nesting components providing scroll in the same direction, unless necessary.

Handling hashchange Events

However, scroll to hash on link press, for example:

<a href="#head">go to top</a>

will not work, because the underlying WebView will not have vertical scroll anymore.

solution

You can register a prop to listen to haschange events with HandleHashChangeFeature.

const Webshell = makeWebshell(
WebView,
new HandleHTMLDimensionsFeature(),
new HandleHashChangeFeature({ shouldResetHashOnEvent: true })
);

After that, you must implement the desired behavior, that is to scroll to the desired DOM element when a hashchange event is fired, while accounting for the space occupied by components above the shell, inside the ScrollView. Below is a complete example:

AutoheightWebViewInScrollView.tsx
Last updated on by Jules Sam. Randolph