Categories CSS javascript

Pixel-perfecting old layouts

Post date February 27, 2021

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 corner
  • top – distance between viewport’s top edge and box’s top left corner
  • right distance between viewport’s left edge and box’s top right corner (can be also calculated as left + width)
  • bottom – distance between viewport’s top edge and box’s bottom left corner (can be also calculated as top + height)
  • x – same as left
  • y – same as top
  • width – the width of the area occupied by the box including content, padding and border. If you are using box-sizing: border-box, this is exactly the same as width
  • height – the height of the area occupied by the box including content, padding and border. If you are using box-sizing: border-box, this is exactly the same as height

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.

 

 

 

 

 

Leave a Reply

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