CSS regions, part 2

The landscape for CSS regions has changed drastically since the last time I visited the technology. Since the last post about the technology (published in October, 2013) the following events took place:

  • Google, citing concerns about performance, withdrew region support from the Blink rendering engine (Cnet posted an article about this)
  • Mozilla split some aspects of the CSS regions specification into another specification which they believe satisfies the use cases put forward by CSS Regions in a way that our team can support in a much more secure, reliable, and performant manner.
  • L. Dave Baron opposed regions as proposed in the specification in the context of performance and its use as a language primitive for the Extensible Web Movement as expressed in their manifesto
  • HÃ¥kon Wum Lie, Opera’s CTO, is also opposed to Regions as proposed (documented in this a list apart article and in this followup)
  • Microsoft has shapes under consideration

For those interested the thread that first got my attention starts with this message and it moves through this and other threads

My concern is that instead of working on improving the current spec both companies decided to go their own way, not supporting the spec as written and proposing their own versions to the W3C. Sadly, until they settle their argument, the lack of a unified specification leaves developers, who are not as opposed to the idea, having to polyfill the feature in half the modern browsers, whether evergreen or not.

Now into specifics.

Regions

Regions are a way to fragment the content of a web page into distinct containers to create magazine-like layouts without having to worry about content flow or where the content will be positioned. This feature is comparable to Fixed Layout ebooks and print magazine layouts generated with Adobe InDesign.

The biggest advantage of the specification is that we no longer have to worry about where the content will flow when it fills the current region. We will discuss how to create regions and flows this later in the article.

Creating regions consist of four steps.

  1. Create the container where the content will flow into
  2. Specify the content section that the content will flow from
  3. Set up the CSS for the containers created in step 1 and 2
  4. Style the resulting region if you so choose

An example from a project under development:

The HTML content below covers both creating the div that will hold the content and the div that has the content we’ll place.

[code lang=html]
<div class='content-overview-region' id='overview01'></div>

<div class='burningman-overview'>

<p><strong>Burning Man</strong> is a week-long annual event that began in San Francisco's Baker Beach and migrated to the Black Rock Desert in northern Nevada, in the United States. The event begins on the last Monday in August, and ends on the first Monday in September, which coincides with the American Labor Day holiday. It takes its name from the ritual burning of a large wooden effigy, which is set alight on Saturday evening. The event is described as an experiment in community, art, radical self-expression, and radical self-reliance.</p>
</div><!– Closes burningman-overview–>
[/code]

The CSS part of the project has the source of the content (using the burningman-overview class) and the region we’ll create to place the content (using the content-overview-region class). We also use content-overview-region to style the content inside the region.

I also like to assign borders with different colors to each region I’m working with to create a visual layout of the content in the page as I develop the layout.

[code lang=css]
/**
* OVERVIEW FLOW
*
* Defines the source of the content that will flow into the
* overview region of the document
*/
.burningman-overview {
flow-into: content-overview-region;
-webkit-flow-into: content-overview-region;
-ms-flow-into: content-overview-region;
}

.content-overview-region {
flow-from: content-overview-region;
-webkit-flow-from: content-overview-region;
-ms-flow-from: content-overview-region;
}

/**
* STYLE FOR THE OVERVIEW REGION
*/
.content-overview-region {
position: relative;
width: 90%;
height:auto;
margin: 0 auto;
padding: 2em;
border: 1px solid purple;
}
[/code]

As your content grows larger you have two options: create more regions to flow the content into (which you can use to create columns or other shapes), make the single regions you created automatically grow to fit the content using height: auto (as in the example above) or use the CSS Object Model (CSSOM) to programmatically add containers for your content to fill into. It will all depend on your design and layout goals for your current project.

A CSS Region experiment is available in this codepen. Currently it only works in Safari and Internet Explorer as it does not use the regions polyfill.

You can find an example of programmatically adding regions to existing content in the CSS Regions Polyfill Github repository and in this codepen

Alternatives: using @support

Another alternative is to use the @support css at-rule to give one set of styles, including regions, for those browsers that support them and another set of styles for those browsers that don’t.

Lea Verou wrote a tutorial on how to use the @support feature in your CSS code. Mozilla Developers Network has useful information and examples, particularly the compatibility table.

