Graduate Program KB

Module 2 - Rendering Logic II

  • Positioned layout is a layout mode that allows elements to overlap
  • The position property can be set to relative, absolute, fixed or sticky. The default value is static

Relative Positioning

  • Constrains certain children
  • Enables additional HTML properties to be used: top, left, right, bottom
    • Can set the values of these directional properties to shift the element around
    • The values are relative to the element's natural position
    • Layout is not affected with position, whereas with margin, sibling elements get pulled along
    • Relative positioning can be applied to both block and inline elements

Absolute Positioning

  • Can position elements wherever desired, breaking the orderly fashion of laying elements out next to one another

  • Generally used for:

    • UI elements that need to float above the UI, such as tooltips or dropdowns
    • Decorative elements that need to be in certain positions, such as illustrations
    • Stacking multiple elements in one place, like a deck of cards
    <style>
        .box {
            position: absolute;
            top: 0px;
            right: 0px;
            width: 100px;
            height: 100px;
            border: solid;
        }
    </style>
    
    <div class="box"></div>
    <p>Hello World</p>
    
    • Typically, in a flow layout, the box would sit above the paragraph because that's their order in the DOM
    • However, with absolute positioning, the box is in the top-right corner and the paragraph has shifted up to fill the space where the box would normally be
  • Can use the properties top, left, right, bottom to position the element based off the constraints of the container, ignoring its natural position

    • Here, the box is positioned 20px below the top edge and 25% away from the left edge
    • Percentages are based off the total amount of space available in the container
    <style>
        .box {
            position: absolute;
            top: 20px;
            right: 25%;
            width: 100px;
            height: 100px;
            border: solid;
        }
    </style>
    
    <div class="box"></div>
    
  • If position: absolute is set but a directional property isn't applied, then the element sits in its default position from flow layout

    • In the following snippet, the box would stack on top of surrounding text. Even if position: absolute was removed, not much will change because it's not used properly
    <style>
        .box {
            position: absolute;
            border: solid;
        }
    </style>
    
    <p>
        This is a bunch of dummy text that
        makes no sense at all.
        <span class="box">Hello World</span>
        This is more dummy text that
        also makes no sense at all.
    </p>
    
  • Elements are pulled out of flow with absolute positioning. When the browser lays out the elements, it pretends absolutely-positioned elements don't exist

    • Here, the first paragraph is placed at the top-left, its natural position
    • But since the paragraph is absolutely-positioned, the browser thinks the space is still empty
    • This causes the other two paragraphs to be placed in the exact same spot
    <style>
        p {
            position: absolute;
        }
    </style>
    
    <p>This is a paragraph.</p>
    <p>Another paragraph, with different words.</p>
    <p>Finally, to complete the set, a third.</p>
    
  • Collapsing parents

    • This is an example when absolute positioning is applied, the parent collapses because it's technically empty
    • It's empty because the child <div> is a "hologram", elements that use absolute positioning don't "exist"
    • Here, the child is a square box within a rectangular parent box. When position: absolute is set, the rectangle collapses and has a height of 8px given by only the border
    <style>
        .child {
            position: absolute; 
            width: 200px;
            height: 200px;
            background: blue;
        }
    
        .parent {
            border: 4px solid;
        }
    </style>
    
    <div class="parent">
        <div class="child"></div>
    </div>
    
  • With absolute positioning, the property display pretty much has no effect on the layout of the element

    • The exception is relative positioning, which can behave similarly to another layout it could be using like grid or flex layout
    • The keyword none hides the element, and that behaviour is consistent in all algorithms
    • An absolutely positioned element determines its size by applying the concept of using as least space as possible
      • If an element like <p> hits the edge of the viewport, it forces a line-wrap
      • If a directional property is applied to add distance from the edges, it will still line-wrap but earlier
      • If you can't line-wrap (no spaces), the element will still have the same size, but its contents leave the container and possibly the viewport. This introduces a horizontal scrolling bar

Centering Trick

  • We can adjust the directional properties to centre an element

    • Here, the size of the box is dynamically calculated by taking a 100% of the width or height of the container, then subtracting its corresponding pixel distances
    .box {
        position: absolute;
        top: 50px;
        left: 50px;
        right: 50px;
        bottom: 50px;
        border: solid;
    }
    
    • But, what if the size of the box to be strict?
    • In the modified example, the box is no longer equidistant from all edges because it's difficult to obey all the declarations given
    • The box is now 100px tall and wide, and it is positioned 50px from the top-left (origin point) which has priority in this type of positioning dilemma
    .box {
        position: absolute;
        top: 50px;
        left: 50px;
        right: 50px;
        bottom: 50px;
        width: 100px;
        height: 100px;
        border: solid;
    }
    
    • If margin: auto is set, the box will be centered but it disobeys all the values of the directional properties given
  • The inset property will set all 4 edge properties (top, left, right and bottom) to the provided value

    • It's basically short-hand for specifying the four directional properties
    .box {
        top: 50px;
        left: 50px;
        right: 50px;
        bottom: 50px;
    }
    
    .box {
        inset: 50px;
    }
    

Containing Blocks

  • Every HTML element has a "containing block", which is a rectangle that forms the bounds of the container
  • In flow layout, elements are contained by their parents
  • In positional layout, absolute elements can only be contained by other elements using positional layout as well
    • If there are no other elements using positional layout, it will be positioned according to the initial containing block (box the size of the viewport)

Stacking Contexts

  • Elements that use positional layout can overlap each other

  • The element that's shown on top is dependent on the DOM order, the last element to be added is shown

    • Generally, positioned elements will always render on top of non-positioned ones, ignoring DOM order
  • To specify the layering order to be different from the DOM order, use the z-index property to manually reorder

    • The default value is auto, equivalent to 0
    • Works on positioned elements and can be used with flex/grid children
    • The z refers too the z axis, which is forward/backward
    • x is left/right, y is up/down
    <style>
        .box {
            position: relative;
        }
        
        .first.box {
            z-index: 2;
        }
        
        .second.box {
            z-index: 1;
        }
    </style>
    
    <div class="first box">
        A
    </div>
    <div class="second box">
        B
    </div>
    
    • z-index can be a negative integer,but can introduce complexity so it's not recommended
  • Other declarations for creating a new stacking context

    • Setting opacity to a value less than 1
    • Setting position to fixed or sticky
    • Apply a mix-blend-mode other than normal
    • Adding a z-index to a child inside a display: flex or display: grid container
    • Using transform, filter, clip-path or perspective
    • Explicitly creating a context with isolation: isolate
  • Debugging stacking contexts

    • Free Chrome/Firefox extension called CSS Stacking Context Inspector
    • Displays if an element creates its own stacking context
    • If an existing z-index property is having any effect
    • Where does an element sit relative to other elements in the same stacking context

Managing z-index

  • Can avoid increasing the value of z-index to high numbers by relying on DOM order instead

    • We can swap the DOM order to omit z-index in some situations
    • Here, we want a card displayed in front of the background image with blob decorations
    <style>
        .wrapper {
            position: relative;
        }
        .card {
            position: relative;
            z-index: 2;
        }
        .decoration {
            position: absolute;
            z-index: 1;
        }
    </style>
    
    <div class="wrapper">
        <div class="card">
            Hello World
        </div>
        <img
            alt=""
            src="/decorative-blob-1.svg"
            class="decoration"
            style="top: -20px; left: -70px;"
        />
    </div>
    
    • Consider if there were multiple elements we wanted to layer, the z-index would skyrocket
    • We can refactor the HTML above to render the card last, putting it at the top of the stacking context
    <style>
        .wrapper {
            position: relative;
        }
        .card {
            position: relative;
        }
        .decoration {
            position: absolute;
        }
    </style>
    
    <div class="wrapper">
        <img
            alt=""
            src="/decorative-blob-1.svg"
            class="decoration"
            style="top: -20px; left: -70px;"
        />
        <div class="card">
            Hello World
        </div>
    </div>
    
  • Consider if we wanted a middle element to be emphasised more than its adjacent two elements, the DOM order swap trick can't be applied

    • We can assign a higher z-index to the middle element, as long as it's in an isolated stacking context so it doesn't interfere with potentially the same z-index values from other elements
    • Here, primary card and <header> have the same z-index, and since the card comes later in the DOM, it overlaps the header
    • We can see pricing is a wrapper and we can give it the position: relative and z-index: 1 properties to create a stacking context for the cards
    • Now all the cards in the wrapper move as a group, and there won't be any individual overlapping of the card over the header
    <header>Hello World</header>
    <main>
        <section class="pricing">
            <article class="card" />
            <article class="primary card" />
            <article class="card" />
        </section>
    </main>
    
    • We can omit the two properties mentioned before and just use the isolation property instead
    .pricing {
        isolation: isolate;
    }
    

Fixed Positioning

  • Similar to absolute positioning, but it can only be contained by viewport, not containing blocks
  • Elements are immune to scrolling, it will still persist in the same position despite scrolling
  • There are certain CSS properties that mess with fixed positioning when applied to a parent element
    • When transform is used, the element stops being locked to viewport
    • The same thing occurs with filter and will-change
    • Diagnosing the problem can be frustrating in large applications, there may be 10s of ancestor elements
    • The following code can be run in the browser console to find any elements with interfering properties
    // Replace “.the-fixed-child” for a CSS selector
    // that matches the fixed-position element:
    const selector = '.the-fixed-child';
    
    function findCulprits(element) {
        if (!element) {
            throw new Error('Could not find element with that selector');
        }
    
        let parent = element.parentElement;
    
        while (parent) {
            const { transform, willChange, filter } = getComputedStyle(parent);
    
            if (transform !== 'none' || willChange === 'transform' || filter !== 'none') {
                console.warn('🚨 Found a culprit! 🚨\n', parent, { transform, willChange, filter });
            }
    
            parent = parent.parentElement;
        }
    }
    
    findCulprits(document.querySelector(selector));
    

Overflow

  • Overflow is a condition that happens when content doesn't fit into its parent's content box

  • Typically, block elements have dynamic height to contain their child elements

    • If you explicitly set a constraining height, you create an impossible condition to fulfill
    • In this case, the contents would spill out the bounding box, it overflows
    • However, layout is not affected by this mess
  • The property overflow is set to visible by default

    • scroll adds two scrollers to the containing box, removing the overflow of contents

    • overflow is shorthand for the two properties, overflow-x and overflow-y

      • We can be more specific about single directional scrolling by specifying the overflow direction
      .info {
          overflow-y: scroll;
      }
      
    • auto is another value recommended for most situations when you want a given element to be scrollable

    • It's smart in regards to only adding a scrollbar when it knows the content can't fit, such as resizing the window to be small

    • hidden truncates any content that extends beyond the boundaries of the container

      • Useful for truncating text with an ellipsis
      • Great for cosmetic effect, can hide overflow for decorative elements

Scroll Containers

  • Setting scroll, auto or hidden creates a scroll container
    • hidden is identical to scroll but with the scrollbars removed
  • When a container becomes a scroll container, it manages overflow in both x and y directions
  • A scroll container is like a portal to another dimension. When an element is contained inside, it's guaranteed to be stuck inside. It won't overflow beyond the boundaries of the container
  • overflow: clip works similar to hidden, but it doesn't create a scroll container
    • This is how hidden should have originally worked

