Skip to content

Commit 3019f7b

Browse files
First stab at fixing the primary nav on small viewports
- Introduce a responsive navigation system that “moves” overflowed items into a hamburger menu. These changes make the header adapt to smaller viewports and prevent wrapping/overflow of nav items. - Tweak the corresponding CSS rules and header image sizing (so it doesn’t shrink to 0 width and entirely disappear).
1 parent bc3cd5e commit 3019f7b

File tree

4 files changed

+140
-15
lines changed

4 files changed

+140
-15
lines changed

_includes/partials/_nav-links.njk

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
1-
<a href="{{ page | relative }}/get/">Get<span class="wide"> Color.js</span></a>
2-
<div class="menu">
1+
<a href="{{ page | relative }}/get/" data-nav-item="1">Get<span class="wide"> Color.js</span></a>
2+
<div class="menu" data-nav-item="2">
33
<a href="{{ page | relative }}/docs/">Docs</a>
44
<ul>
55
{% include "./_docs-nav.njk" %}
66
</ul>
77
</div>
8-
<a href="{{ page | relative }}/api/">API</a>
9-
<a href="{{ page | relative }}/notebook/">Play!</a>
10-
<a href="https://apps.colorjs.io/">Demos</a>
11-
<a href="https://elements.colorjs.io/">Elements</a>
12-
<a href="{{ page | relative }}/test/" class="footer">Tests</a>
13-
<a href="https://github.com/LeaVerou/color.js">GitHub</a>
14-
<a href="https://discord.gg/K64FJBznq4">Discord</a>
15-
<a href="https://opencollective.com/color">♡&nbsp;Sponsor</a>
16-
<a href="https://github.com/LeaVerou/color.js/issues/new" class="footer">File bug</a>
8+
<a href="{{ page | relative }}/api/" data-nav-item="3">API</a>
9+
<a href="{{ page | relative }}/notebook/" data-nav-item="4">Play!</a>
10+
<a href="https://apps.colorjs.io/" data-nav-item="5">Demos</a>
11+
<a href="https://elements.colorjs.io/" data-nav-item="6">Elements</a>
12+
<a href="{{ page | relative }}/test/" class="footer" data-nav-item="7">Tests</a>
13+
<a href="https://github.com/LeaVerou/color.js" data-nav-item="8">GitHub</a>
14+
<a href="https://discord.gg/K64FJBznq4" data-nav-item="9">Discord</a>
15+
<a href="https://opencollective.com/color" data-nav-item="10">♡&nbsp;Sponsor</a>
16+
<a href="https://github.com/LeaVerou/color.js/issues/new" class="footer" data-nav-item="11">File bug</a>
17+
<div class="hamburger-menu">
18+
<button class="hamburger-button" aria-label="Menu">
19+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 48 48">
20+
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M7.95 11.95h32m-32 12h32m-32 12h32"/>
21+
</svg>
22+
</button>
23+
<ul class="hamburger-list">
24+
<li data-nav-item="1" hidden><a href="{{ page | relative }}/get/">Get Color.js</a></li>
25+
<li data-nav-item="2" hidden><a href="{{ page | relative }}/docs/">Docs</a></li>
26+
<li data-nav-item="3" hidden><a href="{{ page | relative }}/api/">API</a></li>
27+
<li data-nav-item="4" hidden><a href="{{ page | relative }}/notebook/">Play!</a></li>
28+
<li data-nav-item="5" hidden><a href="https://apps.colorjs.io/">Demos</a></li>
29+
<li data-nav-item="6" hidden><a href="https://elements.colorjs.io/">Elements</a></li>
30+
<li data-nav-item="8" hidden><a href="https://github.com/LeaVerou/color.js">GitHub</a></li>
31+
<li data-nav-item="9" hidden><a href="https://discord.gg/K64FJBznq4">Discord</a></li>
32+
<li data-nav-item="10" hidden><a href="https://opencollective.com/color">♡&nbsp;Sponsor</a></li>
33+
</ul>
34+
</div>

_includes/plain.njk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<link rel="stylesheet" href="{{ page | relative }}/assets/css/style.css">
1414

1515
<script src="{{ page | relative }}/color.js" type="module"></script>
16+
<script src="{{ page | relative }}/assets/js/nav.js" type="module"></script>
1617

1718
{% if has_mavo %}
1819
<link rel="stylesheet" href="https://get.mavo.io/mavo.css">

