Once I got a task of upgrading an old website and making it responsive. Sounds common enough. Decision was made to migrate data to new CMS but keep the old layout, because the client liked it. And he liked it exactly the way it was – every single pixel of it. So the question is: “How do I use modern CSS and keep the elements exactly where they were on a screen?” on desktop to conveniently move them around on smaller screens?
Using old CSS and media queries
If the old CSS worked just fine and the only problem are elements with width: 600px
or min-width: 1024px
and old HTML was fine, then media queries sound very reasonable. You don’t need to reinvent the wheel for desktop and only write brand new CSS for smaller screens.
Writing new CSS
But what to do when you cannot keep the old HTML (for example layout built with table
elements) and copy-pasting old CSS won’t work with new DOM structure? Or you want to get rid of position: absolute
for everything or replace images with text with real text? That’s where pixel-perfection turns into nightmare.
Finding element’s position on the screen
If you open developer tool you will easily find the box model – width
, height
, padding
, border
and margin
of every element. The values are always in pixels, no matter if they were defined in pixels, rems, percentages or with calc() in the style sheet. In this particular use case it is helpful, however, if you need to know what was evaluated to that value, you better have a look at computed tab.
Same width, different width
CSS is simple and complex at the same time. The syntax is as simple as it could be and list of properties and their values is limited, but when you have a look at interactions between different properties you quickly understand why many back end developers don’t like doing front end.
For example width
. If you copy some element’s width
and paste it directly into your style sheet you may get different result than expected because of box-sizing
property. If it is set to border-box
width
and padding
properties will be treated differently from what they would be treated if you specified box-sizing: content-box
.
In the case of box-sizing: border-box
box is as wide as property width
indicates, with padding
on the inside of specified width
. Box with box-sizing: content-box
has padding
outside the box of specified width
. If you have a clickable element padding
is also clickable part of the element. margin
is not clickable no matter the box-sizing
. That’s first reason why I prefer box-sizing: border-box
– whatever I write behind width
property corresponds to the clickable area. The second reason is creating grids – if you want two elements next to each other width: 50%
sounds just right and with box-sizing: border-box
it is. However if you try to fit two elements with width: 50%
and padding
or border
greater than 0
next to each other box-sizing: content-box
will make them wider than parent’s width and cause overflow.
So we managed to get over the hurdle of box-sizing
. But there are still max-width
, min-width
, flex-basis
, align-self
, transform
and other properties, that can influence the final width of an element on your screen. So how to stay sane reproducing an old layout? And what does it have to do with pixel perfection? Well, it shows, that having the box model alone is not enough to get the job done.
getBoundingClientRect()
for the rescue
If you ever tried to position a popup element on the screen and had to figure out when it gets cut off at the edge of the screen you most probably have heard of getBoundingClientRect()
, if not let me introduce you to this function.
getBoundingClientRect
can be called on any DOM element and will return the values representing size and position on the screen relative to the viewport. You call it like this:
element.getBoundingClientRect();
and get the following values (in pixels):
left
– distance between viewport’s left edge and box’s top left cornertop
– distance between viewport’s top edge and box’s top left cornerright
distance between viewport’s left edge and box’s top right corner (can be also calculated asleft
+width
)bottom
– distance between viewport’s top edge and box’s bottom left corner (can be also calculated astop
+height
)x
– same asleft
y
– same astop
width
– the width of the area occupied by the box including content, padding and border. If you are usingbox-sizing: border-box
, this is exactly the same aswidth
height
– the height of the area occupied by the box including content, padding and border. If you are usingbox-sizing: border-box
, this is exactly the same asheight
Now, that we have the coordinates, the task of getting elements to the right place seems much easier. We need to push the element to the left? Let’s go with margin-left
or, if the element is positioned absolutely, left
.
The nice part of using getBoundingClientRect
is, that it returns values with fractions, so if the element is 12.15px
from the top you get the exact number directly without the need to guess it by typing different fractions and comparing the results manually.
The sad part is that in the worst case we need to run the function for every single element on the website. Ugh. AND we still need to remember, that if the values we get got evaluated from percentages or viewport units, the original layout and the brand new layout may look different the minute you resize the browser window.