Skip to content

Instantly share code, notes, and snippets.

@basham
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save basham/c3df72af6e756e2346c5 to your computer and use it in GitHub Desktop.
Save basham/c3df72af6e756e2346c5 to your computer and use it in GitHub Desktop.
Element Queries Draft Spec

Writing element queries in CSS

To indicate a selector should parse as an element query, prefix the media query rule with the media type element. We can use custom media types, because the spec acknowledges that media types are likely to change over time and affords such flexibility. (However, it should be noted that many media types are planned to be consolidated.)

// app/styles/modules/np/component.less
.np-Component {
  background-color: red;

  @media element and (min-width: 20em) {
    background-color: blue;

    &-part {
      border: 1em solid;
    }

    .child {
      font-size: 2em;
    }
  }
}

If wanting to assign a name to the media query in order to expose it for scripting, prefix the name with two hyphens (--) and place it before the element media type. This syntax roughly aligns with the Media Queries Level 4 draft spec for custom media queries and extension names.

.np-Component {
  @media --medium element and (min-width: 40em) {
    background-color: green;
  }
}

Build process configuration

The following shows an example gulp task that would process the source CSS and preconfigured a JavaScript file for the client.

// gulpfile.js
var gulp = require('gulp');
var less = require('gulp-less');
var elq = require('gulp-element-query');

var stylePath = './app/styles';
var styleSrc = stylePath + '/main.less';

var elqConfig = {
  jsDest: './app/bundle-elq.js'
};

gulp.task('elq', function() {
  return gulp.src(styleSrc)
    .pipe(less())
    .pipe(elq(elqConfig))
    .pipe(gulp.dest(stylePath));
});

Run the task in the command line.

$ gulp elq

Behind the scenes of the build process

Upon executing the build process, the following should occur:

Nested media queries extract into the body of the CSS once LESS compiles.

.np-Component {
  background-color: red;
}
@media element and (min-width: 20em) {
  .np-Component {
    background-color: blue;
  }
  .np-Component-part {
    border: 1em solid;
  }
  .np-Component .child {
    font-size: 2em;
  }
}
@media --medium element and (min-width: 40em) {
  .np-Component {
    background-color: green;
  }
}

Using PostCSS, the source CSS is evaluated for element media queries, with the first selector in each query block (e.g. .np-Component) assumed as the target. LESS prioritizes parent rules in the source order, so this assumption shouldn't be problematic when nesting rules or utilizing the parent selector.

The element media queries are replaced with a modifier class name based on the target selector (e.g. .np-Component) and either the supplied name (e.g. --medium) or a random, obscured, globally unique name (e.g. --bu3Pu). All generated names are evaluated against the source CSS to guarantee uniqueness.

Task: Evaluate unique id projects and recommend one:

.np-Component {
  background-color: red;
}
.np-Component--bu3Pu {
  background-color: blue;
}
.np-Component--bu3Pu .np-Component-part {
  border: 1em solid;
}
.np-Component--bu3Pu .child {
  font-size: 2em;
}
.np-Component--medium {
  background-color: green;
}

A JSON object is created with the corresponding config and compiled into the indicated JavaScript bundle.

{
  ".np-Component": {
    "--bu3Pu" : {
      "className": "np-Component--bu3Pu",
      "name": "--bu3Pu",
      "query": "(min-width: 20em)"
    },
    "--medium": {
      "className": "np-Component--medium",
      "name": "--medium",
      "query": "(min-width: 40em)"
    }
  }
}

Element queries in HTML

Include the compiled JavaScript at the end of the <body>.

<script src="app/bundle-elq.js"></script>

The JavaScript will automatically execute and actively watch the DOM for matching selectors.

<div class="np-Component"></div>

The script finds a match in this DOM, so it appends the <object> element, so the media rules can be evaluated.

<div class="np-Component">
  <object type="text/html" data="about:blank"></object>
</div>

The modifier class names conditionally toggle as each rule is evaluated.

<div class="np-Component np-Component--bu3Pu">
  <object type="text/html" data="about:blank"></object>
</div>

Scripting based on element query events

// Mounts or overrides element query.
// Shortcut for `window.elementQuery().mount(config);
var newComponents = window.elementQuery(config);

var components = window.elementQuery(); // array of all elementQuery objects.

var component = window.elementQuery('.np-Component');
var elements = component.elements; // array of DOM objects
var mediaList = component.media; // array of MediaQueryList objects

var media = component.matchMedia('--medium');

media.addListener(function(data) {
  data.className; // 'np-Component--bu3Pu'
  data.element; // DOM object
  data.matches; // true
  data.query; // '(min-width: 20em)'
  data.selector; // '.np-Component'
});

media.removeListener();

Resources

Parse a media query into a JavaScript object and compare. https://github.com/ericf/css-mediaquery

@basham
Copy link
Author

basham commented Aug 15, 2014

The elq.js project is an interesting take on this problem. However:

  1. All the element query detection happens on the client, instead of in the build process.
  2. It doesn't use the <object> technique to eval media queries, instead using window resize events to evaluate a typical subset of the spec (width, height, aspect-ratio, and their min/max derivatives).
  3. Unsure if the pseudo classes technique (:media(...)) is appropriate; still thinking a rule technique (@media element and (...)) is more aligned with the spirit of the CSS specs.

@basham
Copy link
Author

basham commented Aug 21, 2014

postcss-custom-media is a great exemplar for how to parse and use this syntax:

@custom-media --small-viewport (max-width: 30em);

@media (--small-viewport) {
  /* styles for small viewport */
}

@basham
Copy link
Author

basham commented Nov 23, 2014

elementary is a very simple take on element queries, which parses ::before content. Example CSS:

.mod-foo:before {
  content: "300 410 500";
}
.mod-foo {
  background: green;
  color: #fff;
  padding: 20px;
}
.mod-foo[data-minwidth~="300"] {
  background: blue;
}
.mod-foo[data-minwidth~="410"] {
  background: red;
}
.mod-foo[data-minwidth~="500"] {
  background: gray;
}

@basham
Copy link
Author

basham commented Apr 2, 2015

Syze provides an interesting API to set media queries in JavaScript.

syze.sizes(480, 768, 1920).names({ 320:'Mobile', 768:'Tablet', 1920:'HdSize' });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment