[css4-images] The image-set() function (for responsive images)

Hi all,

It's all too easy for authors to make mistakes when adapting their sites
for high-resolution displays such as the iPhone's Retina display.
Consider the following stylesheet:

  …
  selector {
    background: url(foo-lowres.png) center;
  }
  …
  @media mq {
    …
    selector {
      background: url(foo-highres.png) center / 100px 100px;
    }
    …
  }
  …

The references to the low- and high-resolution variants of foo.png are
far apart from one another in the stylesheet, and the (potentially
complicated) selector has been duplicated. On a large site with many
image assets, this causes stylesheets to become very large and less
maintainable. Here are just a few of the problems with the current
situation:

* Bugs due to non-locality: One developer fixes a bug in the selector,
  but only in the low-resolution case. Another developer changes an
  image reference to refer to a different icon, but only in the
  high-resolution case.

* Both assets may be loaded by the browser, which may degrade
  performance in a constrained bandwidth environment.

* Authors can't specify both assets inside a style="" attribute.

I'd like to propose a new function for the Images module. This function
will allow developers to provide, in a compact manner, multiple variants
of the same image at differing resolutions. Using @media pushes the two
asset references apart from one another, whereas such a function keeps
related asset references together. It also helps keep selectors DRY.
We've called it image-set(), and it takes one or more image specifiers.
Image specifiers consist of an asset reference and a scale factor:

  image-set( imagespec [ ',' imagespec ]* )
  imagespec ::= <image> S {num} 'x'

The above example could be rewritten using image-set() like so:

  selector {
    background: image-set(url(foo-lowres.png) 1x,
                          url(foo-highres.png) 2x) center;
  }

By using image-set() here, the author is saying that foo-highres.png is
twice the resolution of foo-lowres.png. UAs which support image-set()
could then use the 2x image on a high-resolution display, and the 1x
image on a low-resolution display. UAs aren't required to fetch the
assets in order to determine which should be displayed, so we avoid
redundant asset loading.

Some Q&A:

* What's the intrinsic size of an image-set()? Does it vary depending on
  which image is picked? Does the UA apply the scale factor to derive
  the intrinsic size of the image?

  The intrinsic size of the image-set() can be computed from the
  intrinsic size of the actual image asset chosen and that asset's
  associated scale factor.

  Suppose that foo-lowres.png is 100x100 and foo-highres.png is 200x200
  in the above example. If the UA chooses foo-lowres.png, it computes
  the intrisnic size as (100/1)x(100/1) = 100x100. If the UA chooses
  foo-highres.png, it computes the intrisnic size as (200/2)x(200/2) =
  100x100.

* Why a scale factor and not a full-blown media query?

  Media queries are a claim about the state of the UA, whereas here
  we're making a claim about (the relationship between) the image assets
  themselves. It would be confusing to use similar syntax for such
  different things. Also, UAs should have the ability to choose between
  the given variants based on a variety of factors. For instance, a UA
  could use the lower res asset when a user has zoomed out. No existing
  media query distinguishes between the page being zoomed-out and being
  zoomed in. And even if such a media query existed, UAs should be free
  to choose between variant assets regardless of which media queries
  happen to match.

* The problem of stylesheet verbosity when using media queries isn't
  limited to image assets. Shouldn't we have a general mechanism for
  keeping media-specific property values together in rule sets?

  In talking with content producers here, we've found that the
  non-locality of asset references is a real source of Web author pain.
  I think a focused feature to help with that pain is sensible. That
  said, we should also explore how to help authors avoid selector
  repetition and the other pains of @media in general.

* Why not enhance the image() function instead of inventing a new
  function?

  Unlike image(), these image specifiers are unordered. This isn't about
  fallback. There is no preferred variant. UAs are free to use whichever
  image it believes would be best. For instance, consider the page zoom
  example I mentioned earlier. Authors could even use image() and
  image-set() together to handle more exotic cases, e.g.:

    image(image-set(url(foo.png) 1x, url(foo@2x.png) 2x) rtl,
          image-set(url(bar.png) 1x, url(bar@2x.png) 2x) ltr);

* What about other cases where authors would like to specify alternative
  image assets, such as when the UA is on a low- or high-bandwidth
  connection?

  This is definitely something worth exploring. In the future we could
  extend the asset descriptors to cover such cases. Something like this,
  maybe:

    selector {
      background: image-set(url(foo-lowres.png) 1x low-bandwidth,
                            url(foo-highres.png) 2x high-bandwidth);
    }

  I don't have a proposal for how to describe bandwidth here, though,
  and I'd love to hear ideas. I don't think addressing the multiple-
  resolutions case needs to wait for a solution to the bandwidth case.

* What about content images?

  First off, this is www-style, so the design of an HTML feature for
  responsive <img>es is out of scope. :) That said, I can imagine the
  image-set() microsyntax being used in an attribute like so:

    <img alt="A description of foo"
         src=foo-lowres.png
         set="foo-lowres.png 1x, foo-highres.png 2x">

  I'll post something to the whatwg thread referencing this proposal.


Ted

Received on Wednesday, 22 February 2012 00:58:26 UTC