Module 1 - Rendering Logic I
Built-in Declarations and Inheritance
-
Each browser includes their own user-agent stylesheet for specifying the default styling of HTML tags
-
Certain CSS properties are inheritable, meaning a property specified on a parent element can be passed to the children, even if the property is not specified on the child
- Similar to JavaScript's prototypal chain, where it searches the current element for a property, otherwise it moves up the chain, checking its parents for the property
- color is inheritable, when applied to an element, that value gets passed down to the child and grandchild elements
- border is not inheritable, it must be explicitly added to all elements instead
- List of inheritable properties:
- border-collapse, border-spacing, caption-side, color, cursor, direction, empty-cells, font-family, font-size, font-style, font-variant, font-weight, font-size-adjust, font-stretch, font, letter-spacing, line-height, list-style-image, list-style-position, list-style-type, list-style, orphans, quotes, tab-size, text-align, text-align-last, text-decoration-color, text-indent, text-justify, text-shadow, text-transform, visibility, white-space, widows, word-break, word-spacing, word-wrap
-
Inheritance can be forced by explicitly adding the styling in the anchor tags
- color is overwritten by the default style color: -webkit-link despite being inheritable
<p style="color: red;"> Text </p>
Cascade
- If there are conflicting styles between selectors of a class and tag, the class has precedence. But, IDs are more specific than classes
- To implement a cascade in JavaScript, use the spread syntax
- The order to merge the styles is determined by specificity, where the styles merged later overwrite the conflicting styles and non-conflicting styles are kept
const appliedStyles = { ...tagStyles, ...classStyles, ...idStyles }
Block and Inline Directions
-
Words are combined into blocks, such as headings and paragraphs. A page is constructed out of blocks, placed on top of one another
- Block direction is vertical
- Inline direction is horizontal
-
Logical properties provide alternative styles depending on internalisation. Consider languages that read in different directions
- Here's the built-in styles for <p> tags in Chrome
p { display: block; margin-block-start: 1em; /* Top, magin-top */ margin-block-end: 1em; /* Bottom, margin--bottom */ margin-inline-start: 0px; /* Left, margin-left */ margin-inline-end: 0px; /* Right, margin-right */ }
Box Model
-
The four aspects that make up the box model are content, padding, border and margin
-
The browser debugging tool can be used to select elements and view properties of its box model (or Ctrl + Shift + C)
- Scrolling to the bottom, you can view the box sizing
|----------------------------------------------------------| | margin - | | |--------------------------------------------| | | | border - | | | | |------------------------------| | | | | | padding - | | | | | | ------------------- | | | | | | | 512 x 512 | | | | | | | ------------------- | | | | | | | | | | | |------------------------------| | | | | | | | |--------------------------------------------| | | | |----------------------------------------------------------|
-
Consider the following CSS:
<style> section { width: 500px; } .box { width: 100%; padding: 20px; border: 4px solid; } </style>
- The size of the rectangle drawn is 548px wide, not 500px
- Setting .box to have width: 100% means that the box's content size should be equal to the available space, 500px
- Padding and border are added to each side, hence, 500px + (20px * 2) + (4px * 2) = 548px
-
box-sizing is a CSS property that allows us to change rules for size calculations
- content-box takes inner connect content into account only but offers an alternative value, border-box
- box-sizing: border-box allows sizing to behave more intuitively
<style> section { width: 500px; } .box { width: 100%; padding: 20px; border: 4px solid; box-sizing: border-box; /* Now the rectangle is 500px, the content-size is shrunk to accommodate the padding and border */ } </style>
- To set this to default for all elements, add the following CSS snippet
<style> *, *::before, *::after { box-sizing: border-box; } </style>
Padding
-
Padding is the inner-space surrounding the content
- If three values are provided to padding, it sets the top, right, bottom and mirrors the right value to the left
.even-padding { padding: 20px; /* all sides */ } .two-way-padding { padding: 15px 30px; /* top/bottom left/right */ } .asymmetric-padding { padding: 10px 20px 30px 40px; /* top right bottom left (picture a clock) */ } /* Logical properties */ .asymmetric-logical-padding { padding-block-start: 20px; /* top */ padding-block-end: 40px; /* bottom */ padding-inline-start: 60px; /* left */ padding-inline-end: 80px; /* right */ }
- Long-form properties overwrite the relevant value in shorthand properties
- The order matters, whichever declaration is specified last will overwrite the first declaration
.box { padding: 48px; padding-bottom: 0; }
-
Most commonly used units for padding are px, em and rem
- Percentage units are not-intuitive, they are based on the element's available width
Border
-
Border is the space surrounding the padding, unlike the other properties of the box model, it has a cosmmetic component
- border can specify the width, style and colour. Style is the only required field
.box { border: 3px solid blue; } .long-form-box { border-width: 3px; border-style: solid; border-color: blue; }
-
If border colour is not specified, it will use the element text's colour
- To specify this behaviour explicity, use the currentColor keyword
.box { color: green; border: 3px solid currentColor; box-shadow: 2px 2px 2px currentColor; }
-
The border-radius property rounds an element, even without a border
- It accepts fields for specific corners, similar to padding
.box { border-radius: 10px 20px 30px 40px; /* top-left, top-right, bottom-right, bottom-left (picture a clock) */ } .long-form-box { border-top-left-radius: 10px; border-top-right-radius: 20px; border-bottom-right-radius: 30px; border-bottom-left-radius: 40px; } .percentage-box { border-radius: 50%; /* Form circles or ovals, each corner is a percentage of the total width/height */ }
-
The difference between border and outline is outline doesn't affect layout, it's more like a box-shadow (effect draped over an element without affecting size)
- Outlines share the same properties of borders, but with the outline prefix instead for the property names
- Adding the outline and border properties simultaneously will stack the outline on top of the border, essentially forming a two-layer border
- outline-offset will add a gap between the element and its outline
Margin
-
Margin increases space around an element, it's the outer layer in the box model
.box { margin: 20px; /* all sides */ } .asymmetric-box { margin: 20px 10px; /* top/bottom left/right */ } .long-form-box { margin-top: 10px; margin-left: 20px; margin-right: 30px; margin-bottom: 40px; } /* Logical properties */ .asymmetric-logical-box { margin-block-start: 20px; /* top */ margin-block-end: 40px; /* bottom */ margin-inline-start: 60px; /* left */ margin-inline-end: 80px; /* right */ }
-
Only with margin, negative values can be specified
- Can be used to pull the current element outside its parent element
- Setting a negative margin for a direction will pull the element in the opposite direction specified
- Don't think of margin as a way to change positions, rather, it's used to change the gap between elements
- Negative margins can affect the position of all siblings
-
Auto margins can be used to centre child elements in a container
- The auto keyword will try to fill the maximum available space on a side
- However, it doesn't apply to vertical margins (margin-top and margin-bottom). auto would be equivalent to 0px here
- A width property must also be explicited provided, since block elements naturally grow to fill the available horizontal space
- The following CSS snippet applies an equal amount of margin to the left and right side of an element
.content { width: 50%; margin-left: auto; margin-right: auto; }
-
Special replaced elements such as <img>, <video> and <canvas> tags don't automatically expand to fill available space
- They rely on an intrinsic size, so if you want to stretch them out, wrap them in another container
- width: auto means to use natural width for these elements, not "stretch out to fill the space"
.stretch { margin-left: -32px; margin-right: -32px; } img { display: block; width: 100%; }
Flow Layout
-
HTML elements will have its layout calculated by a layout algorithm, called layout modes
-
Flow layout is the default layout mode
- Block elements: headings, paragraphs, footers, asides, the content that makes up the page
- Inline elements: links, bold text, some form of text styling usually
- block stacks elements in the block direction, inline stacks elements in the inline direction
-
Block elements will greedily expand to fill all horizontal space
- Use fit-content keyword to shrink a block down to the minimum required size for text
h2 { width: fit-content; }
-
Inline elements can have extra pixels in size that aren't specified
-
Consider an image with a height and width of 300px wrapped in a container <div>
-
The parent container could have a slightly larger height of 306px for example, because the browser treats inline elements like typography
-
This extra bit of space is usually to create gaps between lines in a paragraph so they aren't crammed tightly, but it's applied here for the image
-
To resolve this issue, there are two ways:
- Set images to display: block
- Set the line-height property on the container <div> to 0
-
Now consider multiple images stacked in the inline direction, there would be spacing between them as a result of whitespace
-
The solution is to refactor the HTML to remove newlines or whitespace characters between images
-
HTML is space-sensitive, the browser can't tell if whitespace has been added to separate words in a paragraph or to indent the HTML (this issue is specific to flow layout only)
<img src="URL" width="100" alt="Description" /> <img src="URL" width="100" alt="Description" /> <img src="URL" width="100" alt="Description" />
<img src="URL" width="100" alt="Description"/> <img src="URL" width="100" alt="Description"/> <img src="URL" width="100" alt="Description"/>
- They can also line-wrap, which explains why a vertical-margin property is useless on inline elements
-
-
An inline-block element is a block-level element that can be placed in an inline context
- In terms of layout, it's treated as an inline element
- Under the hood, it's treated more like a block element
- Properties like width or margin-top have effect now whereas for inline elements, there was no effect
- A downside is line-wrapping is disabled
Width Algorithms
-
Block elements have dynamic sizing, with a default width of auto, not 100%
- width: auto is similar to margin: auto, taking as much available space as possible
- The following <h1> will take up (100% - 16px - 16px) pixels
h1 { width: 100%; margin: 0 16px; }
-
Measurements
- Percentage: Size is relative to the parent element's available space
- Explicit: Fixed size (px, em, rem)
- Keywords: Specify different behaviours depending on the context
-
min-content property specifies the element to become as narrow as possible based on child elements
h1 { width: min-content; /* Shrunk to just barely fit the text */ }
- max-content is similar in principle, but it will shrink the element to the smallest width possible to fit text without any line-breaks
- fit-content factors in the parent container, adding line-breaks if necessary but will always consider selecting the smallest possible width
-
Constrain element sizes using min-width and max-width properties
-
The following is generic utility class to constrain an element to sit in the middle of a column
- Fills available space on smaller viewports
- Sets maximum width and horizontally centre itself within the parent if there is remaining space
- Horizontal padding so the children elements aren't compressed into the edges
<style> .max-width-wrapper { max-width: 350px; margin-left: auto; margin-right: auto; padding-left: 16px; padding-right: 16px; } </style> <div class="max-width-wrapper"> <div class="card"> <p> ... </p> </div> </div>
-
<figure> tag is quite useful for displaying content like images, videos, code, etc. but allows a caption to be added
- <figcaption> allows that content to be captioned
<figure> <img src="URL" alt="Description" /> <figcaption> Source: ... </figcaption> </figure>
Height Algorithms
-
Compared to the default width behaviour, which tries to maximise the filled space, the default height behaviour tries to be as small as possible
- It's closer to width: min-content than width: auto
- Height is more dynamic than width, as more content fills a container, it's expected to grow taller on phone screens and shorter on monitors
-
Consider a page that doesn't have much content but we want it to take up at least 100% of the available space
- min-height: 100% doesn't work because it's based off the parent height, which is the <body> tag in the example
- <body> doesn't have a specific height, so the default behaviour to stay as short as possible is used
<style> .wrapper { min-height: 100%; } </style> <section class="wrapper"> <p> ... </p> </section>
-
The solution is for the parent elements of wrapper to have a height of 100%, so its min-height value is equal to the viewport height
- Put height: 100% on every element before the main one (including html and body)
- Put min-height: 100% on the wrapper
- Don't use any percentage-based heights within that wrapper
/* Some gotchas if you're using React and Next.js, apply height: 100% to these wrappers too */ html, body, #root, /* For create-react-app */ #__next { /* For Next.js */ height: 100%; }
- The vh unit, designed so the element will inherit its height from the viewport size
- Unfortunately, not as practical because there are issues with mobile devices
Margin Collapse
-
Margin collapse is unique to flow layout, not flexbox, grid, or any other layout
-
Only vertical margins collapse
- Here, the paragraphs have 24px of vertical margin and that margin collapses. The paragraphs will be 24px apart, not 48px
<style> p { margin-top: 24px; margin-bottom: 24px; } </style> <p>Paragraph One</p> <p>Paragraph Two</p>
- Horizontal margins don't collapse, due to the objective of CSS in the early days focusing mainly on headers and paragraphs primarily
-
Only adjacent elements can collapse
- Modifying the HTML above to add a line-break means the paragraphs are no longer adjacent, and therefore, don't collapse
<p>Paragraph One</p> <br /> <p>Paragraph Two</p>
-
If two elements have different margin-bottom and margin-top sizes, then the larger size will take precedence
-
Nesting doesn't prevent collapsing
- Margin is used to increase distance between sibling elements, not between a child and its parent's bounding box (that's what padding is used for)
- Here, the margin is transferred to the parent <div> element
<div> <p>Paragraph One</p> </div> <p>Paragraph Two</p>
-
Scenarios where margin collapse can be blocked:
- Padding and borders
- Gaps, if a parent element were given a height which caused a gap beneath the child element
- Scroll container
- Basically, margins must be touching in order for collapse to occur
-
Margins can also collapse in the same direction
- The child margin is "absorbed" into the parent margin, combining to use the larger margin
<style> .parent { margin-top: 72px; } .child { margin-top: 24px; } </style> <div class="parent"> <p class="child">Paragraph One</p> </div>
-
More than two margins can collapse
- Siblings can combine adjacent margins (if the first element has margin-bottom, and the second one has margin-top)
- A parent and child can combine margins in the same direction
<style> header { margin-bottom: 10px; } header h1 { margin-bottom: 20px; } section { margin-top: 30px; } section p { margin-top: 40px; } </style> <header> <h1>My Project</h1> </header> <section> <p>Hello World</p> </section>
- The space between <header> and <section> has 4 separate margins competing to occupy that space
- The header wants space below itself
- The h1 in the header has a bottom margin, which collapses with its parent
- The section below the header wants space above itself
- The p in the section has a top margin, which collapses with its parent
- Finally, the paragraph has the largest total margin so the value of a 40px margin separates the header and section
-
Collapsing negative margins
- Works similarly to positive margins, the more negative value "wins" and becomes the overlapping margin
- If a margin is positive and the other is negative, the values are added together
- If there are multiple margins that collapse with positive and negative values
- All positive margins collapse together
- All negative margins collapse together
- Add the positive and negative value together to get the overlapping margin
<style> header { margin-bottom: -20px; } header h1 { margin-bottom: 10px; } section { margin-top: -10px; } section p { margin-top: 30px; } </style> <header> <h1>My Project</h1> </header> <section> <p>Hello World</p> </section>
- The most positive margin is 30px
- The most negative margin is -20px
- Finally, sum those two values to get an overlapping margin of 10px