Command Palette

Search for a command to run...

Hectal
PHASE 1Beginner ~27 min· topic 4 of 7

Topic 1.4

viewBox and preserveAspectRatio

In one line

The viewBox attribute gives an SVG its own coordinate window, so the drawing scales to whatever size the screen gives it. preserveAspectRatio decides what happens when window and screen have different shapes: fit, fill, or stretch.

Think of it like this

A slide in a projector. The slide never changes, but you can project it onto a postcard-sized screen or a wall. The picture grows or shrinks to fill the screen, and nothing needs to be redrawn. A viewBox is the slide: a fixed set of coordinates that the browser stretches onto any screen size.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

viewBox
Four numbers (min-x, min-y, width, height) that say which part of the drawing's coordinates to show. The browser stretches that window onto the SVG's screen size.
scale
How many screen pixels one user unit becomes. A 200-wide viewBox shown in 300 pixels has scale 1.5.
aspect ratio
Width divided by height. A 200 by 100 box has ratio 2.
meet
Fit the whole viewBox inside the box, keeping its shape. Empty bars may remain.
slice
Cover the whole box, keeping the shape. Parts of the drawing may be cropped.
letterbox
The empty bars above and below (or left and right) of a picture that is fitted without stretching.
responsive
A layout that changes with the screen size. Here: a drawing that scales to the width it is given.

Step by step

01See the same drawing at four viewBoxes

The first example draws one small scene (a frame, a circle and a word) in four 200 by 100 SVGs. The only thing that changes is the viewBox. With none, one unit is one pixel. With 0 0 100 50 you look at the top-left quarter of the scene, so it fills the box: a zoom of 2. With 50 25 100 50 the same zoom is moved to the middle: a pan. With 0 0 400 200 the window is twice as big as the scene, so everything is half size.

For each SVG the Console prints the scale and offset the browser used, read from the circle's getCTM() (its current transformation matrix: where user units land on screen).

02Fit, fill or stretch

The second example puts one square drawing (viewBox 0 0 100 100) in three 300 by 100 boxes. meet shows the whole square at scale 1, centred, with 100 pixels of empty space on each side. slice scales by 3 to fill the width, so it crops the top and bottom: only the middle third shows. none stretches x by 3 and keeps y at 1: the circle becomes a wide oval.

Below the pictures the Console lists what the nine alignments do. In a wide box with a square viewBox, xMin, xMid and xMax push the picture to the left, centre or right (offsets 0, 100 and 200). The Y words do nothing here because the picture already fills the height. In a tall box it is the other way round.

the three modeswhole filehtml
<svg width="300" height="100" viewBox="0 0 100 100">                           <!-- meet, the default -->
<svg width="300" height="100" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice">
<svg width="300" height="100" viewBox="0 0 100 100" preserveAspectRatio="none">

03Make the width follow the box

The third example puts width="100%" SVGs in boxes 160, 240 and 320 pixels wide. With viewBox="0 0 200 100" and no height, the SVG is 80, 120 and 160 tall, always half its width, and the scale is 0.8, 1.2 and 1.6. Without a viewBox, the SVG is always 150 tall and nothing scales: the picture would be cut off or squashed.

Using the Phase 0 week chart, a viewBox of 0 0 380 210 on an SVG with width="100%" fits it to any box, and the code inside does not change at all. Topic 1.7 does exactly this.

04When the viewBox itself is wrong

The same example ends with broken viewBoxes. NaN prints Error: <svg> attribute viewBox: Expected number, "0 0 NaN 100". A width of 0 draws nothing and prints nothing: the SVG is blank. A negative width and a viewBox with only three numbers are ignored, so the drawing appears at scale 1 as if there were no viewBox. The Console prints A negative value is not valid. for the first and Unexpected end of attribute. Expected number for the second. Zero width is the one with no message anywhere.

A blank or unscaled chart with no error is the usual sign. Check the viewBox's four numbers first.

Try it yourself

  1. 1

    Zoom in on the circle

    In the first example, change the second viewBox to 15 20 60 30. Now the circle fills most of the box. The second line of the Console prints a scale of 3.33…: a 60-wide window in 200 pixels. Zoom is only a smaller window.

  2. 2

    Slice instead of meet

    In the second example's first call, change "" to "xMinYMin slice". The picture is cropped and pinned to the top-left: you see the top third of the square, with the L in the corner. Pinning is what the Min and Max words are for.

  3. 3

    Drop the height

    In the third example, change '<svg width="100%" ' to '<svg height="60" '. Now every SVG with a viewBox is 120 by 60, scale 0.6, whatever its box: the browser takes the width from the viewBox's shape. The one without a viewBox becomes 300 by 60. Give one dimension, not both, when the shape should follow the viewBox.

