Graduate Program KB

CSS For Javascript


Module 2

Rendering Logic

This section will cover another layout mode named: positioned layout. The main feature of positioned layout is that items can overlap. This is contrasted with flow layout, which attempts to make sure that no more than one item occupies the same pixels at any one time. To opt into the positioned layout, we use the position property, and give it a value of either relative, absolute, fixed or sticky.

Relative Positioning

Of all positioned layout sub-genres, relative is the most stable. Often it can be applied to an element, and you will observe no difference. However it actually:

  • Constrains certain children
  • Enables additional CSS properties to be used

Opting into positioned layout, we gain new CSS properties including top, let, right and bottom. These directional properties can be use to move an element around. Relative positioning implies that these values are relative to its natural position. Top and left can be used to push along the vertical/horizontal axis respectively. The CSS below will push the box 40 pixels to the right, and 20 pixels up. Negative numbers can also be used for the opposite effect.

.box {
  position: relative;
  bottom: 20px;
  left: 40px;
}

The big difference between moving an object like this, rather than with margin is that the layout is not impacted. Siblings are not moved around like they would be with a changed margin. Similarly, if block items do not have a specific width, they can dynamically resize with margin-left (for example), but with left, it would push the box element without modifying its dimensions. Relative positioning is also advantageous in that it can be applied to block and inline elements too.

Absolute Positioning

If we wanted to take an element out of the orderly flow layout, and place it wherever we wanted, absolute positioning is the way to do it. It can typically be used for things like:

  • Floating UI elements (tooltips/dropdowns)
  • Decorative elements stuck in certain positions
  • Stacking multiple elements in a single place (deck of cards)

Typically block elements stack on top of each other, but say we had a box, followed by a paragraph, we could move the box wherever you wanted on the page using absolute positioning, and the paragraph would be shifted up. To do this, we use top, left, right and bottom.

.pink.box {
  position: absolute;
  top: 0px;
  right: 0px;
  width: 75px;
  height: 75px;
  background: deeppink;
}

This box would be placed in the upper right corner. Percentages can also be used, and are based on the total amount of space available. The order of elements in html is ignored with absolute positioning. When we set position: absolute, we pull the element out of flow. As the browsers lays out elements, these absolute elements are essentially non-existant.

Centring Trick

If absolute has conflicting values, it will prioritise that of top and left. We can set all 4 values, of top, left, right, and bottom 0px, then if we added a 'margin: auto' the element will be centred in the middle of the page regardless of its height and width. The size must be fixed however. Setting all 4 values to 0px seems tedious. Instead we can use the inset property to change all 4 values at once!

