Skip to content

Commit 273d865

Browse files
authored
Merge pull request #615 from itk-dev/fix/timeline-mini-nav-multiple-dots
Fix multiple mini-nav dots highlighted when scrolling fast
2 parents ba4eb4d + 1f9db12 commit 273d865

2 files changed

Lines changed: 59 additions & 3 deletions

File tree

web/modules/custom/hoeringsportal_project/src/Helper/ProjectHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ private function addNoteAsTimelineItem(ParagraphInterface $paragraph, DateTimeIm
269269
$image = $paragraph?->field_paragraph_image?->entity?->field_itk_media_image_upload?->entity?->getFileUri();
270270

271271
return [
272-
'id' => '',
272+
'id' => 'note-' . $paragraph->id(),
273273
'date' => $date->format('Y-m-d'),
274274
'month' => $date->format('F Y'),
275275
'title' => $paragraph->field_title->value,

web/themes/custom/hoeringsportal/assets/js/timeline.js

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,19 +138,75 @@
138138
return;
139139
}
140140

141+
// Track all currently intersecting cards
142+
const visibleCards = new Set();
143+
141144
const observerOptions = {
142145
root: null,
143146
rootMargin: OBSERVER_ROOT_MARGIN,
144147
threshold: 0,
145148
};
146149

147150
state.observer = new IntersectionObserver((entries) => {
151+
// Update the visibility set with changed entries
152+
// Only track cards that are actually visible (have dimensions)
148153
entries.forEach((entry) => {
154+
const { cardId } = entry.target.dataset;
155+
const rect = entry.boundingClientRect;
156+
const isVisible = rect.height > 0 && rect.width > 0;
157+
158+
// Only process events from visible cards (ignore hidden horizontal duplicates)
159+
if (!isVisible) {
160+
return;
161+
}
162+
149163
if (entry.isIntersecting) {
150-
const { cardId } = entry.target.dataset;
151-
updateActiveNavLink(cardId);
164+
visibleCards.add(cardId);
165+
} else {
166+
visibleCards.delete(cardId);
167+
}
168+
});
169+
170+
// Find the card that best overlaps the viewport center
171+
// Prefer the topmost card whose bounds contain the center point
172+
const viewportCenter = window.innerHeight / 2;
173+
let bestCardId = null;
174+
let bestScore = -Infinity;
175+
176+
visibleCards.forEach((cardId) => {
177+
// Find the visible card (not the hidden horizontal view duplicate)
178+
const cards = timeline.querySelectorAll(`[data-card-id="${cardId}"]`);
179+
const card = Array.from(cards).find(
180+
(c) => c.getBoundingClientRect().height > 0,
181+
);
182+
if (card) {
183+
const rect = card.getBoundingClientRect();
184+
185+
// Score based on how well the card covers the viewport center
186+
// Higher score = card contains or is closer to center
187+
let score;
188+
if (rect.top <= viewportCenter && rect.bottom >= viewportCenter) {
189+
// Card contains the center point - highest priority
190+
// Prefer cards where center is further from the edges (more centered)
191+
const distFromTop = viewportCenter - rect.top;
192+
const distFromBottom = rect.bottom - viewportCenter;
193+
score = 1000 + Math.min(distFromTop, distFromBottom);
194+
} else {
195+
// Card doesn't contain center - score by distance
196+
const cardCenter = rect.top + rect.height / 2;
197+
score = -Math.abs(cardCenter - viewportCenter);
198+
}
199+
200+
if (score > bestScore) {
201+
bestScore = score;
202+
bestCardId = cardId;
203+
}
152204
}
153205
});
206+
207+
if (bestCardId) {
208+
updateActiveNavLink(bestCardId);
209+
}
154210
}, observerOptions);
155211

156212
elements.cards.forEach((card) => {

0 commit comments

Comments
 (0)