Explicit API for visual viewport


#1

We’ve got a proposal for explicitly getting the properties of the visual viewport. Would anyone else be interested in discussing this proposal in the WICG (before eventually maturing and migrating it to CSSWG)? The proposal is on my GitHub, pasted here for convenience:

Viewport API

tl;dr

We propose adding a visualViewport object on document that contains the properties of the visual viewport.

Background

The mobile web contains two viewports, the Layout and Visual viewport. The Layout viewport is what a page lays out its elements into(*) and the Visual viewport is what is actually visible on the screen. When the user pinch-zooms into the page, the visual viewport shrinks but the layout viewport is unchanged. UI like the on-screen keyboard (OSK) can also shrink the visual viewport without affecting the layout viewport. See this demo to visualize the two viewports. This isn’t specified anywhere and implementations vary greatly between browsers.

Currently, several CSSOM scroll properties are relative to the visual viewport (see this for list). Again, there is no spec governing this, but this is how browsers have it implemented today. With this implementation, the dimensions of the visual viewport can be easily determined (For example, window.innerHeight = visual viewport height). However, all other coordinates are generally relative to the layout viewport (e.g. getBoundingClientRects, elementFromPoint, event coordinates, etc.). Having these APIs be mixed is arbitrary and confusing.

This confusion has caused many desktop sites to break when pinch-zoomed or when showing the OSK (see this bug for examples). This is because mobile browsers added new semantics to existing properties, expecting they’d to be invisible to desktop browsers. This becomes a problem as the lines between mobile and desktop blur and features like on-screen keyboard and pinch-zoom make their way to desktops, or when accessing desktop pages from mobile devices.

(*) - This isn’t strictly true. In Chrome, the layout viewport is actually the “viewport at minimum scale”. While on most well behaving pages this is the box that the page lays out into (i.e. the initial containing block), extra-wide elements or an explicit minimum-scale can change this. More specifically, the layout viewport is what position: fixed elements attach to.

Proposed Plan

We believe the best way forward is to change those remaining CSSOM scroll properties to be relative to the layout viewport. In fact, Chrome did this in M48 but, due to developer feedback, this change was reverted in M49. There was more reliance on this than anticipated.

In order to make this transition we propose adding a new explicit API for the visual viewport. With an explicit API, and after a sufficient transition period, we could once again change the CSSOM scroll properties to be relative to the layout viewport. This change would make sure existing desktop sites continue to function correctly as new UI features are added. At the same time, it would allow authors to use and customize those features where needed.

The new API is also easy to feature detect and polyfilling this behavior should be fairly straightforward.

Proposed API

  • Add a visualViewport object on document.
visualViewport = {
    double scrollTop;  // Relative to the layout viewport
    double scrollLeft; // and writable.

    double clientWidth;  // Read-only and excludes the scrollbars
    double clientHeight; // if present.

    double pageScale; // Read-only
}
  • Fire a viewportchanged event against document whenever any of these properties change.

Example

Here’s how an author might use this API to simulate position: device-fixed, which fixes elements to the visual viewport.

<style>
    #bottombar {
        position: fixed;
        left: 0px;
        right: 0px;
        bottom: 0px;
        transform-origin: left bottom;
        transform: translate(0px, 0px) scale(1);
    }
</style>

<body>
    <div id="bottombar">This stays stuck to the visual viewport</div>
</body>

<script>
    var bottomBar = document.getElementById('bottombar');
    var viewport = document.visualViewport;
    document.addEventListener('viewportchanged', function()   
    {
        // Since the bar is position: fixed we need to offset it by the visual
        // viewport's offset from the layout viewport origin.
        var offsetX = viewport.scrollLeft;
        var offsetY = document.documentElement.clientHeight
                    - viewport.clientHeight
                    + viewport.scrollTop;

        // You could also do this by setting style.left and style.top if you
        // use width: 100% instead.
        bottomBar.style.transform = 'translate(' + 
                                    offsetX + 'px,' +
                                    offsetY + 'px) ' +
                                    'scale(' + 1/viewport.pageScale + ')'
    });
</script>

#2

I’m interested in discussing this in the WICG with intent to progress it to standardization in the CSSWG. I think we may want to expand the scope of the document to include formal definition of layout vs. visual viewports (otherwise the functionality of the new API is less obvious). And potentially include some of the further features discussed (e.g. you mention position:device-fixed, which could also potentially make sense to define here).


#3

I am wondering whether we clear up all the viewports and put the things in one place. I believe that would make it easier for developers to understand the concepts and use them right. Layout viewport may also change due to orientation change and viewport meta tag values, but for that you will need to listen to resize changes.

It is also confusing that the event is called viewportchanged but it only affects visual viewports.

We could have something like document.viewport.visual document.viewport.layout etc.


#4

Hi Kenneth,

I’ve already changed it due to that feedback.It’s now document.visualViewport.

As for layout viewport…I’m not sure we have a good definition or that the name is very accurate. In Chrome, it usually coincides with the initial containing block (i.e. the root layout container) and documentElement.clientWidth/Height but not always. A more accurate description might be “the position: fixed box” or the “minimum-scale box”, the latter being how it’s implemented in Chrome. I’m not sure of the subtleties in other browsers.


#5

FYI, the repo is now owned by the WICG GitHub org: https://github.com/WICG/ViewportAPI


#6

An experimental implementation has just landed in Chrome, it should be available in the most recent Mac and Windows Canaries and Android dev channel soon. You can give it a try by turning on the “enable-experimental-web-platform-features” flag in chrome://flags.


#7

After some initial rounds of feedback we’ve now got an initial API implemented in Chrome Canary. If you’re interested, please give it a whirl and give us any feedback, file issues, etc. There’s instructions, details and examples you can play around with on the GitHub repo.

Thanks!