jescalan/rupture

Feature Request: Configuration to 'rasterize' media queries to please IE8

Opened this issue · 7 comments

There are scripted ways to to resolve the lack of media query support in older browsers but I have just read a developer article on 'The Guardian' website who have proposed a much nicer solution:

http://www.theguardian.com/info/developer-blog/2013/oct/14/mobile-first-responsive-ie8

They have exposed a global property in their Sass configuration which allows you to effectively disable all unsupported media query tags entirely. Then you can generate two versions of the compiled stylesheet; one for modern browsers, and then another for older browsers.

They have also set up an example which you can mess with:

http://sassmeister.com/gist/6130742

Things like pixel density selection could be entirely omitted. The "static" media query selection might be achieved by setting a global threshold $static-min-width.

I thought that this was a pretty cool idea and just wanted to share it here in case it was of interest :)

I have been experimenting with this and logically it seems like there would be no need for the variable $static-min-width.

Here is what I have been experimenting with and it seems to do the trick:

base-font-size ?= 16px

// Set this to `true` when generating stylesheet for IE 8 and below.
suppress-responsive ?= false

...

between(min, max, anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width)
  if -is-string(orientation)
    orientation = convert(orientation)
  if -is-string(density)
    density = convert(density)
  if -is-string(min)
    min = -get-scale-number(min)
  if -is-string(max)
    max = -get-scale-number(max)

  -min = rupture.scale[min - 1] unless -is-zero(min) or (not -on-scale(min))
  -max = rupture.scale[max] unless not -on-scale(max)
  -min ?= min
  -max ?= max

  if suppress-responsive
    if not (density or -max)
      {block}
  else
    condition = 'only screen'
    use-device-width = use-device-width ? 'device-' : ''
    unless -min is 0
      -min = -convert-to('em', -min) if rupture.enable-em-breakpoints
      -min = -adjust-overlap(anti-overlap, -min, side: 'min')
      condition = condition + ' and (min-' + use-device-width + 'width: %s)' % (-min)
    unless -larger-than-scale(max)
      -max = -convert-to('em', -max) if rupture.enable-em-breakpoints
      -max = -adjust-overlap(anti-overlap, -max, side: 'max')
      condition = condition + ' and (max-' + use-device-width + 'width: %s)' % (-max)
    if orientation
      condition = condition + ' and (orientation: %s)' % (orientation)
    if density
      conditions = ()
      for query in -density-queries(density)
        push(conditions, condition + ' and %s' % (query))
      condition = join(', ', conditions)
    @media condition
      {block}

...

I haven't had a chance to look into the rest of the Rupture implementation, but I suspect that rules could be omitted elsewhere as well when supress-responsive is set to true.

I have spent some more time tweaking this:

  • Media block is now also excluded when orientation is specified.
  • Done similar for +density, +landscape and +portrait mixins.

Here is the contents of the entire script in case this is of interest to anyone :)

base-font-size ?= 16px
suppress-responsive ?= true

rupture = {
  mobile-cutoff: 400px
  desktop-cutoff: 1050px
  hd-cutoff: 1800px
  enable-em-breakpoints: false
  base-font-size: base-font-size
  anti-overlap: false
  density-queries: 'dppx' 'webkit' 'moz' 'dpi'
  retina-density: 1.5
  use-device-width: false
}
rupture.scale = 0 (rupture.mobile-cutoff) 600px 800px (rupture.desktop-cutoff) (rupture.hd-cutoff)
rupture.scale-names = 'xs' 's' 'm' 'l' 'xl' 'hd'
-is-string(val)
  if typeof(val) is not 'unit'
    if val is a 'string' or val is a 'ident'
      true
    else
      false
  else
    false
-get-scale-number(scale-name)
  for list-item, i in rupture.scale-names
    if list-item is scale-name
      return i + 1
  return false

-convert-to(to-unit, value, context = rupture.base-font-size)
  from-unit = unit(value)
  return value if to-unit is from-unit
  if to-unit in ('em' 'rem')
    return value if from-unit in ('em' 'rem')
    return unit((value / context), to-unit)
  if to-unit is 'px'
    return unit((value * context), 'px')

-on-scale(n)
  return unit(n) is ''

-larger-than-scale(n)
  return (n > (length(rupture.scale) - 1)) and -on-scale(n)

-is-zero(n)
  return n is 0

-overlap-shift(anti-overlap, n)
  shift-unit = unit(n)
  anti-overlap = 0px unless anti-overlap
  anti-overlap = 1px if anti-overlap is true
  if length(anti-overlap) is 1
    return -convert-to(shift-unit, anti-overlap)
  for val in anti-overlap
    return val if unit(val) is shift-unit

-adjust-overlap(anti-overlap, n, side = 'min')
  -shift = -overlap-shift(anti-overlap, n)
  if (side is 'min' and -shift > 0) or (side is 'max' and -shift < 0)
    n = n + -shift
  return n

-is-positive(n)
  return n >= 0

-density-queries(density)
  if typeof(density) is not 'unit'
    if not -is-string(density)
      density = '%s' % density
  density = rupture.retina-density if density is 'retina'
  queries = ()
  for query in rupture.density-queries
    if query is 'webkit'
      push(queries, '(-webkit-min-device-pixel-ratio: %s)' % (density))
    else if query is 'moz'
      push(queries, '(min--moz-device-pixel-ratio: %s)' % (density))
    else if query is 'o'
      push(queries, '(-o-min-device-pixel-ratio: %s/1)' % (density))
    else if query is 'ratio'
      push(queries, '(min-device-pixel-ratio: %s)' % (density))
    else if query is 'dpi'
      if -is-string(density)
        density=convert(density)
      push(queries, '(min-resolution: %sdpi)' % (round(density * 96, 1)))
    else if query is 'dppx'
      push(queries, '(min-resolution: %sdppx)' % (density))
  return queries

