Drupal 7, AdaptiveTheme, and iPad meta viewport Bug?

This week, I've been working on a microsite for one of my clients. Since the target platform is an iPad, I decided to try AdaptiveTheme (base theme) and Sky (sub-theme) for this particular project. There's a lot going on with responsive/adaptive design so it's good to try out different base themes and get an idea of what's possible today.

As I started testing the microsite with the iPad and mobile Safari, I noticed an error when rotating to landscape. It was odd since everything was loading correctly in portrait or landscape without rotation. But, once I changed the orientation to or back to landscape, the iPad automatically zoomed in a bit. Think of automatic pinch-to-zoom. Immediately, I checked the theme settings but everything looked good.

Then I thought, oh, it's a bug with AdaptiveTheme. So, I searched the issue queue...nothing. I searched Google with AdaptiveTheme in the keyword phrases...nothing. As it turns out, it isn't an AdaptiveTheme issue at all...it's a viewport mobile Safari/iOS bug.

A "Solution"?

Basically, you want to constrain the viewport scaling to a min and max of 1.0 until the user explicitly requests interaction. Meaning, an event is fired (e.g., touchstart, gesturestart, etc.). This way, a rotation will always "scale" within the min and max parameters. The solution is well documented on StackOverflow and Adactio so they get all the credit for identifying and solving the initial issue. Btw, notice my small modification in their code below. Adding the event to the body did not work for me in mobile Safari/iOS 5.1.

/* javascript */
(function ($) {
  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, initial-scale=1.0';
      // I changed this from document.body.addEventListener to document.addEventListener
      document.addEventListener('gesturestart', function () {
          viewportmeta.content = 'width=device-width, minimum-scale=0.25, maximum-scale=1.6';
      }, false);
    }
  }
})(jQuery);

Since you want to load this code for every Drupal page, put it in a javascript file loaded by your theme .info file. I am assuming you'll be wrapping other functions within this file that need jQuery. If not, you can remove the $ and jQuery parameters. You could also add inline javascript code by using drupal_add_js and setting every_page to TRUE.

While this solution isn't perfect as it can lead to some hesitation or a double gesture, it still allows the user to zoom vs. the alternative of using maximum-scale-1.0 (no user scaling).

But, there were still issues with usability.

Another "Solution"?

In the prior example, once the gesturestart event fires, the viewport was set so you can zoom the page. Well, this is great until you rotate the page to portrait then go back to landscape. Guess what? The problem comes back. What I needed was a way to reset the viewport each time the iPad was rotated...think orientationchange event. Again, we always want to constrain the rotation by the min and max values and allow the user to initiate the zoom.

In thinking about this a bit more and paying attention to how I use my iPad, my first interaction with the screen is typically a touch not a gesture. So, if I extrapolate my statistically insignificant sample and make even more assumptions, it makes sense to allow the viewport to scale when the touchstart event fires. This is a cleaver way to hide the hesitation in the above example. And, if the user starts with a gesture, well, we haven't made the hesitation problem any worse.

/* javascript */
(function ($) {
  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, initial-scale=1.0';
      document.addEventListener('touchstart', function () {
          viewportmeta.content = 'viewportmeta.content = width=device-width, minimum-scale=0.25, maximum-scale=1.6';
      }, false);
      document.addEventListener('orientationchange', function () {
          viewportmeta.content = 'viewportmeta.content = width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0';
      }, false);
    }
  }
})(jQuery);

I have no idea if my usage is typical or atypical of a tablet user. As far as usability, I think it's a better solution for my client. We'll see once the testing feedback starts rolling in!

More "Solutions"?

If above ideas do not work for you, some other "solutions" are...

  1. To disable scaling all together, in the html.tpl.php template or hook_preprocess_html function, set the meta tag viewport to:
     <!-- html -->
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0">
  2. Remove the viewport line all together and allow the iPad to scale the page to fit the screen. I know, what's the point of responsive design then...

While these are less than ideal, you might be forced to use them depending upon your needs.

The Latest Solution: Listen to the Accelerometer

Added 8/6/2012: This solution works by listening to the device's accelerometer and tries to predict when an orientation change is about to occur. The full details of this fix can be found at iOS orientationchange zoom bug on Github.

Other solutions? Comments? Typos? Let me know!

Add new comment

Enter your email address. This will not be displayed when your comment is posted.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Refresh Type the characters you see in this picture. Type the characters you see in the picture; if you can't read them, submit the form and a new image will be generated. Not case sensitive.  Switch to audio verification.