Paul Irish,Divya ManianandShi ChuanlaunchedMobile Boilerplaterecently—a mobile companion site toHTML5 Boilerplate.
There’s some good stuff in there but I was a little surprised to see that themeta viewport
element included values forminimum-scale=1.0, maximum-scale=1.0, user-scalable=no
:
<meta name= "viewport" content= "width=device-width, target-densitydpi=160dpi, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" >
Settinguser-scalable=no
is pretty much the same as settingminimum-scale=1.0, maximum-scale=1.0
.In any case,I’m not keen on it.Like Roger,I don’t think we should take away the user’s right to pinch and zoom to make content larger. That’s why my usual viewport declaration is:
<meta name= "viewport" content= "width=device-width, initial-scale=1" >
Yes, I know that most native apps don’t allow you to zoom but I see no reason to replicate that failing on the web.
But there’s a problem. Allowing users to scale content for comfort would be fine if it weren’t fora bug in Mobile Safari:
When the meta viewport tag is set to
content= "width=device-width,initial-scale=1"
,or any value that allows user-scaling, changing the device to landscape orientation causes the page to scale larger than 1.0. As a result, a portion of the page is cropped off the right, and the user must double-tap (sometimes more than once) to get the page to zoom properly into view.
This is really annoying soShi Chuan set about fi xing the problem.
His initial solution was to keepminimum-scale=1.0, maximum-scale=1.0
in themeta viewport
element but then to change it using JavaScript once the user initiates a gesture (thegesturestart
event is triggered as soon as two fingers are on the screen). At the point, thecontent
attribute of themeta viewport
element gets updated to readminimum-scale=0.25, maximum-scale=1.6
,the default values:
var metas = document.getElementsByTagName('meta');
var i;
if (navigator.userAgent.match(/iPhone/i)) {
document.addEventListener( "gesturestart", gestureStart, false);
function gestureStart() {
for (i=0; i<metas.length; i++) {
if (metas[i].name == "viewport" ) {
metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6";
}
}
}
}
That works nicely but I wasn’t too keen on the dependency between the markup and the script. If, for whatever reason, the script doesn’t get executed, users are stuck with an unzoomable page.
I suggestedthat the script should alsosetthe initial value tominimum-scale=1.0, maximum-scale=1.0
:
var metas = document.getElementsByTagName('meta');
var i;
if (navigator.userAgent.match(/iPhone/i)) {
for (i=0; i<metas.length; i++) {
if (metas[i].name == "viewport" ) {
metas[i].content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0";
}
}
document.addEventListener( "gesturestart", gestureStart, false);
}
function gestureStart() {
for (i=0; i<metas.length; i++) {
if (metas[i].name == "viewport" ) {
metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6";
}
}
}
Now the markup still contains the robust accessible default:
<meta name= "viewport" content= "width=device-width, initial-scale=1" >
…while the script takes care of initially setting the scale values and also updating them when a gesture is detected. Here’s what’s happening:
- By default, the page is scaleable because the initial
meta viewport
declaration doesn’t set aminimum-scale
ormaximum-scale
. - Once the script loads, the page is no longer scalable because both
minimum-scale
andmaximum-scale
have been set to1.0
.If the device is switched from portrait to landscape, the resizing bug won’t be triggered because scaling is disabled. - When the
gesturestart
event is detected—indicating that the user might be trying to scale the page—theminimum-scale
andmaximum-scale
values are updated to allow scaling. At this point, if the device is switched from portrait to landscape, the resizing bugwilloccur because the page is now scaleable.
Jason Weaverpoints out thatyou should probably detect for iPad too.That’s a pretty straightforward update:
if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i))
Mathias Bynensupdated the codeto usequerySelectorAll
which is supported in Mobile Safari. Here’s the code I’m currently using:
if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i)) {
var viewportmeta = document.querySelector('meta[name= "viewport" ]');
if (viewportmeta) {
viewportmeta.content = 'width=device-width, minimum-scale=1.0, maximum-scale=1.0';
document.body.addEventListener('gesturestart', function() {
viewportmeta.content = 'width=device-width, minimum-scale=0.25, maximum-scale=1.6';
}, false);
}
}
You can try it out onHuffduffer,Salter Cane,Principia Gastronomicaand right here onAdactio.
Right now there’s still a little sluggishness between the initial pinch-zoom gesture and the scaling; the scale values (0.25 - 1.6) don’t seem to take effect immediately. A second pinch-zoom gesture is often required. If you have any ideas for improving the event capturing and propagation, dive in there.
Update:the bug has beenfixed in iOS 6.