/* Excluding other CSS properties for example */
.box {
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
/* Equivalent to: */
.box {
  inset: 0;
}

Containing Blocks

In CSS, every HTML element hsa a 'containing block'. This is a rectangle which forms the bounds of an element's container. In flow layout, elements are contained by their parent. Or more precisely, contained with a 'containing block' which has the size and shape of the parents content box. With absolute positioning, containing blocks work a bit differently. If we placed an element using top:0 and left:0, it would be placed in the top left of the elements containing block, but how is this block calculated? Unlike in flow layout, absolute elements are necessarily contained by their parent. Absolute elements can only be contained by other elements using positioned layout. When deciding where to place an absolute element, the algorithm searches up the tree, searching for a positioned ancestor, the first one it finds will provide the containing block, and if none are found, the size of the viewport is used in the initial containing block.

Stacking Contexts

How does a browser determine which element is rendered on top, when 2 or more elements overlap. It is typically dependant on the layout mode. In flow layout, the DOM decides. If an element comes after another, it will be rendered on top. As a general rule, positioned elements will always render on top of non-positioned ones. If two elements are using relative positioning, once again the order in the DOM wins.

z-index

If we ant the layered order to be different from the DOM order, z-index can be used to manually, to select the order.

.box {
  position: relative;
}
.first.box {
  z-index: 2;
}
.second.box {
  z-index: 1;
}
<div class="first box">A</div>
<div class="second box">B</div>

Here, although the second box comes second on the DOM, the first box is rendered first due to the z-index. This will only work with positioned elements. The z in z-index refers to the z axis (typical 3D of xyz).

Stacking contexts are not global, so z-index of 9999 will not necessarily be 'on-top' of every other element. Stacking contexts are created when a position type (non-static) is given to an element, along with a z-index.

Managing the z-index

A common frustration in CSS is getting stuck fighting "z-index wars". This can happen as z-index values are assigned at increasingly higher rates than previous, to ensure that en element will be placed on top. It is like z-index inflation. There are some strategies to mitigate this.

Swapping DOM Order

Instead of always jumping to z-index, it is important to note that simply re-arranging elements in the DOM may be a simpler, and faster solution. Omitting a z-index can be as powerful as using it constantly.

Isolated Stacking Contexts

The example given in the course is that a we have 3 cards, and want one to be on-top of the other two. But when scrolling, the card (due to it's z-index), ends on top of the header. This is due to the header and cards all being within the same stacking context. Providing an isolated stacking context to just the cards will allow this problem to be fixed. To give an isolated stacking context, we can give a wrapper a position and z-index. Alternatively we can give our wrapper the isolation property, which also creates a stacking context.

/* Where pricing is our wrapper */
.pricing {
  isolation: isolate;
}

Fixed Positioning

Fixed positioning is close to absolute, however the only way it can be contained is by the viewport. Containing blocks are ignored. The main advantage is that fixed-position elements are immune to scrolling. For example, you may want a button in the bottom corner of a website, which persists in that one spot, and onClick will take you back to the top of the page. This can be done with fixed positioning.

If we removed the anchor points (top, right, bottom, left), and had a fixed-position element. It would sit in their flow position. It starts in the top-left of its parent element, but will remain stationary on the screen whilst you scroll.

Incompatibility with Properties

Some properties when applied to an ancestor will mess with fixed positioning. An example is transform. This will stop a fixed-position element from being locked to the viewport. filter and will-change can also affect fixed-position elements.

Overflow

Overflow is a condition that happens when content doesn't fit into its parent's context box. Typically, block elements have variable height. But constraining the height to a certain value can create impossible conditions. The browser compensates by letting the contents spill out the bounds of the block. If we add more content below the box with overflowing text, the text will actually overlap with each other, as overflowing content has no effect on layout. The browser provides us with the overflow property to handle this kind of situation.

Overflow: Accepted Values

Overflow defaults to visible, which can allow an element's content to extend beyond it's bounds.

Scroll: If we know that content will overlap, scroll can be used. It will allow a user to scroll the content within the defined box. This will display a scroll bar if used, so we would only want to use scroll when we are certain overflow will occur, otherwise we will have a scroll bar on a static piece of content (pointless).

.box {
  overflow: scroll;
  width: 150px;
  max-height: 100px;
  border: 3px solid;
}

Overflow is technically shorthand for overflow-x, overflow-y. If passed a single value, it will be used for both axis. If we only want scrolling in one direction we can be more precise.

.box {
  overflow-y: scroll;
}

Auto: Auto is smart. It will only add a scrollbar when one is actually required. We don't always know for certain if a scrollbar will be needed, so auto provides ideal behavior if we only think an element might overflow.

Hidden: Hidden is the final, and most extreme option for overflow. This option will truncate anything beyond the bounds of the container. It essentially removes anything that overflows, and only displays the content that is within the bounds described. We may want to use hidden for truncating text with an ellipsis (...). It could also be used for artistic purposes.

Scroll Containers

CSS contains certain 'hidden mechanisms'. These are concepts that exists within the language, but to most developers are invisible. Scroll container is one such mechanism. Within the course, scroll containers are compared to the TARDIS from Dr. Who (it's bigger on the inside). If we set overflow to either scroll, hidden or auto, we create a 'bigger on the inside' space.

The problem is that when a container becomes a scroll-container, overflow in both directions is managed. It becomes a portal to another dimension, and all children/descendants will be affected. When a child is placed in a scroll container, we can guarantee it cannot overflow. Scroll, auto and even hidden all create a scroll container (even if hidden doesn't show it). Overflow: hidden is identical to overflow: scroll, minus the scroll bars. We could access the hidden elements by tabbing through them, and the box will scroll.

Overflow Clip

.box {
  overflow: clip;
}

Clip works similar to hidden, but it does not create a scroll container. It completely trims off overflow in one or both directions. You can still access the hidden elements using tab (like before) but the container will no longer scroll down as you do when using clip. Because it is a relatively new feature, it is not supported on every browser (it is on most main browsers however).

Horizontal Overflow

Say we have a collection of images inline and we want the user to be able to scroll horizontally. Similar to words in a paragraph, they will line-wrap when they cannot all fit. Overflow by itself cannot solve this problem. Provided that wrapper wraps all our images we could do the following:

.wrapper {
  overflow: auto;
  border: 3px solid;
  white-space: nowrap; /* What solves our problem */
}

White-space is a property which lets us tweak how many words (and other inline elements) wrap. By setting to nowrap, we instruct the container to never break a line, and with auto overflow, we gain the desired horizontal scroll effect.

Overflow with Positioned Layout

Overflow and Containing Blocks: Every element is contained by a block. Usually it's the parent, but absolutely-positioned elements can ignore their parents (unless they too use a positioned layout). Absolutely-positioned elements act just like static-positioned elements when it comes to overflow. If the parent has overflow: auto;, then if the parent is the containing block, the child will be able to be scrolled into view.

Overflow with Fixed Positioning

Overflow will not occur with fixed elements, as fixed children are only ever contained by the "initial containing block" - a box which exists outside the DOM structure. This also implies that fixed-position elements are immune from being hidden with overflow: hidden;

Sticky Positioning

Sticky is another type of position in CSS. As you scroll a page, an element can 'stick' to the edge of the page (top, left, right, bottom). With position set to sticky, we give an edge the value of 0px. See below.

.stickyObject {
  position: sticky;
  top: 0px;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: deeppink;
}
/* This will create a pink ball which as you scroll will stick to the top of the page.  */

Sticky: Parent Container

The sticky element will never follow the scroll when outside of its parent container. They will only stick whilst their container is in view. This can be used to create nice effects like headers appearing and disappearing with the related content (if they exist in the same wrapper).

Offset

Before we mentioned that using sticky positioning, we want to set top (for example) to 0px, so that our element sticks to the top of the page. However, we can set it to another value other than 0px, and this offset will be where the element 'sticks' to rather than the direct edge of the container.

Not Incorporeal

Earlier we saw that absolute and fixed elements are "Incorporeal" (they are like ghosts/holograms that don't take up space). Any static/relative siblings would treat the fixed/absolute elements like they do not exist. Sticky are 'real' and take up space in flow. The space they take up still remains even when they are stuck to the edge during scrolling.

Sometimes we can run into issues with sticky not working as we expect. When we set overflow to hidden/scroll/auto, we create a scroll container. And sticky sticks to the closest scroll container. Sticky can only stick in one 'context'. This means that ancestors of sticky could have some overflow bugging sticky down the line. It is also important to ensure the sticky element exists within a large enough parent container.

Hidden Content

display: none

Element using this declaration are effectively removed from the DOM. They are both invisible and incorporeal (will not affect other elements). An element which is set to display: none, cannot be clicked or focused (even using tab). None can be useful when combined with media queries to toggle between different variants of the same element (mobile versus desktop for example).

.desktop-header {
  display: none;
}

@media (min-width: 1024px) {
  .desktop-header {
    display: block;
  }

  .mobile-header {
    display: none;
  }
}

By default our desktop header is hidden, but if our viewport is at least 1024px, our mobile-header is hidden, and our desktop-header shown instead.

Visibility: hidden

The visibility property allows us to hide an element, but unlike display: none, it still exists and takes up space. This typically isn't used as often as we don't want a large hole in our UI. Visibility can also be selectively undone by children. See below.

<style>
  section {
    visibility: hidden;
  }

  .button.two {
    visibility: visible;
  }
</style>

<section>
  <button class="button one">First Button</button>
  <button class="button two">Second Button</button>
  <button class="button three">Third Button</button>
</section>

In this example, only the 'Second Button' would be shown on the page.

Opacity

Unlike the other options, opacity is not binary. We can flip it from 0 to 1 to fully hide an element, or we can give it a factional value to make it only semi-transparent. The closer to 0, the more transparent it becomes, and closer to 1, the more visible. Elements hidden with opacity are not removed from flow. Opacity is not useful when something needs to be fully hidden (use something else), it is only good when we need to make something semi-visible or want fading-in/out transitions.