Skip to content
academia.sh

Lesson 22 / 26

Backgrounds

Background layers, the calculation behind percentage positioning, repeat modes, and the numeric counterpart of cover and contain scaling.

Contents

The text decisions are complete. The back of the boxes has not been looked at yet. The Box Model lesson established that the background extends to the border box and covers the padding; what the background itself can be was not said.

A background is not just a color. It can be a layer stack made of positionable, repeatable, scalable images. This lesson takes up those layers’ rules and calculations.

A Background Is a Layer Stack

background is a shorthand and sets eight longhands: -color, -image, -position, -size, -repeat, -origin, -clip, -attachment.

background-image can take multiple comma-separated values. Each value establishes a layer, and the layers get ordered left to right, top to bottom: the first member of the list gets drawn on top.

background-color is not a layer; it always gets drawn at the very bottom, as a single color. This is why it cannot get multiplied with a comma.

The other longhands also get multiplied with a comma and get matched to the layers in order. The number of layers is determined by the length of the background-image list; lists that fall short get repeated from the start.

A Percentage Position Is an Alignment

When background-position takes a percentage, that percentage is not a distance, it is an alignment: the image’s pp percent point gets matched to the area’s pp percent point. The result:

x=(AwidthIwidth)px = (A_{\text{width}} - I_{\text{width}}) \cdot p

// background.mjs — background-position percentage and repeat math
// percentage positioning: the image's p% point lands on the area's p% point
function position(areaWidth, imageWidth, value) {
  if (typeof value === "number") return value;                 // px
  const p = parseFloat(value) / 100;
  return (areaWidth - imageWidth) * p;
}

const AREA = { width: 400, height: 200 };
const IMAGE = { width: 120, height: 60 };

console.log(`area ${AREA.width}x${AREA.height}, image ${IMAGE.width}x${IMAGE.height}`);
for (const [x, y] of [["0%","0%"], ["50%","50%"], ["100%","100%"], ["25%","75%"], [16, 16]]) {
  const left = position(AREA.width, IMAGE.width, x);
  const top = position(AREA.height, IMAGE.height, y);
  console.log(`position: ${String(x).padEnd(5)} ${String(y).padEnd(5)} -> top-left (${left}, ${top})`);
}

console.log("\n--- repeat: tile starting points ---");
function tiles(area, image, start, repeatMode) {
  if (repeatMode === "no-repeat") return [start];
  const points = [];
  let x = start;
  while (x > -image) x -= image;   // rewind leftward
  x += image;
  while (x < area) { points.push(x); x += image; }
  return points;
}
for (const mode of ["repeat", "no-repeat"]) {
  const points = tiles(AREA.width, IMAGE.width, position(AREA.width, IMAGE.width, "50%"), mode);
  console.log(`${mode.padEnd(10)} -> ${points.length} tile${points.length === 1 ? "" : "s"}, x = ${points.join(", ")}`);
}

console.log("\n--- repeat: leftover horizontal space ---");
for (const areaWidth of [400, 360, 480]) {
  const whole = Math.floor(areaWidth / IMAGE.width);
  const remainder = areaWidth - whole * IMAGE.width;
  const roundScale = areaWidth / Math.round(areaWidth / IMAGE.width);
  console.log(`area=${areaWidth}: repeat -> ${whole} whole + ${remainder}px cropped | round -> ${Math.round(areaWidth / IMAGE.width)} tiles, each ${roundScale.toFixed(2)}px | space -> ${whole} tiles, ${whole > 1 ? (remainder / (whole - 1)).toFixed(2) : 0}px between`);
}

console.log("\n--- background-size: cover and contain ---");
function scale({ areaWidth, areaHeight, imageWidth, imageHeight }, mode) {
  const scaleX = areaWidth / imageWidth, scaleY = areaHeight / imageHeight;
  const factor = mode === "cover" ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
  return { width: +(imageWidth * factor).toFixed(1), height: +(imageHeight * factor).toFixed(1), factor: +factor.toFixed(3) };
}
const dims = { areaWidth: 400, areaHeight: 200, imageWidth: 800, imageHeight: 600 };
for (const mode of ["cover", "contain"]) {
  const result = scale(dims, mode);
  console.log(`${mode.padEnd(8)} factor=${result.factor} -> ${result.width}x${result.height}  (area ${dims.areaWidth}x${dims.areaHeight})`);
}
area 400x200, image 120x60
position: 0%    0%    -> top-left (0, 0)
position: 50%   50%   -> top-left (140, 70)
position: 100%  100%  -> top-left (280, 140)
position: 25%   75%   -> top-left (70, 105)
position: 16    16    -> top-left (16, 16)

--- repeat: tile starting points ---
repeat     -> 5 tiles, x = -100, 20, 140, 260, 380
no-repeat  -> 1 tile, x = 140

--- repeat: leftover horizontal space ---
area=400: repeat -> 3 whole + 40px cropped | round -> 3 tiles, each 133.33px | space -> 3 tiles, 20.00px between
area=360: repeat -> 3 whole + 0px cropped | round -> 3 tiles, each 120.00px | space -> 3 tiles, 0.00px between
area=480: repeat -> 4 whole + 0px cropped | round -> 4 tiles, each 120.00px | space -> 4 tiles, 0.00px between