Code & diagrams

Same scene, four viewBoxes: scale, zoom and pan New tab

Zoom is a smaller viewBox, pan is a different start. The numbers are the browser's own, read back from the drawing.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

viewBox no viewBox      -> scale 1, moved by 0, 0
viewBox 0 0 100 50      -> scale 2, moved by 0, 0
viewBox 50 25 100 50    -> scale 2, moved by -100, -50
viewBox 0 0 400 200     -> scale 0.5, moved by 0, 0
Meet, slice and none in a wide box New tab

A square drawing in a 3-to-1 box. Meet fits it whole, slice crops it, none distorts it. The last two lines show where the nine alignments place it.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

meet (the default): scale 1.0 across, 1.0 down, offset 100, 0
slice, centred: scale 3.0 across, 3.0 down, offset 0, -100
none, stretched: scale 3.0 across, 1.0 down, offset 0, 0
wide box, xMin / xMid / xMax (YMid): 0,0   100,0   200,0
tall box, YMin / YMid / YMax (xMid): 0,0   0,100   0,200
Responsive width, and viewBoxes that are wrong New tab

With a viewBox the height follows the width. The zero-width viewBox draws nothing (look at the blank box in the preview); the negative and three-number ones print their errors and show scale 1 because the browser ignored them.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

viewBox 0 0 200 100 in a 160px box: SVG is 160 x 80, scale 0.8
viewBox 0 0 200 100 in a 240px box: SVG is 240 x 120, scale 1.2
viewBox 0 0 200 100 in a 320px box: SVG is 320 x 160, scale 1.6
no viewBox          in a 240px box: SVG is 240 x 150, scale 1
zero width (blank)   in a 240px box: SVG is 240 x 150, scale 1
negative width       in a 240px box: SVG is 240 x 150, scale 1
Error: <svg> attribute viewBox: A negative value is not valid. ("0 0 -200 100")
three numbers only   in a 240px box: SVG is 240 x 150, scale 1
Error: <svg> attribute viewBox: Unexpected end of attribute. Expected number, "0 0 200".
NaN                  in a 240px box: SVG is 240 x 150, scale 1
Error: <svg> attribute viewBox: Expected number, "0 0 NaN 100".

Try it: interactive

The viewBox lab

Change the four viewBox numbers and the alignment, and watch the window move over the drawing. Try a zoom, a pan, and a box with a different shape.

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Squash a drawing with preserveAspectRatio="none"

In the third example's first inBox call, change the SVG attributes to viewBox="0 0 200 100" height="150" preserveAspectRatio="none".

terminal
$ Preview and Console
── what you'll see ──
The first line of the Console changes to: viewBox 0 0 200 100 in a 160px box: SVG is 160 x 150, scale 0.8
In the preview, the yellow circle in the first box is a tall oval, about 64 wide and 120 tall. The other boxes are unchanged. No error is printed.

Break #2

Type a viewBox with NaN

In your own page, compute the viewBox from data that is not ready yet: svg.setAttribute("viewBox", "0 0 " + width + " 100") where width is NaN.

terminal
$ Console
── what you'll see ──
Error: <svg> attribute viewBox: Expected number, "0 0 NaN 100".

Myth vs fact

Myth

The viewBox is the size of the SVG.

Fact

The SVG's size is its width and height (or CSS). The viewBox is the part of the drawing's own coordinates that fills that size. They are separate, and the scale is the ratio between them.

Myth

Scaling an SVG only changes its size, so the lines stay the same thickness.

Fact

Strokes and text scale with the drawing. A 2-unit stroke at scale 3 is 6 pixels thick. CSS vector-effect: non-scaling-stroke keeps a stroke's width in screen pixels.

Myth

An SVG always fills its box if I give it width 100%.

Fact

