A fix for iPhone viewport scale bug

Jeremy first raised his concern (about iPhone viewport scaling) and later by Andreas. My early view was stated here. Since this issue was raised, I have been trying to find ways to work around this problem. Below is a proof of concept of how to preserve the accessibility (scaling) without affecting the usability for the majority.

All we need to do to solve this problem is to dynamically reset the scale factors to default when user zoom the page. Here is a demo: http://www.lab.highub.com/m/scalefix.html

HTML:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

JavaScript:

  <script>
    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";
        }
      }
    }
  </script>

Here is the code explanation:

1. we need to know what are the default minimum-scale, maximum-scale values, on iPhone’s official document, it’s mentioned the minimum-scale value is 0.25 by default, and the maximum-scale value is 1.6 by default. So to replace the default value, we need to set

function gestureStart() {
var metas = document.getElementsByTagName('meta');
var i;
for (i=0; i if (metas[i].name == "viewport") {
metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6";
}
}
}

2. we need to know when to set this, this is very easy, iPhone has gesture event listener we can use to target the document body, here is how to do so:

document.addEventListener("gesturestart", gestureStart, false);

3. The last thing we need to take care about is to make sure this only happens on iPhone, again this can be easily done using:

if (navigator.userAgent.match(/iPhone/i)) {
document.addEventListener("gesturestart", gestureStart, false);
}

In this way, we can prevent the page scale jump issue by default if the user never initialize the zoom, allow greater user experience, at the same time, without compromising the accessibility for the minority.

This is a proof of concept, so do let me know if you have different view on this. :)

Update: 4/5/2011 There is a bit imperfection in this script, have a read of it here: https://gist.github.com/901295

Be Sociable, Share!

About Shi Chuan

I am a web developer.
This entry was posted in Mobile and tagged , . Bookmark the permalink.

