Skip to content

Commit 7deb70a

Browse files
feat: Mobile content-navigation-menu (#310)
## Description Display a content-navigation-menu on mobile devices (max. MD). - sticky menu bar with page name - bottom sheet: choose another chapter - click and drag-support --------- Co-authored-by: MoritzWeber0 <kontakt@moritz-weber.net>
1 parent a1c3442 commit 7deb70a

File tree

17 files changed

+438
-203
lines changed

17 files changed

+438
-203
lines changed

assets/js/contentNavigation.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import * as mq from "./mediaqueries";
2+
3+
const isMobile = () => {
4+
return window.matchMedia(mq.maxMD).matches;
5+
};
6+
7+
const initAside = () => {
8+
const aside = document.querySelector(".o-aside");
9+
const asideContent = document.querySelector(".o-aside__content");
10+
const handleBtn = document.querySelector(".o-aside__header");
11+
const overlay = document.getElementById("overlay");
12+
13+
if (!aside || !asideContent || !handleBtn || !overlay) return;
14+
15+
let isClosed = true;
16+
17+
const closeSheet = () => {
18+
isClosed = true;
19+
aside.classList.remove("o-aside--open");
20+
asideContent.removeAttribute("role");
21+
asideContent.setAttribute("aria-hidden", "true");
22+
handleBtn.setAttribute("aria-expanded", "false");
23+
overlay.classList.remove("overlay--show");
24+
};
25+
26+
const openSheet = () => {
27+
isClosed = false;
28+
aside.classList.add("o-aside--open");
29+
overlay.classList.add("overlay--show");
30+
asideContent.setAttribute("role", "dialog");
31+
asideContent.setAttribute("aria-hidden", "false");
32+
handleBtn.setAttribute("aria-expanded", "true");
33+
};
34+
35+
if (isMobile()) {
36+
closeSheet();
37+
}
38+
39+
const toggleSheet = () => {
40+
if (isClosed) {
41+
openSheet();
42+
} else {
43+
closeSheet();
44+
}
45+
};
46+
47+
handleBtn.addEventListener("click", toggleSheet);
48+
overlay.addEventListener("click", closeSheet);
49+
50+
// Drag support
51+
let startY = 0;
52+
let currentY = 0;
53+
let isDragging = false;
54+
55+
const dragStart = (e) => {
56+
startY = e.clientY || e.touches?.[0].clientY;
57+
isDragging = true;
58+
};
59+
60+
const dragging = (e) => {
61+
if (!isDragging) return;
62+
e.preventDefault();
63+
currentY = e.clientY || e.touches?.[0].clientY;
64+
const deltaY = startY - currentY;
65+
66+
if (isClosed && deltaY > 50) {
67+
openSheet();
68+
}
69+
70+
if (!isClosed && deltaY < -50) {
71+
closeSheet();
72+
}
73+
};
74+
75+
const dragEnd = () => {
76+
if (!isDragging) return;
77+
isDragging = false;
78+
};
79+
80+
handleBtn.addEventListener("mousedown", dragStart);
81+
handleBtn.addEventListener("mousemove", dragging);
82+
handleBtn.addEventListener("mouseup", dragEnd);
83+
84+
handleBtn.addEventListener("touchstart", dragStart);
85+
handleBtn.addEventListener("touchmove", dragging);
86+
handleBtn.addEventListener("touchend", dragEnd);
87+
88+
// close bottom-sheet if link is clicked
89+
document.querySelectorAll(".o-aside__toc-link").forEach((link) => {
90+
link.addEventListener("click", () => {
91+
if (isMobile()) {
92+
closeSheet();
93+
}
94+
});
95+
});
96+
97+
let wasMobile = isMobile();
98+
99+
const handleResize = () => {
100+
if (isMobile() && !wasMobile) {
101+
wasMobile = true;
102+
closeSheet();
103+
}
104+
if (!isMobile()) {
105+
wasMobile = false;
106+
overlay.classList.remove("overlay--show");
107+
aside.classList.remove("o-aside--open");
108+
}
109+
};
110+
111+
window.addEventListener("load", handleResize);
112+
window.addEventListener("resize", handleResize);
113+
};
114+
115+
if (document.readyState === "interactive") {
116+
initAside();
117+
} else {
118+
window.addEventListener("DOMContentLoaded", () => {
119+
initAside();
120+
});
121+
}

assets/js/highlightHeadline.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
import * as mq from "./mediaqueries";
2-
3-
function isAsideActive() {
4-
return window.matchMedia(mq.minLG).matches;
5-
}
6-
71
function initHighlightHeadline() {
82
const headings = Array.from(
93
document.querySelectorAll(".o-single__highlight :is(h1, h2, h3)"),
@@ -28,10 +22,6 @@ function initHighlightHeadline() {
2822
let scrollDebounce;
2923

3024
function updateActiveHeading() {
31-
if (!isAsideActive()) {
32-
return;
33-
}
34-
3525
let currentHeading = null;
3626

3727
for (let i = 0; i < headings.length; i++) {

assets/js/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import "./mobileMenu.js";
22
import "./resizeObserver.js";
33
import "./mediaqueries.js";
44
import "./highlightHeadline.js";
5+
import "./contentNavigation.js";
56
import "./anchorlinks.js";
67
import "./dropdown.js";
78
import "./darkmode.js";

assets/js/mediaqueries.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
* import * as mq from '../helpers/mediaqueries';
44
*==================================================
55
*/
6-
export const maxXS = "(max-width: 575px)";
7-
export const maxSM = "(max-width: 751px)";
8-
export const maxMD = "(max-width: 967px)";
9-
export const maxLG = "(max-width: 1182px)";
10-
export const maxXL = "(max-width: 1463px)";
6+
export const maxXS = "(max-width: 576px)";
7+
export const maxSM = "(max-width: 768px)";
8+
export const maxMD = "(max-width: 992px)";
9+
export const maxLG = "(max-width: 1200px)";
10+
export const maxXL = "(max-width: 1400px)";
1111

12-
export const minSM = "(min-width: 576px)";
13-
export const minMD = "(min-width: 752px)";
14-
export const minLG = "(min-width: 968px)";
15-
export const minXL = "(min-width: 1183px)";
16-
export const minXXL = "(min-width: 1464px)";
12+
export const minSM = "(min-width: 577px)";
13+
export const minMD = "(min-width: 768px)";
14+
export const minLG = "(min-width: 993px)";
15+
export const minXL = "(min-width: 1201px)";
16+
export const minXXL = "(min-width: 1401px)";

assets/sass/contentNavigation.scss

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
.o-aside {
2+
margin-bottom: 0;
3+
position: sticky;
4+
top: 8rem;
5+
font-size: 1.4rem;
6+
line-height: 1.5;
7+
margin-right: 0;
8+
z-index: 3;
9+
display: flex;
10+
flex-direction: column;
11+
gap: 1.5rem;
12+
flex-wrap: wrap;
13+
height: fit-content;
14+
15+
ul,
16+
ol {
17+
list-style-type: none;
18+
list-style-position: outside;
19+
padding-left: 0;
20+
21+
ul,
22+
ol {
23+
margin-bottom: 0;
24+
}
25+
}
26+
}
27+
28+
.o-aside__header {
29+
display: none;
30+
}
31+
32+
.o-aside a {
33+
text-decoration-line: none;
34+
display: flex;
35+
align-items: flex-start;
36+
gap: 0.4rem;
37+
}
38+
39+
.o-aside__backlink-text {
40+
display: flex;
41+
align-items: center;
42+
gap: 0.4rem;
43+
}
44+
45+
.o-aside__toc {
46+
li {
47+
list-style-type: none;
48+
}
49+
50+
ol {
51+
padding: 0 0 0 1em;
52+
}
53+
54+
> ol {
55+
padding-left: 0;
56+
}
57+
}
58+
59+
.o-aside__toc-link {
60+
display: flex;
61+
}
62+
63+
.o-aside__toc a[data-current] {
64+
font-weight: bold;
65+
66+
&:before {
67+
content: "";
68+
width: 0.4rem;
69+
height: 2.1rem;
70+
position: absolute;
71+
display: flex;
72+
left: 0;
73+
background-color: var(--link-default);
74+
}
75+
}
76+
77+
.o-related {
78+
&__list {
79+
list-style-type: none;
80+
list-style-position: outside;
81+
padding-left: 0;
82+
}
83+
84+
&-date {
85+
padding-left: 2.5rem;
86+
}
87+
88+
p {
89+
margin-bottom: 0;
90+
}
91+
}
92+
93+
.o-related__item img {
94+
border-radius: var(--border-radius-s);
95+
}
96+
97+
@media (max-width: #{$breakpoint-lg}) {
98+
.o-aside {
99+
position: fixed;
100+
top: unset;
101+
bottom: 0;
102+
left: 0;
103+
width: 100%;
104+
max-height: 100vh;
105+
gap: 0;
106+
overflow: hidden;
107+
flex-flow: column;
108+
transition: transform 0.5s ease-in-out;
109+
border-radius: var(--border-radius-m) var(--border-radius-m) 0 0;
110+
transform: translateY(calc(100% - 6.4rem - env(safe-area-inset-bottom)));
111+
box-shadow: var(--box-shadow);
112+
max-height: calc(100vh - 20rem - env(safe-area-inset-bottom));
113+
114+
&--open {
115+
transform: translateY(0);
116+
}
117+
118+
&:not(.o-aside--open) {
119+
> .o-aside__header {
120+
border-bottom: none;
121+
}
122+
123+
> .o-aside__content {
124+
pointer-events: none;
125+
user-select: none;
126+
127+
* {
128+
opacity: 0;
129+
}
130+
}
131+
}
132+
133+
&__content {
134+
background: var(--bg-default);
135+
overflow-y: scroll;
136+
overscroll-behavior-y: contain;
137+
border-left: var(--border);
138+
border-right: var(--border);
139+
140+
.o-single__container {
141+
border: none;
142+
}
143+
144+
* {
145+
transition: opacity 0.3s ease-in-out;
146+
}
147+
}
148+
149+
&__header {
150+
display: flex;
151+
align-items: center;
152+
flex-direction: column;
153+
gap: 1.5rem;
154+
width: 100%;
155+
border: var(--border);
156+
border-bottom: 1px solid #3d444d;
157+
padding: 1rem;
158+
height: 6.4rem;
159+
border-radius: 1rem 1rem 0 0;
160+
background: var(--bg-default);
161+
color: var(--color-body);
162+
}
163+
164+
&__drag {
165+
height: 0.4rem;
166+
width: 4rem;
167+
display: block;
168+
background: var(--color-body);
169+
border-radius: 2rem;
170+
}
171+
172+
&__logo {
173+
display: flex;
174+
align-items: center;
175+
gap: 1.5rem;
176+
}
177+
}
178+
179+
main.o-newspage .o-aside {
180+
display: none;
181+
}
182+
}
183+
184+
.overlay--show {
185+
backdrop-filter: blur(4px);
186+
background-color: rgba(0, 0, 0, 0.6);
187+
position: fixed;
188+
inset: 0;
189+
z-index: 3;
190+
}
191+
192+
body:has(.overlay--show) {
193+
overflow: hidden;
194+
}

assets/sass/main.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
@import "styles.scss";
33
@import "fonts.scss";
44
@import "navigation.scss";
5-
@import "sidemenu.scss";
5+
@import "contentNavigation";
66
@import "search.scss";
77
@import "teaser.scss";
88
@import "footer.scss";

0 commit comments

Comments
 (0)