What’s in the box?

Understanding the css box model is critical for building flexible and accurate abstractions. However, interacting with it in JavaScript is extremely confusing. This talk will explore the various positioning apis such as getBoundingClientRect, offsetTop, scrollWidth and so on – what they measure and how to get them to work together.

(upbeat music) – [Alex] Hello everyone. Oh great, the mic’s working. My name’s Alex.

As mentioned, I’m a principal engineer at Atlassian. And today what I’m going to be talking about is the CSS Box Model.

What it is, how it works, and how you can get access to it from JavaScript. So, firstly, what is the Box Model? At a really high level, the Box Model is about the spacing of an element, and spacing in relationship to other elements on the page. And, before going any further, I just wanna be totally vulnerable and say like this is an area of front end development that I have struggled with for a very long time. I think I look back and I’ve had kind of a very piecemeal understanding of how this all fits together. And after talking with a number of other people as well, I find that this is generally true.

That most people have a pretty broken understanding of what it is and how it works.

And I think a big reason because of that, for that, is because of the poor, poorly named JavaScript APIs for interacting with the Box Model.

I think it just adds to a lot of confusion in this space. So let’s get into it.

So when a browser views your webpage, it just sees a series of boxes.

And if you open up your developer tools, you go to the elements pane, you’ll see this little box here. And I’ll highlight it. And that’s the Box Model for a given element so when you select a different element you’ll see different values populated here. But let’s dive into what the Box Model is.

Okay, so this is the first component.

This is called the content, the content box. And this is where the real content is called for an element sit.

So this is things like text, image, video, it all goes in this box.

That’s known as the content box.

On the outside of that, we have padding.

Padding is spacing that sits on the inside of the visible edge of an compo- of an element.

And that box there, everything kind of the padding and down, is called the padding box. On the outside of padding, we have border.

And this is the visible edge of an element. And that box and everything inside it, is known as the border box.

This is a really important one, we use this one all the time.

On the outside of the border box, we have margin. So margin is spacing on the outside of an element that is relative to other elements on the page. Margin has this, sort of interesting behaviour where, in certain circumstances, the margins between two elements will be collapsed. Now the rules for this are fairly nuanced, so I won’t go into a great detail about them here. But if you just type in m-d-n margin collapsing, they’ve got a great guide there, which you can look at if you want to.

Coming back to this box, if you have a scrollbar, that will sit inside of the padding box of an element.

Which kinda looks something like this.

This is sort of a way of looking at it.

So in this case, I’ve set overflow:scroll or could be in overflow order and what’s happening is that padding box and the content box are being cut when it’s sort of reached its maximum level height. So the padding box gets clipped if you set an overflow to scroll or order.

Cool, uh Box sizing.

So, if you have an element and you set a width or a height, or some other dimension-related property on it, by default, that’s targeting the content box. So if you say, “I want my element to be 100 pixels wide,” and then you put a nice padding and border around it your element’s actually going to be wider than 100 pixels. ‘Cause that 100 pixels is targeting that content box. Now, you can change what box those properties are targeting by changing that box-sizing property there. Which is probably more what you expect when you’re actually setting, like, widths and heights and things.

That’s just something to be aware of, some CSS resets will will actually just set everything to border box, but yeah, just something to be aware of. Cool, there’s also this concept of borders that don’t take up any spacing in the browser, so these are things like outline and box-shadow. And they sit outside of the visible edge of an element, but they don’t actually push anything else away, they don’t add an extra space to the document. The Box Model can run in lots of different modes and they’re controlled by using that display property there. And these control how the padding and margin works on the element, how the elements flow and how they kind of relate to their siblings. So some, like, pretty classic ones are block, inline, inline-block.

There’re also loads of other ones like table, flex, grid. These ones here control the spacing of their child blocks. And there’s lots of other ones as well.

And these kind of control how these elements all interact with one another. Cool, so that’s the Box Model.

You can now do your next interview with confidence. But, if we have a look at now how can we get access to some of these values from within JavaScript? What’s always worth answering before how is the why. So, let’s say in this case we had a button and we wanted to have a really fancy tool tip that had some custom html in there.

We’d need to know some information about this element to be able to do this.

We would need to know what the visible edge is of this element, as well as, what that bottom line is so we could put our tool tip sort of where we wanted to. So that’s one use case, maybe a little more complicated is, say you have a list of things and you want to reorder that list in an animated way, what kind of information would you need to know to be able to do that? Well, you would probably need the visible edge of the element that’s moving, so it’s border box, but you would also need to know the margin box of the thing that you’re moving relative to so that you could make sure that you sat underneath that margin box after you finish moving. And to continue this list example, say we wanted to move an item from one list to another using JavaScript, we would need to know the visible edge of the thing that’s moving and, in this case, we wanted to align it to the top of the content box. So, in this example, we needed a border box and a content box.

So this is just a few different examples and there are lots of other ones out there. Where you may need to know different aspects of the Box Model and what those values are. So let’s take a look at how to actually get some of this information.

So here’s our beautiful Box Model that we’ve seen before. The first API that we have available to us is offsetWidth and offsetHeight.

And what these do is they return the borderBoxWidth and borderBoxHeight for an element.

I think the naming of these APIs is pretty bad and I’m gonna list what I think is, like, a more sane API name underneath in grey there. So this simply just returned the borderBoxWidth and borderBoxHeight.

After that we have clientWidth and clientHeight and these are just simply borderBoxWidth and borderBoxHeight.

