The visual viewport is a kind of viewport whose scrolling area is another viewport, called the layout viewport.
In addition to scrolling, the visual viewport may also apply a scale transform to its layout viewport. This transform is applied to the canvas of the layout viewport and does not affect its internal coordinate space.
From MDN:
The portion of the viewport that is currently visible is called the visual viewport
The Visual Viewport can be smaller than the layout viewport, such as when the user has pinched-zoomed
To visualize it, authors can get sizing and positioning info of the Visual Viewport through the Visual Viewport API
let {
width,
height,
scale,
offsetTop,
offsetLeft,
pageLeft,
pageTop,
} = window.visualViewport;
These values are:
width
/height
= Width and Height of the Visual ViewportoffsetTop
/offsetLeft
= Distance from edges of the Visual Viewport to edges of the Layout ViewportpageTop
/pageLeft
= Distance from edges of the Visual Viewport to edges of the ICBscale
= The scale factor (default:1
)
Using a tab more JS, these values can be synced to Custom Properties which you can use to position an element that outlines this Visual Viewport
document.documentElement.style.setProperty('--vvw', `${vvv.width}px`);
document.documentElement.style.setProperty('--vvh', `${vvv.height}px`);
document.documentElement.style.setProperty('--vvpt', `${vvv.pageTop}px`);
document.documentElement.style.setProperty('--vvpl', `${vvv.pageLeft}px`);
document.documentElement.style.setProperty('--vvot', `${vvv.offsetTop}px`);
document.documentElement.style.setProperty('--vvol', `${vvv.offsetLeft}px`);
document.documentElement.style.setProperty('--vvz', vvv.scale);
Depending on wether you are using position: absolute
or position: fixed
, you need to use these values differently:
#visualviewport {
box-sizing: border-box;
border: 8px solid;
border-image: repeating-linear-gradient(45deg, orange, orange 10px, transparent 10px, transparent 20px) 10;
}
#visualviewport {
position: absolute;
top: var(--vvpt, 0px);
left: var(--vvpl, 0px);
width: var(--vvw, 100%);
height: var(--vvh, 100vh);
}
#visualviewport {
box-sizing: border-box;
border: 8px solid;
border-image: repeating-linear-gradient(45deg, orange, orange 10px, transparent 10px, transparent 20px) 10;
}
#visualviewport {
position: fixed;
top: var(--vvot, 0px);
left: var(--vvol, 0px);
width: var(--vvw, 100%);
height: var(--vvh, 100vh);
right: var(--vvol, 0px) + var(--vvw, 0px);
bottom: var(--vvol, 0px) + var(--vvh, 0px);
}
👉 Try it out: Visual Viewport
💡 These findings are a textual representation of the test results table.
As mentioned above, the Visual Viewport API can be used for this. Note that the size of Visual Viewport will be equal to or less than the Layout Viewport
The Visual Viewport updates nicely as you scroll and shifts down with the scroll position.
When UA UI Elements expand/contract is also updates accordingly, except in Edge on Android where there’s a few pixels missing. This bug is corrected as soon as you stop the gesture (i.e. lift up your finger), then the correct values are flushed.
The presence of Classic Scrollbars take away space from the Visual Viewport
The Visual Viewport updates nicely as you pinch zoom
In Safari on Desktop the values do not update immediately as you pinch-zoom, but are only updated when the gesture is finished (i.e. lifting up your fingers).
When you over pinch-zooming out in browsers that allow this (i.e. all based on WebKit) something funky happens with the position and dimensions of the Visual Viewport:
- Its position gets anchored to
0,0
of the canvas. This origin moves between the top-left edge of the Large and Small Viewport as you move around. - The
width
/height
is measured from that0,0
origin (which can move) to the right/bottom edge of the Layout Viewport
👀 See Screenshot 1 of Safari on iOS and Screenshot 2 of Safari on iOS
👀 See Recording of Safari on iOS
See Virtual Keyboard: Findings.
When overscrolling, WebKit allows negative values for window.scrollX
and window.scrollY
. This is the only engine to expose this. While doing so, the values for the Visual Viewport’s pageTop
and pageLeft
also become negative, while its height
and width
remain the same size. Because of this, the visualization of the Visual Viewport can get clipped by the ICB which does bounce with the rubber banding effect
👀 See Recording of Safari on Desktop and Recording of Safari on iOS
Other engines that do not allow negative values for window.scrollX
and window.scrollY
as it has a rubber banding effect going on – i.e. Chrome on macOS and Firefox on macOS – follow the same behavior as the ICB: the bounce with the effect. The values for the height
and width
remain the same as it rubber bands.
👀 See Recording of Firefox on Desktop and Recording of Chrome on Desktop
In engines that keep the Layout Viewport in place as the browser rubber bands the values for offsetTop
and offsetLeft
remain 0
.
👀 See Recording of Firefox on Desktop and Recording of Chrome with flag on Desktop
We are tracking issues using the label Visual Viewport