Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Edge of CSS

Tommy Hodgins
April 20, 2017

The Edge of CSS

Have you ever felt limited by CSS and found yourself needing to apply styles to an element CSS can't quite select, or based on a breakpoint CSS isn't quite aware of? Follow along as Tommy shares a CSS extension that adds a few new abilities to how you can write CSS, and learn how you can begin using concepts like style scoping, element and container queries, and much more in your own projects right away!

Tommy Hodgins

April 20, 2017
Tweet

More Decks by Tommy Hodgins

Other Decks in Design

Transcript

  1. The Edge of CSS
    by Tommy Hodgins

    View Slide

  2. Have you ever found
    yourself at the edge of what
    CSS can do?

    View Slide

  3. Signs you’re on the edge
    • using polyfills for unsupported features

    View Slide

  4. Signs you’re on the edge
    • using polyfills for unsupported features
    • generating unique IDs or classes for elements

    View Slide

  5. Signs you’re on the edge
    • using polyfills for unsupported features
    • generating unique IDs or classes for elements
    • duplicating code only to change breakpoints

    View Slide

  6. Signs you’re on the edge
    • using polyfills for unsupported features
    • generating unique IDs or classes for elements
    • duplicating code only to change breakpoints
    • so many more (see Sisyphus)

    View Slide

  7. In situations where your only
    option is writing custom JavaScript
    to apply a style to an element…

    View Slide

  8. Think how CSS could be
    extended to reach that
    situation instead

    View Slide

  9. What if you could extend CSS
    in the browser to add new
    features and functionality?

    View Slide

  10. What if instead of a CSS
    preprocessor you had a
    CSS reprocessor?

    View Slide

  11. What is a CSS
    reprocessor?

    View Slide

  12. A CSS reprocessor is a tool to
    recalculate your styles as often as
    needed after the page has loaded,
    and after user interaction

    View Slide

  13. Thinking beyond the
    edge of CSS

    View Slide

  14. My journey beyond the
    edge began in 2014 with
    the EQCSS project

    View Slide

  15. The goal of EQCSS was to add
    the features I wanted to use
    to CSS so I could use them

    View Slide

  16. The hard question:

    View Slide

  17. What’s the smallest set of ideas
    that could be added to CSS to lead
    to the functionality I wanted?

    View Slide

  18. Our 4 What-ifs

    View Slide

  19. What if CSS Could
    • Set a scope for a selector like a @media query
    but for individual elements

    View Slide

  20. What if CSS Could
    • Set a scope for a selector like a @media query
    but for individual elements
    • Add a block of CSS styles to the page when that
    selector is true

    View Slide

  21. What if CSS Could
    • Set a scope for a selector like a @media query
    but for individual elements
    • Add a block of CSS styles to the page when that
    selector is true
    • Add responsive conditions to that selector

    View Slide

  22. What if CSS Could
    • Set a scope for a selector like a @media query but
    for individual elements
    • Add a block of CSS styles to the page when that
    selector is true
    • Add responsive conditions to that selector
    • Evaluate JavaScript from the context of the element
    you are styling and use those values in the CSS
    you're applying to the page

    View Slide

  23. With those four ideas added on top
    of what CSS already does many
    new responsive techniques become
    very simple to express

    View Slide

  24. The amount of JavaScript required to
    add support for these four ideas is
    about 3000 lines of code, or about
    3kb when minified and gzipped

    View Slide

  25. But there’s no end to the
    amount of new responsive
    techniques possible…

    View Slide

  26. New techniques possible
    • scoped styles
    • element queries & container queries
    • breakpoints based on element properties
    • variables scoped to individual elements
    • self-responsive HTML components
    • parent selector, previous element selector, etc

    View Slide

  27. • element-based units
    • scroll-based responsive styles
    • smarter attribute selectors (number comparison)
    • make any element scalable
    • work easily with aspect ratios
    • polyfill and emulate unsupported CSS features
    • many more… (search ‘EQCSS’ on CodePen for 100+)

    View Slide

  28. EQCSS has been used in
    production for 2 years

    View Slide

  29. EQCSS allows us to
    • Build layout-independent design components

    View Slide

  30. EQCSS allows us to
    • Build layout-independent design components
    • Scope, isolate, and transplant existing layout
    parts from one project to another

    View Slide

  31. EQCSS allows us to
    • Build layout-independent design components
    • Scope, isolate, and transplant existing layout
    parts from one project to another
    • Support modern responsive layouts in older
    browsers

    View Slide

  32. EQCSS allows us to
    • Build layout-independent design components
    • Scope, isolate, and transplant existing layout
    parts from one project to another
    • Support modern responsive layouts in older
    browsers
    • Keep our CSS short and our workflow simple

    View Slide

  33. Moving beyond
    The edge of CSS

    View Slide

  34. How EQCSS works
    • If any @element queries are found, the
    selector(s), condition(s), and styles are
    extracted

    View Slide

  35. How EQCSS works
    • EQCSS recalculates on: DOM ready, scroll, resize,
    input, click, mousedown, mousemove, mouseup

    View Slide

  36. How EQCSS works
    • Any time there is at least 1 element in the DOM
    matching a selector in the selector list of an
    @element query
    • And all conditions are true (if any included)
    • Add the block of styles to the page

    View Slide

  37. How EQCSS works
    • And while we’re applying CSS to the page:
    provide a way to evaluate JavaScript from the
    context of each element we’re styling

    View Slide

  38. With @media queries the most common
    responsive features are min/max width
    and height, but with @element queries,
    many new responsive features make sense

    View Slide

  39. Responsive conditions
    • width
    • height
    • characters (number of characters of text content)
    • lines (number of lines of text)
    • children (number child elements)
    • scroll-x (horizontal scroll position)
    • scroll-y (vertical scroll position)
    • aspect-ratio
    • orientation

    View Slide

  40. Inside the scope of the
    @element query, new
    selectors are possible

    View Slide

  41. Meta-Selectors
    • $this is the scoped element
    • $parent is the parent of the scoped element
    • $prev is the element before the scoped element
    • $next is the element after the scoped element

    View Slide

  42. Evaluating the properties of the
    elements as we style them adds
    the possibility for new units

    View Slide

  43. Similar to how viewport units (VW, VH, VMIN
    and VMAX) refer to viewport-based
    percentages, what if element-based units
    referred to dimensions of individual elements

    View Slide

  44. Element-based units
    • EW is 1% element width
    • EH is 1% element height
    • EMIN is 1% element minimum length
    • EMAX is 1% element maximum length

    View Slide

  45. Eval
    • eval('') uses the output of JavaScript
    evaluated from the context of each element the
    query applies to anywhere inside the styles
    being added to the page

    View Slide

  46. Some CSS+
    Techniques

    View Slide

  47. Imagine if CSS had more
    dynamic values like
    currentColor

    View Slide

  48. What if you could use values
    like currentWidth or
    parentChildren in your CSS?

    View Slide

  49. Many values like this, while out of reach
    of CSS, are simple to measure with
    JavaScript if you could evaluate from the
    context of each element you are styling

    View Slide

  50. Imagine if an iframe had a way
    to scale height responsively
    based on its own aspect ratio

    View Slide

  51. iframe {
    width: 100%;
    height: calc(currentWidth / (16/9));
    }

    View Slide

  52. currentWidth is
    kind of like 100ew

    View Slide

  53. @element iframe {
    $this {
    width: 100%;
    height: calc(100ew / (16/9));
    }
    }

    View Slide

  54. What if CSS could access each
    iframe’s own width and height
    attributes, something kind of like
    attr(width) or attr(height)

    View Slide

  55. @element iframe {
    $this {
    width: 100%;
    height: calc(100ew / (attr(width)/attr(height)));
    }
    }

    View Slide

  56. We can access these values for $this
    element by using eval('width')
    and eval('height')

    View Slide

  57. @element iframe {
    $this {
    width: 100%;
    height: calc(100ew / (eval('width')/eval('height')));
    }
    }

    View Slide

  58. Here eval('width') is giving us the
    same result in our CSS as el.width or
    el.getAttribute('width') would
    return in JavaScript

    View Slide

  59. No matter what the current width, we can
    always scale the height of each iframe
    based on its own aspect ratio, determined
    by its own width and height attributes

    View Slide

  60. What about a parent
    selector, or :has()?

    View Slide

  61. Have you ever wanted to apply
    a style to an element that
    contains another element?

    View Slide

  62. Suppose we want to make an
    h2 red, but only if it contains
    a strong element inside

    View Slide

  63. This is possible with CSS using
    h2:has(strong), but unfortunately
    no web browsers support :has()

    View Slide

  64. h2:has(strong) {
    color: red;
    }

    View Slide

  65. This is equivalent to styling $this
    inside an element query for
    h2:has(strong) if it had support…

    View Slide

  66. @element h2:has(strong) {
    $this {
    color: red;
    }
    }

    View Slide

  67. Since we can’t use :has(), could we
    use querySelector() inside
    eval('') to check for strong tags?

    View Slide

  68. @element h2 {
    eval('querySelector("strong") && "$this"') {
    color: red;
    }
    }

    View Slide

  69. Inside our element query for h2 tags, if
    querySelector('strong') returns
    true, the rule applies to $this

    View Slide

  70. …Or maybe you could apply a style
    to the parent of every strong tag
    if the $parent is also an h2

    View Slide

  71. @element strong {
    h2$parent {
    color: red;
    }
    }

    View Slide

  72. Or another way to do the same
    thing would be to apply a style to
    the $parent of any h2 strong

    View Slide

  73. @element h2 strong {
    $parent {
    color: red;
    }
    }

    View Slide

  74. Comparing attribute
    values as numbers

    View Slide

  75. CSS has an attribute selector
    that can select elements based
    on comparing values as strings

    View Slide

  76. But what if you could compare
    attributes as numbers, like
    [value<0] or [value >=50]

    View Slide

  77. What if you could use a selector like
    [type=number][value<0] to
    select inputs with values less than 0

    View Slide

  78. [type=number][value < 0] {
    border: 1px solid red;
    }

    View Slide

  79. We can use [value=0] and match the
    number 0 as a string, but not compare
    as a number with >, <, >=, or <=

    View Slide

  80. @element [type=number] {
    eval('(value < 0) && "$this"') {
    border: 1px solid red;
    }
    }

    View Slide

  81. Here we add a red border to
    every [type=number]
    element with a value < 0

    View Slide

  82. Can we create automatic
    hyphenation based on an
    element's own width?

    View Slide

  83. Imagine you want to set hyphens for
    heading elements when they are less
    than 35em wide, you may use a
    @media query like this

    View Slide

  84. @media (max-width: 35em) {
    h1, h2, h3, h4, h5, h6 {
    hyphens: auto;
    }
    }

    View Slide

  85. We can use an @element query to
    make the breakpoint relative to the
    width of each heading tag rather than
    the width of the browser’s viewport

    View Slide

  86. @element h1, h2, h3, h4, h5, h6 and (max-width: 35em) {
    $this {
    hyphens: auto;
    }
    }

    View Slide

  87. Now our heading tags will have
    hyphenation when any of them are
    less than 35em wide, regardless of
    the width of the browser

    View Slide

  88. Think of how you might
    accomplish some of the
    the following ideas…

    View Slide

  89. • change the background-position of a
    background image based on an element’s
    aspect-ratio

    View Slide

  90. • change the background-position of a
    background image based on an element’s
    aspect-ratio
    • style an input that's empty (input:empty
    doesn't work in CSS)

    View Slide

  91. • change the background-position of a
    background image based on an element’s
    aspect-ratio
    • style an input that's empty (input:empty
    doesn't work in CSS)
    • create scalable/responsive CSS border art

    View Slide

  92. • change the background-position of a
    background image based on an element’s
    aspect-ratio
    • style an input that's empty

    (input:empty doesn't work in CSS)
    • create scalable/responsive CSS border art
    • style a textarea or input so it expands to fit as
    many characters or lines of text as are inside

    View Slide

  93. To get started, add the
    following line of code to
    your project:

    View Slide


  94. View Slide

  95. Reprocessing CSS

    View Slide

  96. If you are going to try a
    CSS reprocessor, here are
    things to keep in mind

    View Slide

  97. Which events should trigger
    recalculations?
    • things like: load, resize, input and keyboard,
    clicks and touch events, scroll on elements, etc.
    • can you only listen to events that make sense to
    the styles you are recalculating?
    • can you use resize observers for width/height?
    • do mutation observers help?

    View Slide

  98. Is your reprocessor integrated inside
    a specific tech stack or tool, or does
    it exist as a plugin that runs in the
    browser like a shim or polyfill?

    View Slide

  99. What’s your exit strategy or
    migration path for porting code
    away from the reprocessor if you
    need to at some point in the future?

    View Slide

  100. THANKS!
    Visit elementqueries.com for
    demos, docs, & links

    View Slide