--- background-size: cover and contain ---
cover    factor=0.5 -> 400x300  (area 400x200)
contain  factor=0.333 -> 266.7x200  (area 400x200)

The first block’s 50% 50% value giving 140 and 70 is the calculation’s direct result: half the difference between the area and the image. The result is the image getting centered. 100% 100% pushes it to the bottom-right corner, 0% 0% to the top-left.

The difference with pixel values shows up in the last row: 16px 16px is always 16 units from the edge, independent of the area’s and image’s dimensions. A percentage declares an alignment, a length declares a distance.

background-position also accepts a four-value writing, which selects which edge to measure from: the writing right 16px bottom 16px places it 16 units in from the bottom-right corner.

Repeat Modes

The second block shows that in repeat mode, tiling starts outside the visible area: the first tile sits at 100-100. Repetition is defined as extending the positioned tile to infinity in both directions; whatever falls outside the visible area gets cropped.

The third block gives the difference between the three modes in numbers. A 120-unit image in a 400-unit area:

  • repeat — 3 whole tiles fit, a 40-unit remainder appears cropped.
  • round — the tile count gets rounded and the image gets scaled: 3 tiles, each 133.33 units. No cropping remains, but the image’s aspect ratio breaks.
  • space — the tile count gets rounded down and the leftover space gets distributed between the gaps: 3 tiles, 20 units apart each. The scale is preserved, a gap opens up.

When the area is an exact multiple of the image (the 360 and 480 rows), all three modes give the same result.

repeat-x and repeat-y repeat on only one axis; no-repeat does not repeat at all.

cover and contain

The last block compares the two scaling keywords. An 800×600 image in a 400×200 area:

cover — picks the larger of the two axes’ scale ratios. The result is 400×300: the image fills the area completely, and the overflowing part gets cropped.

contain — picks the smaller one. The result is 266.7×200: the whole image is visible, and part of the area stays empty.

The decision gets made by this question: is cropping acceptable, or is leftover space? cover gets written for a decorative image, contain for an image that needs to be seen in full.

When cover gets used, where the cropping happens gets directed with background-position; if the image’s important part is near an edge, centered cropping cuts it off.

The Background’s Boundaries

Three properties determine which region the background relates to, and the region names from the Box Model lesson appear here as values.

background-origin — chooses which box positioning gets measured relative to. Its initial value is padding-box.

background-clip — chooses how far the background gets drawn. Its initial value is border-box; this is why the background shows through the gaps of a dashed border. When content-box gets written, the background does not enter the padding.

background-attachment — chooses whether the background scrolls together with the box (scroll, local) or gets fixed to the viewport (fixed).

Image Backgrounds and Accessibility

A background image does not exist in the document: it has no alternative text, does not get declared to assistive technology, and does not get read as content by a search engine. This is the same boundary as the one for content produced with pseudo-elements.

The consequence is a rule: no image that carries meaning gets placed as a background. A location map has to stand in the document as an img element with alternative text. Backgrounds are for decorative textures and surfaces that text gets written over.

The legibility of text written over a base image also needs to get secured. If the image does not download or its download is delayed, the text faces the background color; the contrast between that color and the text color has to pass the criterion calculated in the previous lesson.

/* station.css — step 21: background layers */
.masthead {
  background-color: var(--surface);
  background-image: url("/images/topography.svg");
  background-position: right -40px top 50%;
  background-repeat: no-repeat;
  background-size: 320px auto;
}

.measurement-table tbody tr:nth-child(odd) {
  background-color: hsl(var(--hue) 18% 97%);
  background-clip: padding-box;
}

.warning-box {
  background-color: hsl(0 66% 97%);
  background-image: url("/images/warning.svg");
  background-position: 12px center;
  background-repeat: no-repeat;
  background-size: 20px 20px;
  padding-inline-start: 44px;
}

The pattern in the masthead is decorative and gets written together with background-color: if the image does not download, the surface color remains, and the heading text’s contrast stays intact.

The icon in the .warning-box class is decorative too; the warning itself is written in the text inside the box. The icon is only a visual marker for the text, and padding-inline-start makes room for it — the icon not overlapping the text depends on this declaration.

Summary

  • background-image builds a layer stack by getting multiplied with a comma; the first member of the list gets drawn on top, and background-color always stays at the bottom, as a single color.
  • A percentage background position is an alignment, not a distance: the image’s pp percent point gets matched to the area’s pp percent point; this is why 50% centers.
  • Repeat modes handle the leftover space differently: repeat crops it, round removes the crop by scaling the image, space distributes the leftover between the tiles.
  • cover picks the larger of the two scale ratios, fills the area, and crops the overflow; contain picks the smaller one, shows the whole image, and leaves empty space.
  • A background image does not exist in the document and does not get declared to assistive technology; images that carry meaning belong in the markup, backgrounds get written only as a decorative layer.

Next Step

Background layers got built with images coming from a file. A layer can also get produced without a file: with calculated transitions between colors. The next lesson takes up linear and radial gradients, the calculation behind color stops, and why gradients count as a background image, not an image.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close