After that, I’ll probably post this on Twitter so you can grab it if you want.

I’ll pause for a sec so you can take a screen shot of the whole thing when I’m done.

Is scrollWidth and scrollHeight and I mentioned before that the border box can get clipped if it has a scroll on it.

What these two values here will they will give you the full height if it wasn’t clipped.

So the alternative name could’ve been unclippedPaddingBoxWidth and height.

Uh there’s no APIs for content box and no API for margin box so, yeah, if you wanna take a photo you’re welcome to. I wish I had this, like, a year ago, three years ago, five years ago, probably longer.

Would’ve saved me a lot of time.

Anyway, so there’s the one.

So those are some APIs.

Now they return sizes, but not coordinates. And all of these values that they return are rounded so they will round, the browser will internally store such things in fractional units often and this will round it to the closest pixel value. So what if we wanted to get coordinates for our element? Well we have this pretty cool API here called an element prototype called Element.getBoundingClientRect(). And it returns the coordinates and sizes for an element. But the question is, which of these boxes does it give us? Well, we’ve seen this sort of word called client before, it was referring to the border box, borderBoxWidth and height.

So maybe it’s gonna give us back the padding box? Uh and no, it doesn’t do that.

It actually gives us back the border box.

Okay, well maybe it returns a ClientRect, whatever that is? Eh, no, it actually returns a DOMRect.

To be fair, a DOMRect and a ClientRect are pretty similar. So, a ClientRect has a top, right, bottom, left, width and height.

And a DOMRect just adds x and y to that.

X and y is actually redundant it’s the same as left, top, but, anyway, they’re in there.

So we have this API, it’s actually super powerful it will return fractional units so they’re not rounded, its like a raw fractional unit.

It accounts for any transforms, so if you have a scale on it or if you’re sliding it somewhere, it will know, like, it’s actual live size.

It accounts for any scroll in scroll containers. But I guess something to be aware of is that returning positions are relative to the current client viewpoint.

So if you needed those coordinates relative to the entire page, you would need to shift those values by whatever the current window scroll is in order to get it’s actual on, like, a full document. Cool, so this returns us the border box.

But what if we wanted the margin box, the padding box and the content box? Unfortunately, there’s no direct APIs for these things we sort of have to go about them a very roundabout way. So, there’s another API called window.getComputedStyles(element).

Not super sure why this one is on the window element, sorry, window object, and the other one’s on the element prototype.

So this one’s on the window and you pass near an element and it spits back a CSSStyleDeclaration.

And this is a massive object, which contains all of the computed style information, as the name suggests, about an element.

So it’ll have things like your boxSizing, your colour, but it will also have all of those nice spacing things that we care about.

So like marginTop, marginLeft, paddingLeft, borderRightWidth and so on.

And this will coerce them into pixel values even if they were like EMs or some other value. So, what we can do then is take this object here that we got back, pass it through a function, toSpacing’s just a name I made up, pass in that style and get it to spit back an object that we can work with.

So, in this case, it’s the margin object with the top, right, bottom, left values so, awesome. So, now we can use these objects to generate the margin border and padding for an object. Okay, bear with me, so we now have the border box so we’ve gotten this from Element.getBoundingClientRect() and what we can do is we can expand that by the margin object that we just computed to get the margin box.

We can shrink the border box by the border to get the padding box, and we can shrink the padding box by the padding to get the content.

It’s pretty wild, but by using those two APIs, you can get all of the different boxes that are associated with an element.

So, what can you take home, like today, from all this stuff, all this kind of information dump? So first up, I hope you have a bit more understanding about the Box Model, like it’s actually not too overly complicated.

And just looking at like this and the different terms kind of associated with it. I created a library called CSS-Box-Model.

Anytime you’re using like .getBoundingClientRect(), you may wanna consider using this one instead. So what it’ll do is it gives you a function, getBox, and will spit back a big object here so it has properties marginBox, borderBox, paddingBox, contentBox, and border, padding, margin.

And those boxes are, I call them like a rect. So, instead of DOMRect and ClientRect just dot rect, just to add more confusion, but those are the same as ClientRect and DOMRect, but also add a centre ’cause like centre is a super useful piece of information about a box, pretty easy to calculate, but, anyway, that’s in there.

And then, also those as well so you just have access to those.

It also has another export called withScroll. So I mentioned before that the .getBoundingClientRect() only considers where it is in the current viewpoint, if you want to you can use this API and adjust it for where it is on the page.

So you can get like it’s true coordinates relative to the entire page, not just the current viewpoint.

So coming back to where I’ve started, this is an area that I have struggled with and I see other people struggle with all the time. And I think about why this is, why this is an area that people struggle with. And, honestly, I think it’s because of the naming, of poor naming choices, I think or inconsistent naming choices, between the JavaScript APIs and how the Box Model kind of works.

And so, yes, be aware that there is this craziness that you have to deal with, but also, I think, at a high level, like think about the applications and systems that you are writing and it’s so often to be naming things that kind of, to look at the same problem through different lenses and come up with different names or slightly different abstractions, and this adds to friction and makes it harder to understand kind of the core building blocks. I think the web is kind of a bit stuck in that still, for backwards compatibility reasons, we can’t really change things but maybe in your software you can think about how you could be realigning these kind of concepts to make them more cohesive ’cause when you do that it’s surprising how easy things become to actually understand what’s going on.

Cool, that’s it. Thanks, everyone.

(applause) (upbeat music)

Join the conversation!

Your email address will not be published. Required fields are marked *

No comment yet.