assets/css/style.css

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ body > footer {
197197
animation: var(--rainbow-scroll);
198198

199199
& nav {
200-
& > .menu {
200+
& > :where(.menu, .hamburger-menu) {
201201
flex: 1;
202202
display: flex;
203203
position: relative;
@@ -224,13 +224,14 @@ body > footer {
224224
}
225225
}
226226

227-
& a {
227+
& a:where(:not([hidden])) {
228228
display: block;
229229
text-align: center;
230230
}
231231

232232
& > .menu > a,
233-
& a:not(.logo) {
233+
& a:not(.logo),
234+
& .hamburger-button {
234235
flex: 1;
235236
padding: .6em;
236237
font-weight: 800;
@@ -243,6 +244,33 @@ body > footer {
243244
text-decoration: none;
244245
}
245246
}
247+
248+
.hamburger-menu {
249+
&:has(li:last-of-type[hidden]) {
250+
/* All menu items are hidden; hide the menu */
251+
display: none;
252+
}
253+
254+
& .hamburger-button {
255+
display: grid;
256+
place-items: center;
257+
border: none;
258+
cursor: default;
259+
text-align: center;
260+
color: hsl(var(--gray) 40%);
261+
262+
> svg {
263+
width: 100%;
264+
}
265+
}
266+
267+
& .hamburger-list {
268+
min-inline-size: 15ch;
269+
right: 0;
270+
top: 100%;
271+
transform-origin: top right;
272+
}
273+
}
246274
}
247275
}
248276

@@ -295,6 +323,7 @@ body > header {
295323

296324
& img {
297325
height: 2.15em;
326+
min-width: 1.3em;
298327
margin-bottom: -1.5em;
299328
margin-left: -.3em;
300329
}
@@ -426,7 +455,7 @@ pre[class*="language-"] {
426455
}
427456

428457
@supports (-webkit-background-clip: text) and (not (-moz-margin-start: 0)) {
429-
body > header nav a,
458+
body > header nav :where(a, .hamburger-button),
430459
body > footer nav a,
431460
main h2,
432461
main h2 > a {

assets/js/nav.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Responsive Navigation Script
3+
* Dynamically hides navigation items that don't fit on screen in the main nav
4+
* and shows them in the hamburger menu instead. Uses ResizeObserver to monitor
5+
* the nav element and checks items from right to left to determine which ones
6+
* overflow. Items are matched between nav and hamburger menu using data-nav-item
7+
* attributes.
8+
*/
9+
10+
const nav = document.querySelector("header nav");
11+
const menu = nav.querySelector(".hamburger-menu");
12+
const [menuButton, menuList] = menu.children;
13+
14+
// Map to track nav items and their hamburger menu counterparts
15+
let itemMap = new Map();
16+
17+
// Get all nav items (excluding the hamburger menu and the ones that are supposed to be shown in the footer)
18+
let navItems = [...nav.children].filter(
19+
child => !child.classList.contains("hamburger-menu") && !child.classList.contains("footer"),
20+
);
21+
22+
let hamburgerMenuItems = [...menuList.children];
23+
for (let navItem of navItems) {
24+
let index = navItem.dataset.navItem;
25+
let hamburgerItem = hamburgerMenuItems.find(item => item.dataset.navItem === index);
26+
if (hamburgerItem) {
27+
itemMap.set(navItem, hamburgerItem);
28+
}
29+
}
30+
31+
const resizeObserver = new ResizeObserver(checkFit);
32+
resizeObserver.observe(nav);
33+
34+
function checkFit () {
35+
// Reset: show all items in nav, hide all in hamburger
36+
itemMap.forEach((hamburgerItem, navItem) => {
37+
navItem.hidden = false;
38+
hamburgerItem.hidden = true;
39+
});
40+
41+
// Temporarily show hamburger menu to measure its width
42+
menu.style.setProperty("display", "block");
43+
44+
let hamburgerButtonWidth = menuButton.offsetWidth ?? 50;
45+
let navRect = nav.getBoundingClientRect();
46+
let navRight = navRect.right;
47+
let availableRight = navRight - hamburgerButtonWidth;
48+
49+
let items = [...itemMap.keys()];
50+
let toHide = [];
51+
52+
// Check each item from right to left to see which ones overflow
53+
// by comparing their right edge to available space
54+
for (let i = items.length - 1; i >= 0; i--) {
55+
const item = items[i];
56+
const itemRect = item.getBoundingClientRect();
57+
const itemRight = itemRect.right;
58+
// If this item's right edge exceeds available space, hide it
59+
if (itemRight > availableRight) {
60+
toHide.push(item);
61+
}
62+
else {
63+
// Once we find an item that fits, we can stop
64+
break;
65+
}
66+
}
67+
68+
menu.style.removeProperty("display");
69+
70+
// Hide items in nav and show corresponding items in hamburger menu
71+
for (let navItem of toHide) {
72+
let hamburgerItem = itemMap.get(navItem);
73+
74+
navItem.hidden = true;
75+
hamburgerItem.hidden = false;
76+
}
77+
}

0 commit comments

Comments
 (0)