Responsive design is not about removing and shrinking web page components as the browser narrows: it’s about making the most of the space available in the viewport. Sometimes that means merging components, rather than taking them away. A prime example is the columnar layout of the kind shown above. On wide screens, the columns appear side-by-side:
But as the viewport narrows, it may make sense to merge columns A and C together:
There are scripts (such as the excellent appendAround) that can help create this, but they all have similar disadvantages:
- Framework dependencies.
- The setup of “catcher” containers for moved elements can be confusing.
The reality is, you can achieve your own custom solution in just a half-dozen lines of code, as demonstrated here.
The HTML & CSS
The markup for this example is pretty straightforward:
<div id="wrapper">
<nav>A</nav>
<main>B</main>
<aside>C</aside>
</div>
I’ll place the elements side-by-side and the same height using flexbox, although many other possibilities exist, including display: table
.
#wrapper {
display: flex; width: 100%;
}
#wrapper > * {
flex: .5;
}
#wrapper nav {
background: hsl(90, 50%, 80%);
display: flex;
flex-direction: column;
}
#wrapper main {
background: hsl(180, 50%, 80%); flex: 2;
}
#wrapper aside {
background: hsl(270, 50%, 80%);
}
#wrapper aside, #wrapper main {
min-width: 150px;
}
I’ve given each of the elements an HSL background to keep them distinct; the <main>
element is set to be 4× the width of the other two via the flex
property. As the <nav>
element will be taking the <aside>
element as a guest when the viewport narrows, I’ve set it to display as a column. This won’t disturb the content of the element, but will set the original content above the <aside>
when the two are merged.
Merging Elements With JavaScript
At the end of the page, we start our script by identifying the elements that will be affected, together with the conditions for switching:
var nav = document.querySelector("#wrapper > nav"),
aside = document.querySelector("#wrapper > aside"),
wrapper = document.getElementsById("wrapper"),
tablet = window.matchMedia("screen and (max-width: 800px)");
The matchMedia
expression sets the viewport width at which we want to merge the elements. This movement and merge is controlled by a single function:
function combinator(state){
aside.remove();
if (state.matches) {
nav.appendChild(aside);
} else {
wrapper.appendChild(aside);
}
}
This function effectively “pops” the <aside>
element in and out of <nav>
at the breakpoint set by the matchMedia
variable. We just have to call it:
combinator(tablet);
However, this will only call the function once, during the initial page load. We must also add a listener to react to any subsequent changes to the viewport:
tablet.addListener(combinator);
Blending The Merge
Once the merge is complete, the shifted element should take some of the styles of its new parent. I would suggest adding a single-line CSS declaration:
.wrapper nav > aside {
width: 100%; background: inherit;
}
Note that this declaration doesn’t have to placed inside an @media query, since we can usually assume there’s no other condition in which an <aside>
element might be placed inside <nav>
.
Of course there are many other possibilities for this technique; I’ll explore more in future articles.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/clrzH