Pixel-perfect WebView

Published: February 28, 2014

There are a number of options you can use to create the perfect interface for your WebView.

Set the viewport meta tag

The viewport meta tag is of the most important tags to add to your web app. Without it, the WebView may act as if your site is designed for desktop browsers. This causes your web page to be given a larger width (typically 980px) and scales it to fit the WebView width. In most cases, this results in a tiny overview version of the page that requires the user to pan and zoom to actually read content.

If you want the width of your site to be 100% of the WebView's width, set the viewport meta tag:

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

Set width to the special value device-width to gain more control over the page layout.

By default, the WebView sets the viewport to device-width, rather than defaulting to a desktop viewport. However, for reliable and controlled behavior, it's good practice to include the viewport meta tag.

Display desktop sites

In some cases, you may need to display content that isn't designed for mobile devices. For example, you may display content you don't control. In this case, you can force the WebView to use a desktop-size viewport:

If these methods are not set and no viewport is specified, the WebView attempts to set the viewport width based on the content size.

In addition, you may want to use the layout algorithm TEXT_AUTOSIZING, which increases the font size to make it more readable on a mobile device. See setLayoutAlgorithm.

Use responsive design

Responsive design is an approach to designing an interface which changes based on screen size.

There are a number of ways to implement responsive design. One of the most common is @media queries, which applies CSS to elements based on a device's characteristics.

For example, suppose you wanted to go from a vertical layout to a horizontal layout based on orientation. Set CSS properties to default to portrait:

.page-container {
    display: -webkit-box;
    display: flex;

    -webkit-box-orient: vertical;
    flex-direction: column;

    padding: 20px;
    box-sizing: border-box;
}

To switch to a horizontal layout, switch the flex-direction property based on the orientation:

@media screen and (orientation: landscape) {
  .page-container.notification-opened {
    -webkit-box-orient: horizontal;
    flex-direction: row;
  }

  .page-container.notification-opened > .notification-arrow {
    margin-right: 20px;
  }
}

You can also change the layout based on screen width.

For example, adjusting the size of the button width from 100% to something smaller as the physical screen size gets larger.

button {
  display: block;
  width: 100%;
  ...
}

@media screen and (min-width: 500px) {
  button {
    width: 60%;
  }
}

@media screen and (min-width: 750px) {
  button {
    width: 40%;
    max-width: 400px;
  }
}

These are minor changes, but depending on your UI, media queries can help you to make much larger changes to appearance of your application, while keeping the same HTML.

Crisp and clear images

The variety of screens sizes and densities also presents challenges for images. Smaller images require less memory and are faster to load, but blur if you scale them up.

Here are a few tips and tricks to make sure your images look crisp and clear on any screen:

  • Use CSS for scalable effects.
  • Use vector graphics.
  • Provide high-resolution photos.

Use CSS for scalable effects

Use CSS whenever you can, instead of images. It's possible some combinations of CSS properties can be expensive to render. Always test the specific combinations you're using.

Learn more about First Contentful Paint (FCP), which measures the time from when the user first navigated to the page to when any part of the page's content is rendered on the screen. "Content" refers to text, images (including background images), <svg> elements, and non-white <canvas> elements.

Use vector graphics

Scalable Vector Graphics (SVGs) are a great way to provide a scalable image. For images that are well-suited to vector graphics, SVG provides high-quality images with very small file sizes.

Provide high-resolution photos

Use a photo suitable for a high-DPI device and scale the image using CSS. This way, the image can render in a high quality across devices. If you use high compression (low quality setting) when generating the image, you may be able to achieve good visual results with a reasonable file size.

This approach has a couple of potential downsides: highly-compressed images may show some visual artifacts, so you need to experiment to determine what level of compression you find acceptable. And resizing the image in CSS can be an expensive operation.

If high compression is not suitable for your needs, try the WebP format, which gives a high quality image with relatively small file size. Remember to provide a fallback for versions of Android where WebP isn't supported.

Fine-grained control

In many cases, you can't use a single image for all devices. In this case, you can select different images based on the screen size and density. Use media queries to select background images by screen size and density.

You can use JavaScript to control how images load, but this adds complexity.

Media queries and screen density

To select an image based on the screen density, you need to use dpi or dppx units in your media query. The dpi unit represents dots per CSS inch, and dppx represents dots per CSS pixel.

In the following table, you can see the relation between dpi and dppx.

Device pixel ratio Generalized screen density Dots per CSS inch (dpi) Dots per CSS pixel (dppx)
1x MDPI 96dpi 1dppx
1.5x HDPI 144dpi 1.5dppx
2 XHDPI 192dpi 2dppx

The generalized screen density buckets are defined by Android and are used in other places to express screen density (for example, https://screensiz.es).

Background images

You can use media queries to assign background images to elements. For example, if you have a logo image with 256px by 256px size on a device with a pixel ratio of 1.0, you might use the following CSS rules:

.welcome-header > h1 {
  flex: 1;

  width: 100%;

  max-height: 256px;
  max-width: 256px;

  background-image: url('../images/html5_256x256.png');
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

To swap this out for a larger image on devices with device pixel ratio of 1.5 (hdpi) and 2.0 (xhdpi), you can add the following rules:

@media screen and (min-resolution: 1.5dppx) {
  .welcome-header > h1{
    background-image: url('../images/html5_384x384.png');
  }
}

@media screen and (min-resolution: 2dppx) {
  .welcome-header > h1{
    background-image: url('../images/html5_512x512.png');
  }
}

You can then merge this technique with other media queries, such as min-width, which is useful as you account for different form factors.

@media screen and (min-resolution: 2dppx) {
  .welcome-header > h1{
    background-image: url('../images/html5_512x512.png');
  }
}

@media screen and (min-resolution: 2dppx) and (min-width: 1000px) {
  .welcome-header > h1{
    background-image: url('../images/html5_1024x1024.png');

    max-height: 512px;
    max-width: 512px;
  }
}

You might notice that the max-height and max-width are set to 512px for 2ddpx resolution, with an image of 1024x1024px. this is because a CSS "pixel" actually takes into account the device pixel ratio (512px * 2 = 1024px).

What about <img/>?

The web today doesn't have a solution for this. There are some proposals, but they aren't available in current browsers or in the WebView.

In the meantime, if you generate your DOM in JavaScript, you can create multiple image resources in a thoughtful directory structure:

images/
  mdpi/
    imagename.png
  hdpi/
    imagename.png
  xhdpi/
    imagename.png

Then use the pixel ratio to try and pull the most appropriate image:

function getDensityDirectoryName() {
  if(!window.devicePixelRatio) {
    return 'mdpi';
  }

  if(window.devicePixelRatio > 1.5) {
    return 'xhdpi';
  } else if(window.devicePixelRatio > 1.0) {
    return 'hdpi';
  }

  return 'mdpi';
}

Alternatively, you can alter the base URL of the page to define the relative URLs for images.

<!doctype html>
<html class="no-js">
<head>
  <script>
    function getDensityDirectoryName() {
      if(!window.devicePixelRatio) {
          return 'mdpi';
      }

      if(window.devicePixelRatio > 1.5) {
          return 'xhdpi';
      } else if(window.devicePixelRatio > 1.0) {
          return 'hdpi';
      }

      return 'mdpi';
    }

    var baseUrl =
        'file:///android_asset/www/img-js-diff/ratiores/'+getDensityDirectoryName()+'/';
    document.write('<base href="'+baseUrl+'">');
  </script>

    ...
</head>
<body>
    ...
</body>
</html>

This approach blocks page load and forces use of absolute paths for all resources, such as images, scripts, and CSS files, as the base URL points to a density-specific directory.