If we look at the MDN compatibility table we see the biggest weakness of the @support at-rule. It is not supported accross the board… in the case of regions, though, the browsers that we need to target as not supporting regions (Chrome and Firefox) support the @support at-rule. I would construct the rules like this:

@support not (flow-into: content-overview-region;) {
  /* 
    Write rules here to accommodate browsers not supporting
    regions, maybe using media queries to address positioning
    and size
  */

Alternative: using Modernizr or other conditional loaders

Modernizr feature detection is another tool we an use to conditionally load content based on support (or lack thereof) for a given feature.

Modernizr test for css regions under the non-core detects (you have to manually add the test as it is not added to the default build.)

To make sure that the polyfill doesn’t conflict with native implementations of the specification I use Modernizr.load to test for browser support in regions and then only load the JavaScript polyfill for browsers that do not support the feature. I placed the following JavaScript code on the head of my page.

<html class='no-js'>
  <head>
  <!-- portions of the document head section omitted -->
  <script src="scripts/modernizr-region.js"></script>
  <script>
    Modernizr.load({
      test: Modernizr.region,
      nope: 'scripts/cssregions.js'
    });
   </script>
   </head>
   

See the Pen mHlxL by Carlos Araya (@caraya) on CodePen.

To cut the bandwidth requirements for the demo, I created a custom Modernizr build with only regions and load support. I then used Modernizr.load to test for support of regions (the test part) and only load the polyfill when the test fails (the nope section). If you’re already using Modernize you can just add the CSS regions feature test (in the non core section) and download a new build for your project.

Alternative: CSS Regions Polyfill

All is not lost if you want to move this into more serious testing or even some light production work. There is a CSS Region Polyfill available that makes regions work in all browsers.

The polyfill doesn’t change the CSS code described above. We coded defensively and added the three possible options we can use for CSS regions:

  • Unprefixed version used by the polyfill code
  • -webkit used by Safari
  • -ms used by IE

Everything is not rosy. The polyfill lacks support for some features and has some serious limitations. From the polyfill Readme file:

Some features are not supported:

CSS:

Basic @region support (styling fragments based on current region)

JS:

NamedFlow.getRegionFlowRanges()

Known issues with regions polyfill

From the polyfill Readme file:

Some caveats apply:

  • Because the code is asynchronous, the only way to be sure you can act on a NamedFlow is to listen to its regionfragmentchange event. Unlike the browser which computes the layout of the page synchronously, the JavaScript implementation is asynchronous by nature and cannot perform synchronous operations
  • Another consequence of the code executing asynchronously is that screen flashing is possible in some cases, especially during the page load if correct display:none styling is not applied to hide the source content wrapper before the content itself is flown into a region. It’s also advised to put overflow: hidden on regions when possible even if it shouldn’t be strictly required
  • The regionoversetchange event is not guaranteed to fire only when the overset actually changes. Guaranteeing this would requires storing a lot of information and compare them at runtime, and I decided it would not be worth the time
  • Dynamic elements cannot be put into a flow without harming their functionnality (this incudes forms, and a lot of interactive objects). This implementation is only suitable for static or mostly static content
  • In the same vein, hover and active style do not apply to content inside a region. This limitation could possibly be lifted in some cases but I await feedback this is actually useful before proceeding
  • Because elements are actually cloned in the regions, you may receive those clones as a result of getElementsByTagName or querySelectorAll queries, as well as methods such a elementsFromPoint. The actual ID and class names of the objects are not preserved in the fragments to reduce the risk, but this is by no mean a complete guarantee. A solution is to check the data-css-regions-fragment-of attribute and recover the original source by using the data-css-regions-fragment-source attribute
  • Because computing nested css-counters manually would be very expensive in cpu horse power, I decided to leave this case as is. Most non-nested css-counters should work fine, however

The biggest unknown for the polyfill is performance in mobile devices. Neither Adobe or the polyfill author has made statements about performance of the polyfill in mobile devices. This may not be an issue for simple layouts but will definitely become a problem for more complex layouts and designs.

The answer is the same as with many other development projects. Test, iterate, get feedback and iterate some more.