Only with a viewBox. Without one the height falls back to 150 pixels and nothing scales.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A viewBox keeps the drawing's coordinates stable, but it also shrinks text. A chart drawn for 380 units wide and shown in 190 pixels has text half its size. For text that must stay readable, responsive charts measure their container and redraw with new coordinates (Phase 10). A viewBox is the right tool for icons, fixed-shape illustrations and the simple dashboard frame built here.

  • ▸

    getCTM() returns the matrix [a b c d e f] that maps an element's units to its nearest SVG's pixels: a and d are the horizontal and vertical scale, e and f the offset. getScreenCTM() goes all the way to the page, which tooltips and pointer maths need (Phase 8).

  • ▸

    Because slice crops, a 3-to-1 box with a square viewBox shows only the middle third by default (YMid). Anything the viewBox says is visible can be outside the box. Use meet for charts and diagrams (nothing must be cut) and slice for decorative backgrounds.

  • ▸

    When both width and height come from CSS (for example width: 100%; height: 100% in a sized parent), the box's shape is whatever the layout gives it. A mismatch with the viewBox then letterboxes the drawing (meet) rather than squashing it, which is why none is rarely right for charts.

Remember this

  1. 1

    The problem this solves: the Phase 0 week chart has a fixed width of 380 pixels. On a phone it overflows, so the box scrolls sideways. On the wall screen it stays small. With a viewBox the same drawing scales to the space it is given, from a phone to a wall, without changing a single coordinate inside it.

  2. 2

    The four numbers. viewBox="min-x min-y width height" says which part of the drawing's own coordinates to show. viewBox="0 0 200 100" shows x from 0 to 200 and y from 0 to 100. The width and height attributes (or CSS) say how big the SVG is on the screen. The browser then scales by screen width ÷ viewBox width. A viewBox 200 wide shown in 400 pixels doubles everything.

  3. 3

    User units and pixels are now different things. Until now one unit was one pixel. With a viewBox, one unit is whatever the scale makes it: half a pixel, two pixels, or 1.53. Stroke widths and text sizes scale with the drawing, too. A 2-unit line in a viewBox shown at three times its size is a 6-pixel line.

  4. 4

    Zoom and pan are just edits to the viewBox. A smaller viewBox shows less of the drawing, so it looks bigger: that is zoom. Changing min-x and min-y moves the window: that is pan. Map and chart zoom later in this course (Phase 8) change exactly these four numbers.

  5. 5

    **preserveAspectRatio handles the mismatch.** If the viewBox is square but the screen box is wide, something has to give. meet (the default) fits the whole drawing inside, leaving empty bars, like background-size: contain. slice fills the box and crops the overflow, like cover. none stretches each direction on its own, which distorts circles into ovals. The first word picks where the drawing sits: xMin, xMid or xMax, then YMin, YMid or YMax, giving 9 alignments. The default is xMidYMid meet.

  6. 6

    A responsive SVG has a viewBox and no fixed height. Give the SVG width="100%" and a viewBox, and leave out height: the browser works out the height from the viewBox's shape. Without a viewBox the height falls back to 150 pixels and nothing scales, which is the usual cause of a squashed or cut-off chart. A wrong viewBox fails differently depending on the mistake: NaN prints an error, a zero width draws nothing, and a negative or incomplete value is ignored, all described in the third example.

Explain it without notes

01

What do the four numbers of a viewBox mean, and how does the browser use them?

02

What is the difference between meet and slice?

03

Why does an SVG with width="100%" and no viewBox stay 150 pixels tall?

04

What do you see when a viewBox is 0 0 0 100, 0 0 -200 100 and 0 0 NaN 100?

Practice

01

A drawing is 380 wide and 210 tall (as in the Phase 0 week chart). Write the opening <svg> tag so it fills its container's width and keeps the right shape.

02

You want to zoom into the area from x = 100 to x = 200 and y = 20 to y = 70 of a drawing. What viewBox do you write, and what is the scale if the SVG is 400 pixels wide?

Trade-offs

  • ↔

    A viewBox gives free scaling and crisp lines at any size, but text scales too and may become too small on a phone. Redrawing at the container's size keeps text readable at the cost of more code (Phase 10).

  • ↔

    slice makes a drawing always fill its box but may crop data. For charts, cropped data is a lie by omission, so charts use meet.

Done when you can

  • I can say what each of the four viewBox numbers does.

  • I can compute the scale from a viewBox and an SVG size.

  • I know when to use meet, slice and none, and why charts use meet.

  • I can make an SVG responsive: width 100%, a viewBox, no height.

  • I can recognise a NaN, zero, negative or incomplete viewBox from what I see.