Extending CSS with Event-Driven Virtual Stylesheets

D2ddf71464b997e71ddb17c1812285b9?s=47 Tommy Hodgins
January 24, 2018

Extending CSS with Event-Driven Virtual Stylesheets

A talk explaining the places and way that CSS can be extended using JavaScript by populating virtual stylesheets

D2ddf71464b997e71ddb17c1812285b9?s=128

Tommy Hodgins

January 24, 2018
Tweet

Transcript

  1. Extending CSS with Event-Driven Virtual Stylesheets

  2. Extending CSS with Event-Driven Virtual Stylesheets

  3. “Extending CSS” • Adding new styling abilities • Doing things

    CSS can’t ‘polyfill’ using existing features • Leveraging JavaScript and built-in DOM APIs for styling • Styling beyond the scope of what CSS is designed to do
  4. “Extending CSS” is NOT • Not replacing or reimplementing what

    CSS already does • Not just adding syntactic sugar to CSS (pre-processing) • Not necessarily violating the “Separation of Concerns”
  5. Extending CSS with Event-Driven Virtual Stylesheets

  6. “Event-Driven” • CSS is already event-driven (:hover, :focus, etc) •

    Style changes often mirror events happening in the browser (@media) • Style changes are often driven by events in Javascript
  7. Extending CSS with Event-Driven Virtual Stylesheets

  8. “Virtual Stylesheets” • Dynamic stylesheet that can change over time

    • Leverage JavaScript logic for templating • Leverage JavaScript for interpolating values • Can be located in DOM or CSSOM
  9. Types of Virtual Stylesheet

  10. Types of Virtual Stylesheet • <style> tag in HTML •

    <script> or <template> in HTML • External <script> or <link> file • Any string in JavaScript
  11. Places Where You Can
 Add Logic to Styles

  12. Places Where You Can
 Add Logic to Styles • HTML:

    using custom attributes (for selector, test, event) • CSS: using custom attribute selectors or CSS variables • JS: using strings, variables, and functions
  13. Useful Events for Styling

  14. Useful Events for Styling • Global events: load, resize, input,

    click • Element-based events: scroll, mutation observer, resize observer • App-based events: JS framework updates, plugin callbacks, state-based styling logic in apps
  15. Useful Tests for Styling

  16. Useful Tests for Styling • el => el.offsetWidth < 500

    // less than • el => el.offsetWidth <= 500 // less or equal (max) • el => el.offsetWidth == 500 // equal • el => el.offsetWidth >= 500 // greater or equal (min) • el => el.offsetWidth > 500 // greater than
  17. Useful Properties for Testing • offsetWidth • offsetHeight • textContent.length

    || value.length • children.length • scrollTop • scrollLeft
  18. The 3 Rules of
 Event-Driven Styling

  19. Rule №1

  20. Do everything in CSS that can be done in CSS

  21. AKA: Let CSS be CSS,
 let selectors be selectors

  22. Rule №2

  23. Limit the number of tests being performed

  24. Rule №3

  25. Consolidate event listeners

  26. Style Scoping

  27. Style Scoping • Exists at 3 levels: stylesheet, selector, and

    property • 1 scoped stylesheets: @media & container queries • 2 scoped selectors: :has(), :hover, element queries • 3 scoped properties: CSS variables, JS interpolation
  28. A Selected History of Scoped Style Proposals

  29. Scoped Style Proposals • CSS first proposed in 1994 •

    JavaScript Style Sheets (JSSS) by Netscape in 1996 • Dynamic Properties in IE5-8 by Microsoft in 1999 • CSS @media queries proposed in 2001
 (standardized in 2012 after eleven years of drafting) • :scope proposed in CSS Selectors Level 4 in 2011 (still in draft) • Houdini specs proposed in 2016 (still active WIP)
  30. EDVS Plugins

  31. EDVS Plugins • Load stylesheets • Are aware of JavaScript

    logic and events • Populate virtual stylesheets • Interact with helper functions (mixins) to extend CSS
  32. Template for a
 basic EDVS Plugin

  33. function JSinCSS() { var tag = document.querySelector('#JSinCSS') if (!tag) {

    tag = document.createElement('style') tag.id = 'JSinCSS' document.head.appendChild(tag) } tag.innerHTML = ` body:before {
 content: '${innerWidth} x ${innerHeight}'; 
 } ` } window.addEventListener('load', JSinCSS) window.addEventListener('resize', JSinCSS) window.addEventListener('input', JSinCSS) window.addEventListener('click', JSinCSS)
  34. What It's Doing • Find (or create and attach) a

    <style> tag to populate • Hold CSS as a template string to be interpolated • Add event listeners to trigger stylesheet reprocessing
  35. Average size of an EDVS plugin is ~100 SLOC

  36. EDVS Mixins

  37. EDVS Mixins • Can be used interchangeably with most EDVS

    plugins • Can search and modify the DOM • Return a string of CSS to the EDVS plugin • Borrow from Unix Philosophy (text-based, act as filter)
  38. Template for a
 basic EDVS Mixin

  39. function mixin(selector, rule) { var tag = document.querySelectorAll(selector) var style

    = '' var count = 0 for (var i=0; i<tag.length; i ++) { var attr = selector.replace(/\W+/g, '') tag[i].setAttribute(`data-mixin-${attr}`, count) style += `[data-mixin-${attr}="${count}"] { ${rule} }` count ++ } return style }
  40. What It's Doing • Find all tags in document matching

    a selector • Assign a unique attribute to matching tags • Generate CSS targeting these unique attributes • Return CSS as a string • Extend by adding JS logic to decide which tags to style
  41. Average size of an EDVS mixin is ~20 SLOC

  42. It Doesn't Take Much Code • To conceive of new

    ways of thinking about design • To define a comfortable syntax for describing styles • To implement your desired workflow and tooling • To support a wide range of web browsers