Setting the Viewport to Scale to Fit Both Width and Height

Nick picture Nick · Jan 29, 2014 · Viewed 48.3k times · Source

I'm working on a website that fits within a specific width and height (an 885x610 div with a 1px border and 3px top margin). I would like the user to never have to scroll or zoom in order to see the entire div; it should always be fully visible. Since devices have a wide variety of resolutions and aspect ratios, the idea that came to mind was to set the "viewport" meta tag dynamically with JavaScript. This way, the div will always be the same dimensions, different devices will have to be zoomed differently in order to fit the entire div in their viewport. I tried out my idea and got some strange results.

The following code works on the first page load (tested in Chrome 32.0.1700.99 on Android 4.4.0), but as I refresh, the zoom level changes around. Also, if I comment out the alert, it doesn't work even on the first page load.

Fiddle

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0">
        <script type="text/javascript">
            function getViewportWidth() {
                if (window.innerWidth) {
                    return window.innerWidth;
                }
                else if (document.body && document.body.offsetWidth) {
                    return document.body.offsetWidth;
                }
                else {
                    return 0;
                }
            }

            function getViewportHeight() {
                if (window.innerHeight) {
                    return window.innerHeight;
                }
                else if (document.body && document.body.offsetHeight) {
                    return document.body.offsetHeight;
                }
                else {
                    return 0;
                }
            }

            if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {
                var actual_width = getViewportWidth();
                var actual_height = getViewportHeight();

                var min_width = 887;
                var min_height = 615;

                var ratio = Math.min(actual_width / min_width, actual_height / min_height);

                if (ratio < 1) {
                    document.querySelector('meta[name="viewport"]').setAttribute('content', 'initial-scale=' + ratio + ', maximum-scale=' + ratio + ', minimum-scale=' + ratio + ', user-scalable=yes, width=' + actual_width);
                }
            }

            alert(document.querySelector('meta[name="viewport"]').getAttribute('content'));
        </script>
        <title>Test</title>
        <style>
            body {
                margin: 0;
            }

            div {
                margin: 3px auto 0;
                width: 885px;
                height: 610px;
                border: 1px solid #f00;
                background-color: #fdd;
            }
        </style>
    </head>
    <body>
        <div>
            This div is 885x610 (ratio is in between 4:3 and 16:10) with a 1px border and 3px top margin, making a total of 887x615.
        </div>
    </body>
</html>

What can I do to have this website scale to fit both the width and the height?

Answer

hexalys picture hexalys · Feb 12, 2014

It's possible to get a consistent behavior. But it's unfortunately very complex. I am working on a script that detects spoofed agents and dynamically rescale the viewport to desktop or other spoofed agents accordingly. I was also facing the zooming issue with Android/Chrome as well as the iOS emulator...

To get around it, you need to disable zooming and/or set the viewport twice. On the first pass, preferably inline in the <head> as you do now, you set your scale and disable user-scaling temporarily to prevent the zoom issue, using the same fixed value for all 3 scales like:

document.querySelector('meta[name=viewport]').setAttribute('content', 'width='+width+',minimum-scale='+scale+',maximum-scale='+scale+',initial-scale='+scale);

Then to restore zooming you set the viewport again on DOMContentLoaded, with the same scale, except that this time you set normal min/max scale values to restore user-scaling:

document.querySelector('meta[name=viewport]').setAttribute('content', 'width='+width+',minimum-scale=0,maximum-scale=10');

In your context, because the layout is fixed and larger than the viewport, initial-scale='+scale is perhaps needed for a more sound alternative for DOMContentLoaded:

document.querySelector('meta[name=viewport]').setAttribute('content', 'width='+width+',minimum-scale=0,maximum-scale=10,initial-scale='+scale);

That should get the viewport to rescale as you would like in Webkit browsers without zooming problems. I say only in webkit because sadly IE and Firefox do not support changing the viewport as per this Browser Compatibility Table for Viewports, Dimensions and Device Pixel Ratios shows: http://www.quirksmode.org/mobile/tableViewport.html

IE has its own way to change the viewport dynamically which is actually needed for IE snap modes to be responsive. http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/

So for IEMobile and IE SnapMode (IE10&11) you need to dynamically insert an inline <style> in the <head> with something like.

<script>
 var s = document.createElement('style');
 s.appendChild(document.createTextNode('@-ms-viewport{width:'+width+'px')+';}'));
 document.head.appendChild(s);
</script>

And unfortunately, Firefox has neither: The viewport is set for once and for all as the above compatibility table shows. At the moment, for lack of other methods, using CSS Transform (as @imcg pointed out) is the only way to alter the viewport in FireFox Mobile on Android or Gecko OS. I have just tested it and it works in the context of a fixed size design. (In "Responsive Design context", the layout can be rescaled larger via CSS Transform, say at desktop size on a phone, but Firefox still read the phone size MQs. So that's something to be mindful off in RWD context. /aside from webkit)

Though, I have noticed some random Webkit crashes with CSSTransform on Android so I would recommend the viewport method for Safari/Chrome/Opera as more reliable one.

In addition, in order to get cross-browser reliability for the viewport width, you also have to face/fix the overall inconsistency between browsers for innerWidth (note that documentElement.clientWidth is much more reliable to get the accurate layout pixel width over innerWidth) and you also have to deal with devicePixelRatio discrepancies as indicated on the quirksmode.org table.

Update: Actually after some more thought into a solution for my own problem with Firefox, I just found out a way to override the viewport in Firefox Mobile, using document.write(), applied just once:

document.write('<meta name="viewport" content="width='+width+'px,initial-scale='+scale+'">');

Just tested this successfully in both Webkit and Firefox with no zooming issues. I can't test on Window Phones, so I am not sure itf document.write works for IEMobile...