49 Responses to A fix for iPhone viewport scale bug

  1. Jeremy Keith says:

    The demo page still has “minimum-scale=1.0, maximum-scale=1.0″. I believe that this is what’s causing the reflow to work—not the script.

    I tried the script out on http://huffduffer.com which simply sets “width=device-width; initial-scale=1.0;” and in that situation, the script doesn’t appear to work: changing from portrait to landscape still causes Mobile Safari to scale up too much.

  2. admin says:

    Hi Jeremy, what I mean is now even with minimum-scale=1.0, maximum-scale=1.0 on the demo page, now you can zoom.

  3. Jeremy Keith says:

    Yes, but all you’ve done is recreated “width=device-width; initial-scale=1.0;” using JavaScript. What problem does this solve? By simply leaving out “minimum-scale=1.0, maximum-scale=1.0″ (but including “initial-scale=1.0″) you get exactly the same result (without the overhead of added JavaScript).

    I was hoping that this would be a fix for the iPhone bug I mentioned (too much scaling up when going from portrait to landscape).

  4. admin says:

    What I mean is to a normal user first come to the page, who never intends to zoom the site, when he goes from portrait to landscape, he won’t experience the scaling up problem.

    For those who have eye sight issue, who choose to zoom, they will get the scaled result as they wanted, and it doesn’t matter if there is too much scaling up or not to them.

  5. admin says:

    It’s not perfect in a way that if a person already initialized the zoom, then the scale up issue will happen again, but at least for the majority, they won’t experience the problem. And at the same time, the people need to zoom can still do so.

  6. Jeremy Keith says:

    I see. So you set “minimum-scale=1.0, maximum-scale=1.0″ in the markup, but then at the first use of a gesture event, replace it with “minimum-scale=0.25, maximum-scale=1.6″

    Rather than using those specific values, maybe it would be better to use the script to remove all references to minimum-scale and maximum-scale.

    So the initial meta element contents would be: “width=device-width, minimum-scale=1.0, maximum-scale=1.0″

    But when a gesture event is detected, change it to: “width=device-width”

    Would that work?

  7. admin says:

    I have tried that, that didn’t work. which is why i use minimum-scale=0.25, maximum-scale=1.6. but they basically do the same thing :)

  8. Jeremy Keith says:

    I feel a little weird about the dependency between the markup and the JavaScript. I’d rather have it all scripted or not at all. How about this…

    In the markup, set “width=device-width, initial-scale=1.0;”. Now if for whatever reason, the JavaScript isn’t loaded, you’ve got a good dependable accessible default.

    Then in the script, replace this with “width=device-width, minimum-scale=1.0, maximum-scale=1.0″ but also add the event handler that listens for a gesture. If a gesture is detected, update the value to “width=device-width, minimum-scale=0.25, maximum-scale=1.6″

    That would feel a little more robust to me, ensuring that the screen is always scaleable (even if the JavaScript isn’t included).

  9. admin says:

    Great idea! updated the script (http://www.lab.highub.com/m/scalefix.html). yea, this can make sure people who have both eyesight problem and js disabled to access.

  10. Jeremy Keith says:

    Excellent! Fantastic work — sorry I was so slow in grasping the idea.

    I’m going to start implementing this. On sites where I’m already loading in jQuery, I’ll probably use the selector $(‘meta[name="viewport"]‘) rather than looping through all the meta elements.

  11. admin says:

    Yea, jQuery is so much easier.

    I have also updated the article above to make the idea clearer :)

  12. Scott Jehl says:

    Great idea!

    I wonder if that iPhone UA detect in there should include a version number and maybe some more specifics to the current (and older) iPhone that has this bug? Just following the logic of IE conditional comments, restricting hacks to LTE latest version allows the hack to die out along with that version of the browser. (I realize it may be in the next version as well though… after all, I reported this bug to Apple mid-last year!).

    One more point on the UA match, and I’m not sure how relevant this is: I believe navigator.userAgent.match(/iPhone/i) will be true for most any browser running on the iPhone (such as Opera Mini), and events aside, I’m not sure this fix needs to apply that broadly.

    Anyway, nice hack! Considering pulling this into jQuery Mobile so we can get user scaling back :)

  13. admin says:

    Hi Scott, thanks for the comment, definitely valid concerns, there is room for improvement.

  14. John Polacek says:

    Sure is nice having you guys around to figure all this stuff out for us. Thanks!

  15. iDGS says:

    More fan mail: You guys are so bright, I’m getting a tan!

    Observed: on *this* page, freshly loaded on an iPhone in either orientation, a two-fingered spread gesture works the first time you try it. No so on the *demo* page. First spread attempt there (freshly loaded, either orientation) is a no-op. Nothing happens. Second try and thereafter, it works as advertised. Not a show-stopper, by any means; but, I thought you’d like to know.

    Also, I see the 0.25 and 1.6 zoom defaults referenced in the official Apple docs, but I don’t see an explanation for those particular limits. I bring this up because my own “natural” spread gesture seems to exceed the allowed for 1.6 magnification on the demo page, resulting in a kind of (objectionable) rubber-band-like “snap-back” to the smaller (presumably 1.6) scale. I can live with it, but it does give me one of those “just wondering…” moments. But again, all nit-picking aside, I’m loving what you guys are doing here! The present demo is a h-u-g-e improvement over the previous status quo. Many thanks!

  16. Ben Smithett says:

    Thanks fellas, this makes me happy :)

    As a fan of Android’s text-reflow-on-zoom-y goodness, the inability to zoom on mobile sites (especially the big, “well made” ones that everyone copies) has irked me for a long time.

  17. Nice work!

    Suggestion: use document.body instead of document.getElementsByTagName('body')[0].

  18. admin says:

    @iDGS: glad it helps :) I think it’s because the default font i used in the example is quite small, so the scale doesn’t seem big.

    @Mathias Bynens: thanks for the tip!

  19. Paul Irish says:

    Here’s the link to where Opera Mini’s UA includes “iPhone”: http://my.opera.com/ODIN/blog/what-opera-mini-on-the-iphone-means-for-developers

    Here is my somewhat optimized snippet of the above:

    var viewportmeta = document.querySelector && document.querySelector(‘meta[name="viewport"]‘),
    ua = navigator.userAgent,

    gestureStart = function () {
    viewportmeta.content = “width=device-width, minimum-scale=0.25, maximum-scale=1.6″;
    },

    scaleFix = function () {
    if (viewportmeta && /iPhone|iPad/.test(ua) && !/Opera Mini/.test(ua)) {
    viewportmeta.content = “width=device-width, minimum-scale=1.0, maximum-scale=1.0″;
    document.addEventListener(“gesturestart”, gestureStart, false);
    }
    };

  20. For the record, here’s my attempt at rewriting the snippet: https://gist.github.com/901295 Be sure to read the comments! Oh, and check out the forks, too.

  21. Pingback: JavaScript Magazine Blog for JSMag » Blog Archive » News roundup: iOS viewport fixes, Mobile Boilerplate, CommunityJS, Ender.js

  22. Pingback: Design for the changing web: Our response :: Studio :: Headshift

  23. Brent says:

    Great discussion and fixes. Thanks very much for figuring this out. I agree about using jquery to get the viewpoint selector.

    I’ve been trying to integrate the boilerplate mobile with jquery mobile, with mixed results. Mainly the css stomps and conflicts with stuff. ScottJ, I’ll keep a lookout out for integration. And any notes on what ‘not’ to mix when integrating these two techs. Thx!!

  24. Shi Chuan says:

    Hi Brent, thanks for letting us know, if there is any specific settings that cause the conflict, do let us know. For the next version of Mobile Boilerplate, I want to do the best to make it as flexible as possible to work with other frameworks.

  25. shaggy says:

    This is good. it seems this would work to fix the same problem on the ipad.
    Does anyone know what are the default min/max-scale values there?

  26. oli says:

    I just looked into the helper.js from MBP. You’re testing for iPhone and iPad. I’m not sure, but shouldn’t you also test for the iPod touch? Like “/iPhone|iPad|iPod/.test(MBP.ua)”? Or just for “/iPhone OS/.test(MBP.ua)”?
    Haven’t tested anything myself, just wondered…

  27. Ryan Cannon says:

    I’m not exactly sure what the issue you’re fixing is, but that demo page definitely doesn’t scale right. Do this:

    * hold the phone in portrait mode
    * load http://www.lab.highub.com/m/scalefix.html
    * rotate the phone to landscape
    * rotate the phone to portrait

    You end up with the page zoomed in slightly.

  28. Pingback: Webkrauts » Ein Blick durch den Viewport

  29. Pingback: JsMag - the magazine for JavaScript developers

  30. Chris says:

    This code is still no good.

    The “gesture” event fires, and then *after* it’s fired, you allow the page to be re-sized, however, the resize of the page was **already disabled** before the event fired, so re-size does not work. (granted – it would work if the visitor tried to re-size a second time, but they won’t, because it already didn’t work the 1st time they did that).

    It also leaves the page in a state where the rotation is going to mess up again, so what you *think* is fixing the problem, has really only made it worse. (you’ve not fixed it, *and* you’ve made users believe they cannot resize the page).

    One definite fix for the problem is this:-

    fix the rotation problem:-

    meta name=”viewport” content=”width=device-width; initial-scale=1.0; minimum-scale=1.0, maximum-scale=1.0″

    fix the scaling problem:-

    var viewportmeta = document.querySelector && document.querySelector(‘meta[name="viewport"]‘),
    ua = navigator.userAgent;

    function allowscale() {
    viewportmeta.content = “width=device-width, minimum-scale=0.25, maximum-scale=3.2″;
    }
    var t=setTimeout(“allowscale()”,1000);

    and re-fix the orientation bug to clean up all problems:-

    function doorientationchange(){

    if (viewportmeta && /iPhone|iPad/.test(ua) && !/Opera Mini/.test(ua)) {
    if(((window.orientation)&2)==2) {
    location.reload(false); // Safari messes up when changing into landscape mode… so reload to fix it.
    }
    }
    }

    document.addEventListener(“orientationchange”,doorientationchange,false);

    Above works in all situations, although it’s not ideal because it does a page-reload to fix the orientation problem (however – making it work, first time, for all visitors is better than all of the various messed-up alternatives out there today).

  31. Chris says:

    So my question for all you geeks out there is this…

    How can we force safari on iOS devices to re-draw an already-loaded web page, without loading it form the server again? We need it to do a total “reset”, and re-read the entire DOM stuff from the start somehow (in order to set it’s internal zoom factor to 1.0).

    Any ideas?

    (other than the obvious – being to use META headers to cache the page, so the “reload” doesn’t really hit the server, but gets everything from the cache instead)

  32. Pingback: jQuery mobile page is zoomed or oversized in iOS landscape mode | fL2rS

  33. M!chel says:

    Great post man!

    It solved my problem! :)

  34. Chris says:

    With extensive hacking, I made everything work perfectly (independent of all the above) – although a side-effect of my fix is that after a rotation, it is possible to scroll past the boundary of the page into blank white areas (not that anyone would – because the page neatly fits the view – who’s going to scroll right when the right-edge is already in view?)

    Anyhow – rather than paste my solution – I’ll paste my “problem”. I want to improve the accessability. I want to “reflow on zoom”, so that people who want the writing bigger, do not henceforth have to spend the rest of their “read” scrolling left and right to read each line. Der.

    So – if someone would be kind enough to give us some “reflow on zoom” samples, I’ll come to the party and incorporate them into my solution, and deliver an “everything” solution – one that does the rotating and resizing reflows all properly (and yes, without a server reload needed).

  35. Thanks , I’ve just been searching for information about this subject for a while and yours is the greatest I have discovered till now. But, what in regards to the bottom line? Are you sure concerning the source?

  36. nootropic says:

    Very interesting info!Perfect just what I was looking for!

  37. Marcos says:

    * load the demo while in portrait
    * zoom in
    * zoom out
    * rotate

    now in landscape its zoomed. :(

    Still trying to figure out a fix to keep the zoom factor.

  38. Pingback: jQuery fix for the iOS viewport

  39. Pingback: 基于HTML5的iPad电子杂志横竖屏自适应方案 - 出家如初,成佛有余 - 关注电子商务领域,关注无线互联网,关注新媒体;

  40. Pingback: 基于HTML5的iPad电子杂志横竖屏自适应方案 « 出家如初,成佛有余

  41. Pingback: Mobile web: problema orientamento e scalatura dispositivi portatili | mattiafrigeri.it

  42. rezwits says:

    Word! My svgs now scale on rotation! with preserveAspectRatio=”none”

  43. Hi, I hopped over to your post via Digg. Not something I ordinarily read, however I appreciate your thoughts nonetheless. Thanks a lot for creating some thing worthy of reading!

  44. If you are going for most excellent contents like me, only pay a quick visit this website all the time as it offers quality
    contents, thanks

  45. What’s up, its pleasant piece of writing regarding media
    print, we all be aware of media is a enormous source of information.

  46. Hello there! This article couldn’t be written
    much better! Looking through this post reminds me
    of my previous roommate! He always kept preaching about this.
    I most certainly will send this post to him.
    Fairly certain he’ll have a great read. Thank you for sharing!

  47. An outstanding share! I have just forwarded this onto a co-worker who had been conducting a
    little research on this. And he in fact bought me lunch because
    I found it for him… lol. So let me reword this…. Thank YOU
    for the meal!! But yeah, thanx for spending some time to discuss this matter
    here on your blog.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>