My hobby recently has been making website scale better on phones, but I ran into a problem on several of them: The viewport meta tag assumes that a website either has a fixed width or that it scales to any possible width. I investigated several solutions and eventually settled on an (apparently) novel one: Check the screen size, and if it's too small, add a new meta tag with a fixed viewport. I also wrote a JavaScript polyfill that lets me just set a min-width
in the viewport tag.
Why I needed a different solution
Assume I have a website that scales nicely down to 500px. I want it to use any available screen real estate, but never try to render below that size.
- If I don't set a viewport, mobile browsers will behave as if I set
<meta name="viewport" content="width=960"/>
. - If I set a static viewport with
<meta name="viewport" content="width=500"/>
, it will render at 500px for all devices that support viewports, so a tablet with a 1024px screen will render at 2x scale (and it won't be possible to zoom out). - If I set a dynamic viewport with
<meta name="viewport" content="width=device-width"/>
, it will render how I want it to, but it will also set the initial zoom to the device width. On a phone with a 320px viewport, it will start out zoomed in. It is possible to zoom out, but this is still a bad user experience. - If I change the viewport meta tag's content with JavaScript, Firefox will ignore it.
- I could use
@viewport
CSS device adaptation, but none of the major mobile browsers support it.
My solution
Changing the viewport meta tag was a good idea, and the only change I made is realizing that Firefox will use a viewport tag added in JavaScript. This means the trivial solution is to just add your tag with JavaScript:
var MIN_WIDTH = 500;
var viewport = document.createElement("meta");
viewport.setAttribute("name", "viewport");
if (screen.width < MIN_WIDTH) {
viewport.setAttribute("content", "width=" + MIN_WIDTH);
} else {
viewport.setAttribute("content", "width=device-width, initial-scale=1");
}
document.head.appendChild(viewport);
But, having JavaScript in the header to deal with this isn't ideal, so I experimented and found that as long as I remove the previous viewport, I can add another. With that in mind, I decided to just add the missing min-width
attribute with a polyfill:
var viewport = document.querySelector("meta[name=viewport]");
if (viewport) {
var content = viewport.getAttribute("content");
var parts = content.split(",");
for (var i = 0; i < parts.length; ++i) {
var part = parts[i].trim();
var pair = part.split("=");
if (pair[0] === "min-width") {
var minWidth = parseInt(pair[1]);
if (screen.width < minWidth) {
document.head.removeChild(viewport);
var newViewport = document.createElement("meta");
newViewport.setAttribute("name", "viewport");
newViewport.setAttribute("content", "width=" + minWidth);
document.head.appendChild(newViewport);
break;
}
}
}
}
This just finds your viewport meta tag, checks if it contains min-width
. If the min-width
is less than the screen width, it replaces the meta tag with a fixed one.
If you want to use this in your own code, just get the JavaScript file from the GitHub repo and do this:
<meta name="viewport" content="width=device-width, initial-scale=1, min-width=500"/>
<script type="text/javascript" src="viewport-min-width.js"></script>
At some point I'll probably make this polyfill support max-width
, and also leave other attributes alone (although you really shouldn't use any of the others, since preventing user scaling is annoying). If you want to implement any of this, feel free to send me a pull request.