Defining auto An Adventure in Layout

Elika J. Etemad (fantasai) W3C CSS Working Group

Specs I've Worked On Since 2004

How a Layout Engine Works: Overview

Style Computation
Each element in the DOM is assigned a value for every CSS property that exists.
Box Construction
Each displayed element and pseudo-element is represented by a box.
Layout
Each box's size and position is established.
Painting
Each visible element is drawn on the screen.

How a Layout Engine Works: Style Computation

Style computation is per element, depth-first traversal of all dirty elements.

Selector Matching
Compile list of all style rules that match.
Cascading
Pick the property declaration that wins.
Computation
Compute the values (e.g. absolutize lengths).
Inheritance
Default missing values to inherited or initial value.

End result: exactly one value per property per element.

How a Layout Engine Works: Box Construction (nsCSSFrameConstructor)

{1} DOM Element → {0,1,2} CSS Boxes

# Boxes display
0 none, contents
1 all other values
2 table, list-item

End result: zero or more boxes per element.

Aside How a Layout Engine Works: Fragmentation

{1} CSS Box → 1+ Box Fragments

Fragmentation = line breaking, page breaking, column breaking, region breaking, etc.

We're not going to talk about fragmentation.

How a Layout Engine Works: The CSS Box Object (nsIFrame)

  • Make a tree!
    • mParent
    • mNextSibling
    • mPrevSibling
    • mFrames // child list
  • DOM/Style Data
    • mContent
    • mStyleContext
  • Size and Position
    • mRect.x
    • mRect.y
    • mRect.width
    • mRect.height
  • The goal of layout is to fill in the correct values for x, y, width, and height for all boxes in the tree.

    Origin coords are relative to parent origin.

    How a Layout Engine Works: nsBlockFrame.cpp

    ~10K lines of code, i.e. we're simplifying this next slide a lot.

    How a Layout Engine Works: Reflow

      VeryBasicLayoutBox::Reflow() {
        CalculateMyWidth();
        positionOfNextChild = 0;
        for each child in childList {
          “Yo, I'm this wide. What size do you wanna be?” — me
          “I wanna be this big.” — child
          “Okay, I'm gonna put you here.” — me
          positionOfNextChild += child.size;
        }
        CalculateMyHeight();
      }

    How a Layout Engine Works: Reflow

      VeryBasicLayoutBox::Reflow() {
        CalculateMyWidth();
        positionOfNextChild = 0;
        for each child in childList {
          “Yo, I'm this wide. What size do you wanna be?” — me
          “I wanna be this big.” — child
          “Okay, I'm gonna put you here.” — me
          positionOfNextChild += child.size;
        }
        CalculateMyHeight();
      }

    How a Layout Engine Works: Reflow

      VeryBasicLayoutBox::Reflow() {
        CalculateMyWidth();
        positionOfNextChild = 0;
        for each child in childList {
          “Yo, I'm this wide. What size do you wanna be?” — me
          “I wanna be this big.” — child
          “Okay, I'm gonna put you here.” — me
          positionOfNextChild += child.size;
        }
        CalculateMyHeight();
      }

    How a Layout Engine Works: Reflow

      VeryBasicLayoutBox::Reflow() {
        CalculateMyWidth();
        positionOfNextChild = 0;
        for each child in childList {
          “Yo, I'm this wide. What size do you wanna be?” — me
          “I wanna be this big.” — child
          “Okay, I'm gonna put you here.” — me
          positionOfNextChild += child.size;
        }
        CalculateMyHeight();
      }

    How a Layout Engine Works: Reflow

      VeryBasicLayoutBox::Reflow() {
        CalculateMyWidth();
        positionOfNextChild = 0;
        for each child in childList {
          “Yo, I'm this wide. What size do you wanna be?” — me
          “I wanna be this big.” — child
          “Okay, I'm gonna put you here.” — me
          positionOfNextChild += child.size;
        }
        CalculateMyHeight();
      }

    How a Layout Engine Works: Reflow

      VeryBasicLayoutBox::Reflow() {
        CalculateMyWidth();
        positionOfNextChild = 0
        for each child in childList {
          “Yo, I'm this wide. What size do you wanna be?” — me
          “I wanna be this big.” — child
          “Okay, I'm gonna put you here.” — me
          positionOfNextChild += child.size;
        }
        CalculateMyHeight();
      }

    How a Layout Engine Works: Width Calculation

      VeryBasicLayoutBox::CalculateMyWidth() {
        if mStyleContext.width == auto {
          mRect.width = mParent.width - border - padding - margin;
        }
        else { // fixed size
          mRect.width = mStyleContext.width;
        }
      }

    How a Layout Engine Works: Height Calculation

      VeryBasicLayoutBox::CalculateMyHeight() {
        if mStyleContext.height == auto {
          mRect.height = positionOfNextChild;
        }
        else { // fixed size
          mRect.height = mStyleContext.height;
        }
      }

    How a Layout Engine Works: 1D Flows

    How a Layout Engine Works: CSS1 Block Auto-sizing

    width fills containing block
    = mParent.width - border - padding - margin
    height exactly fits laid-out content
    = positionOfNextChild // after last child is positioned

    Works great... until we start to put things side-by-side.

    CSS Layout Principles of Design

    What is ‘auto’?

    “CSS [spec] editing: expect 80% of the time to be spent defining what 'auto' does and does not do and you'll do fine.” Sylvain Galineau

    Defining auto: Sizing Primitives

    Fixed Sizes
    Size is independent of layout or contents.
    Extrinsic Sizes
    Size depends on containing block size.
    Intrinsic Sizes
    Size depends on size of contents.

    Sizing Primitives: Fixed Sizes

    Fixed Sizes
    Size is independent of layout or contents.
    • Absolute Units: px, pt, pc, in, cm, mm
    • Relative Units: em, ch, rem, vw, vh, vmin, vmax

    Sizing Primitves: Extrinsic Sizing

    Extrinsic Sizes
    Size depends on containing block size.
    • fill-available sizing
    • percentage (%) sizing

    containingBlock.width×percentage − (borders + padding + margins)

    Sizing Primitives: Intrinsic Sizing

    Intrinsic Sizes
    Size depends on size of contents.
  • min-content
    Goal: smallest size that avoids overflow
    ≈ largest image, fixed-size box, or (unbreakable) word
  • max-content
    Goal: largest size that avoids wasting space
    ≈ largest image, fixed-size box, or (unwrapped) line of text
  • Defining auto: Compound Sizing

  • fit-content aka shrink-to-fit
    Fit around the content, but avoid overflow.
  • flex
    Put stuff next to each other, distribute excess space.
  • Compound Sizing: Shrink-to-fit

    fit-content aka shrink-to-fit
    Fit around the content, but avoid overflow.

    max( min(max-content, /* Be as wide as the content but no wider */ fill-available), /* But avoid overflowing the containing block */ min-content) /* And definitely don't let any content overflow */ )

    Compound Sizing: Flexing

    flex
    Put stuff next to each other, distribute excess space.

    basis + flex ratio × (fill-available − ∑basis)

    Defining auto: Sizing Constraints

  • min/max-width/height
    Size within this range.
  • aspect ratio
    Don't skew my image.
  • Defining auto: What is auto?

    Defining auto: Why is it auto?

    Defining auto: Flexbox

    CSS Best Practices

    With great power comes great responsibility.

    CSS Best Practices: Linearized Logical Source Order

    Reorder visuals by reordering the source
    not by using order or positioning
    unless there's a clear accessibility justification for keeping them out of sync.

    CSS Best Practices: Liquid Layouts and Relativity

    What is your sizing based on?

    Absolute units are usually the wrong answer.

    WARNING: Requires Critical Thinking

    CSS Best Practices: Media Queries

    Set media query breakpoints in em or ch, not always in px.

    CSS Best Practices: Media Queries

    CSS Best Practices: Defensive Use of Shorthands

    Use shorthands. Protect yourself against invading rules!

    .lowSpecificity { background: linear-gradient(red, maroon); }
    /* … more stuff … */
    #highSpecificity { background-color: green; }

    Unless you're intentionally wanting a longhand to cascade in from somewhere else, use the shorthand to give you a blank slate.

    background: url(…) top left, url(…) bottom right, url(…) center;
    background-repeat: no-repeat

    CSS Best Practices: Summary

    Logical Source Order
    Keep DOM order readable: for accessibility, mobile optimization, device adaptability, and long-term maintainability.
    Liquid Layouts and Relativity
    Use smart relative sizing: to optimize layouts while minimizing media query code forks.
    Media Queries
    Adapt to screen size changes; get font size adaptation free by using ems.
    Shorthands
    Use shorthands to create a “blank slate”. Use longhands for only for targetted tweaking.
    Never use direction or unicode-bidi.
    Use the HTML dir attribute, because it's content, not style (and you can then use :dir() selectors).