Horizontal Overflow

  • Consider if we want to overflow horizontally rather than vertically
    • Images are inline by default, and they line-wrap when they can't fit
    • We can use the white-space property with nowrap on the container to disable line breaks
    <style>
        .wrapper {
            overflow: auto;
            border: 3px solid;
            white-space: nowrap;
        }
    
        .wrapper img {
            width: 32%;
        }
    </style>
    
    <div class="wrapper">
        <img alt="Description" src="URL" />
        <img alt="Description" src="URL" />
        <img alt="Description" src="URL" />
        <img alt="Description" src="URL" />
    </div>
    

Positioned Layout

  • If you want to contain an element that has absolute positioning by adding the overflow property, then the parent element must use a positional layout

    • Otherwise, the child element ignores the parent container and it will overflow
    • Here, the box has a larger height than the wrapper, causing it to peek outside. Without the position: relative on .wrapper, it would overflow
    <style>
        .wrapper {
            position: relative;
            overflow: hidden;
            width: 150px;
            height: 150px;
            border: solid black;
        }
    
        .box {
            position: absolute;
            top: 25px;
            left: 25px;
            background: blue;
            width: 150px;
            height: 200px;
        }
    </style>
    
    <div class="wrapper">
        <div class="box" />
    </div>
    
    • Using overflow: auto also works, adding scrollbars to view the whole box
    • Generally, absolute positioning is ignored by standard layout algorithms but overflow: auto treats it like every other element
  • If an element uses fixed positioning, overflow can't be triggered because it's contained by the initial containing block which exists outside the DOM

Sticky Positioning

  • position: sticky is a mix of relative and fixed positioning

    • As you scroll, if the element touches the edge of the viewport, it sticks and becomes fixed
    • One of the directional properties must also be declared to specify which edge to stick to. It's commonly done with top: 0
  • It was mentioned sticky elements follow the scroll as it becomes fixed to the edge. Despite this, they will never follow outside of its parent container

    • Therefore, if the container containing the sticky element goes out of view, then so will the sticky element
  • Directional properties behave differently in all the positional layouts

    • In sticky positioning, the value given specifies the minimum gap between the element and the edge of the viewport while the contaienr is in frame
    • Setting a value of 0 means the element will stick right against the edge
    • Negative values can add an aesthetic cushioning effect, basically there's a delay before an element starts sticking to the edge (even though visually it has)
    <style>
        .sticky {
            position: sticky;
            top: 25px;
        }
    </style>
    
  • Elements of other positional layouts don't block out layout space, but sticky elements are different

    • They're laid out in-flow, taking up real space even when it's stuck to an edge during scrolling
  • Troubleshooting

    • When a scroll container is created, sticky elements can stick to the closest scroll container

      • position: sticky can only stick in one context
    • Ensure the container is large enough so the sticky element can move

    • If there's a thin gap above a sticky header element, this might be a rounding issue with fractional pixels. The following snippet resolves this:

    header {
        position: sticky;
        top: -1px;
    }
    
    • Similar to before, the following is a code snippet that's runnable in the browser console to find ancestor elements creating scroll containers
    // Replace “.the-sticky-child” for a CSS selector
    // that matches the sticky-position element:
    const selector = '.the-sticky-child';
    
    function findCulprits(element) {
        if (!element) {
            throw new Error('Could not find element with that selector');
        }
    
        let parent = element.parentElement;
    
        while (parent) {
            const { overflow } = getComputedStyle(parent);
    
            if (['auto', 'scroll', 'hidden'].includes(overflow)) {
                console.log(overflow, parent);
            }
    
            parent = parent.parentElement;
        }
    }
    
    findCulprits(document.querySelector(selector));
    
    • If the culprit uses hidden, switch to clip instead
    • If it uses auto or scroll, you might be able to remove the property or lower it in the DOM. There's no quick solution

Hidden Content

  • display: none hides elements by effectively removing it from the DOM. Quite useful for media queries when toggling between mobile/desktop variants
  • visbility: hidden hides elements but they still exist, taking up space
  • opacity can take fractional values between 0 and 1 to adjust an element's transparency. You can still interact with elements, even if they are invisible