// +between(min, max)
// usage (scale can be mixed with custom values):
//   - +between(1, 3) scale:scale
//   - +between(0, 3) 0 width:scale
//   - +between(200px, 500px) custom:custom
//   - +between(0, 300px) 0 width:custom
//   - +between(1, 300px) scale:custom
//   - +between(200px, 4) custom:scale

between(min, max, anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width)
  if -is-string(orientation)
    orientation = convert(orientation)
  if -is-string(density)
    density = convert(density)
  if -is-string(min)
    min = -get-scale-number(min)
  if -is-string(max)
    max = -get-scale-number(max)

  -min = rupture.scale[min - 1] unless -is-zero(min) or (not -on-scale(min))
  -max = rupture.scale[max] unless not -on-scale(max)
  -min ?= min
  -max ?= max

  if suppress-responsive
    if not (density or -max or orientation)
      {block}
  else
    condition = 'only screen'
    use-device-width = use-device-width ? 'device-' : ''
    unless -min is 0
      -min = -convert-to('em', -min) if rupture.enable-em-breakpoints
      -min = -adjust-overlap(anti-overlap, -min, side: 'min')
      condition = condition + ' and (min-' + use-device-width + 'width: %s)' % (-min)
    unless -larger-than-scale(max)
      -max = -convert-to('em', -max) if rupture.enable-em-breakpoints
      -max = -adjust-overlap(anti-overlap, -max, side: 'max')
      condition = condition + ' and (max-' + use-device-width + 'width: %s)' % (-max)
    if orientation
      condition = condition + ' and (orientation: %s)' % (orientation)
    if density
      conditions = ()
      for query in -density-queries(density)
        push(conditions, condition + ' and %s' % (query))
      condition = join(', ', conditions)
    @media condition
      {block}

at(scale-point, anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width)
  if -is-string(orientation)
    orientation = convert(orientation)
  if -is-string(density)
    density = convert(density)
  +between(scale-point, scale-point, anti-overlap, density, orientation, use-device-width)
    {block}

from-width(scale-point, anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width)
  if -is-string(orientation)
    orientation = convert(orientation)
  if -is-string(density)
    density = convert(density)
  +between(scale-point, length(rupture.scale), anti-overlap, density, orientation, use-device-width)
    {block}

above = from-width

to-width(scale-point, anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width)
  if -is-string(orientation)
    orientation = convert(orientation)
  if -is-string(density)
    density = convert(density)
  +between(1, scale-point, anti-overlap, density, orientation, use-device-width)
    {block}

below = to-width

mobile(anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width)
  if -is-string(orientation)
    orientation = convert(orientation)
  if -is-string(density)
    density = convert(density)
  +below(rupture.mobile-cutoff, anti-overlap, density, orientation, use-device-width)
    {block}

tablet(anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width)
  if -is-string(orientation)
    orientation = convert(orientation)
  if -is-string(density)
    density = convert(density)
  +between(rupture.mobile-cutoff, rupture.desktop-cutoff, anti-overlap, density, orientation, use-device-width)
    {block}

desktop(anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width)
  if -is-string(orientation)
    orientation = convert(orientation)
  if -is-string(density)
    density = convert(density)
  +above(rupture.desktop-cutoff, anti-overlap, density, orientation, use-device-width)
    {block}

hd(anti-overlap = rupture.anti-overlap, density = null, orientation = null, use-device-width = rupture.use-device-width)
  if -is-string(orientation)
    orientation = convert(orientation)
  if -is-string(density)
    density = convert(density)
  +above(rupture.hd-cutoff, anti-overlap, density, orientation, use-device-width)
    {block}

density(density, orientation = null)
  if not suppress-responsive
    conditions = ()
    for query in -density-queries(density)
      condition = 'only screen and %s' % (query)
      if orientation
        condition = condition + ' and (orientation: %s)' % (orientation)
      push(conditions, condition)
    condition = join(', ', conditions)
    @media condition
      {block}

pixel-ratio = density

retina(orientation = null)
  +density('retina', orientation)
    {block}

landscape(density = null)
  if not suppress-responsive
    if -is-string(density)
      density = convert(density)
    if density
      +pixel-ratio(density, orientation: landscape)
        {block}
    else
      @media only screen and (orientation: landscape)
        {block}

portrait(density = null)
  if not suppress-responsive
    if -is-string(density)
      density = convert(density)
    if density
      +pixel-ratio(density, orientation: portrait)
        {block}
    else
      @media only screen and (orientation: portrait)
        {block}

Woah @rotorz thanks for all the research and work here! It seems like this kind of discussion would be better suited for a pull request. Could you perhaps take this code and submit it as a PR? Also want to ping @declandewet here.

This is definitely something that seems interesting to me, so would love to continue the conversation about getting this pulled it 😀

Awesome, I will sort a pull request out for you :D

I have created a pull request, I haven't done a PR on GitHub before so please let me know if I have done anything wrong!

Your PR looks great, it just needs some tests :)

Closed by #46, available in v0.6.0