From fae98e70e00e80409fc9afe0a9d1d9c0c5d2698b Mon Sep 17 00:00:00 2001 From: Volker Schaefer Date: Wed, 6 Dec 2023 14:26:00 +0100 Subject: [PATCH 1/5] feat: optimizes one page navigation * optimizes behavior of intersection oberver * adjusts markup structure of kurzbericht template for intesection observer --- src/_layouts/kurzbericht.11ty.js | 4 +- src/assets/scripts/main.js | 88 +++++++++++++++++++++++++++----- 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/src/_layouts/kurzbericht.11ty.js b/src/_layouts/kurzbericht.11ty.js index 8939ff23b..ea28bf05c 100644 --- a/src/_layouts/kurzbericht.11ty.js +++ b/src/_layouts/kurzbericht.11ty.js @@ -15,9 +15,9 @@ module.exports = { const status = item.data.meta && item.data.meta.status ? `is-${item.data.meta.status}` : ''; return ` -
+
- ${item.data.title} + ${item.data.title} ${utils.getOpenInNewWindowLink(item, data)}${utils.getEditLink(item, data)} ${meta} ${item.content} diff --git a/src/assets/scripts/main.js b/src/assets/scripts/main.js index b7a444a87..48d042f20 100644 --- a/src/assets/scripts/main.js +++ b/src/assets/scripts/main.js @@ -232,7 +232,6 @@ const addScrollToTop = () => { ############################################################################ */ const addScrollSpy = () => { - const inlineNavigation = document.querySelector("[data-js-scrollspy]"); if(!inlineNavigation) return; @@ -244,25 +243,86 @@ const addScrollSpy = () => { } if(viewportSize !== "large") return; - const sections = document.querySelectorAll("h2[id], h3[id]"); - + const sections = document.querySelectorAll("[data-js-scrollspy-section]"); const intersectionCallback = (entries, observer) => { - if (entries[0].intesectionRatio <= 0) return; - - if (entries[0].intersectionRatio > 0 || entries[0].intersectionRatio < 0.2) { + let updated = false; + entries.forEach(entry => { + // if the menu was aleady updated by a previous entry, we don't need to check the other entries. + if (updated) { + return; + } + let activeBlock; // the block that should be highlighted in the menu. + if(!entry.isIntersecting) { + + // This element has just left the viewport. This means at least one of 2 possibilities: + // 1. the next element has its top aligned with the top of the viewport. + // In this case the next element should be activated. + // 2. the previous element is (at least partly) visible. + // In this case we need to keep looking at previous elements until + // we find the highest one that is still visible. + if (isInViewport(entry.target.nextElementSibling)) { + // possibility 1. + activeBlock = entry.target.nextElementSibling; + + } else if (isInViewport(entry.target.previousElementSibling)) { + // possibility 2. + activeBlock = entry.target.previousElementSibling; + while ( isInViewport(activeBlock.previousElementSibling) ) { + activeBlock = activeBlock.previousElementSibling; + } + } + } else { + // This element just entered the viewport. 2 possibilities: + // 1. The top of the element moved in from the bottom. + // In this case nothing needs to happen. + // 2. The bottom of the element moved in from the top. + // In this case the current element should also become active. + if (!isInViewport(entry.target.nextElementSibling)) { + // possibility 1. + return; + + } else if (!isInViewport(entry.target.previousElementSibling)) { + // possibility 2. + activeBlock = entry.target; + } + } + + // remove active class on all elements. + inlineNavigation.querySelectorAll('.is-active').forEach((item) => { + item.classList.remove('is-active'); + }); + // Couldn't deternmine an active block. So don't do anything. + if (!activeBlock) { + return; + } - if(scrollSpyActiveElement) scrollSpyActiveElement.classList.remove('is-active'); + // get id of the intersecting section + const id = activeBlock.getAttribute('id'); + + // find matching link and add appropriate class + const navElement = inlineNavigation.querySelector(`[data-scrollspy-target="${id}"] as`); - const {id} = entries[0].target; - const activeElement = inlineNavigation.querySelector(`[data-scrollspy-target="${id}"]`).querySelector("a"); - activeElement.classList.add('is-active'); + if(!navElement) return; + navElement.classList.add('is-active'); - scrollSpyActiveElement = activeElement; - } + // Wer're done. Set flag to make sure that we don't check other entries. + updated = true; + }); }; - const intersectionOptions = {}; - const intersectionObserver = new IntersectionObserver(intersectionCallback, intersectionOptions); + + const isInViewport = (element) => { + if (!element) { + return false; + } + const rect = element.getBoundingClientRect(); + return ( + rect.bottom >= 0 && + rect.top <= (window.innerHeight || document.documentElement.clientHeight) + ); + } + + const intersectionObserver = new IntersectionObserver(intersectionCallback, { }); sections.forEach((section) => { intersectionObserver.observe(section); From cd529b14502fcc7afa22471264eaed7b259bf94a Mon Sep 17 00:00:00 2001 From: Volker Schaefer Date: Wed, 6 Dec 2023 14:26:00 +0100 Subject: [PATCH 2/5] feat: optimizes one page navigation * optimizes behavior of intersection oberver * adjusts markup structure of kurzbericht template for intesection observer --- src/_layouts/kurzbericht.11ty.js | 4 +- src/assets/scripts/main.js | 88 +++++++++++++++++++++++++++----- 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/src/_layouts/kurzbericht.11ty.js b/src/_layouts/kurzbericht.11ty.js index 8939ff23b..ea28bf05c 100644 --- a/src/_layouts/kurzbericht.11ty.js +++ b/src/_layouts/kurzbericht.11ty.js @@ -15,9 +15,9 @@ module.exports = { const status = item.data.meta && item.data.meta.status ? `is-${item.data.meta.status}` : ''; return ` -
+
- ${item.data.title} + ${item.data.title} ${utils.getOpenInNewWindowLink(item, data)}${utils.getEditLink(item, data)} ${meta} ${item.content} diff --git a/src/assets/scripts/main.js b/src/assets/scripts/main.js index b7a444a87..7debdaaf1 100644 --- a/src/assets/scripts/main.js +++ b/src/assets/scripts/main.js @@ -232,7 +232,6 @@ const addScrollToTop = () => { ############################################################################ */ const addScrollSpy = () => { - const inlineNavigation = document.querySelector("[data-js-scrollspy]"); if(!inlineNavigation) return; @@ -244,25 +243,86 @@ const addScrollSpy = () => { } if(viewportSize !== "large") return; - const sections = document.querySelectorAll("h2[id], h3[id]"); - + const sections = document.querySelectorAll("[data-js-scrollspy-section]"); const intersectionCallback = (entries, observer) => { - if (entries[0].intesectionRatio <= 0) return; - - if (entries[0].intersectionRatio > 0 || entries[0].intersectionRatio < 0.2) { + let updated = false; + entries.forEach(entry => { + // if the menu was aleady updated by a previous entry, we don't need to check the other entries. + if (updated) { + return; + } + let activeBlock; // the block that should be highlighted in the menu. + if(!entry.isIntersecting) { + + // This element has just left the viewport. This means at least one of 2 possibilities: + // 1. the next element has its top aligned with the top of the viewport. + // In this case the next element should be activated. + // 2. the previous element is (at least partly) visible. + // In this case we need to keep looking at previous elements until + // we find the highest one that is still visible. + if (isInViewport(entry.target.nextElementSibling)) { + // possibility 1. + activeBlock = entry.target.nextElementSibling; + + } else if (isInViewport(entry.target.previousElementSibling)) { + // possibility 2. + activeBlock = entry.target.previousElementSibling; + while ( isInViewport(activeBlock.previousElementSibling) ) { + activeBlock = activeBlock.previousElementSibling; + } + } + } else { + // This element just entered the viewport. 2 possibilities: + // 1. The top of the element moved in from the bottom. + // In this case nothing needs to happen. + // 2. The bottom of the element moved in from the top. + // In this case the current element should also become active. + if (!isInViewport(entry.target.nextElementSibling)) { + // possibility 1. + return; + + } else if (!isInViewport(entry.target.previousElementSibling)) { + // possibility 2. + activeBlock = entry.target; + } + } + + // remove active class on all elements. + inlineNavigation.querySelectorAll('.is-active').forEach((item) => { + item.classList.remove('is-active'); + }); + // Couldn't deternmine an active block. So don't do anything. + if (!activeBlock) { + return; + } - if(scrollSpyActiveElement) scrollSpyActiveElement.classList.remove('is-active'); + // get id of the intersecting section + const id = activeBlock.getAttribute('id'); + + // find matching link and add appropriate class + const navElement = inlineNavigation.querySelector(`[data-scrollspy-target="${id}"] a`); - const {id} = entries[0].target; - const activeElement = inlineNavigation.querySelector(`[data-scrollspy-target="${id}"]`).querySelector("a"); - activeElement.classList.add('is-active'); + if(!navElement) return; + navElement.classList.add('is-active'); - scrollSpyActiveElement = activeElement; - } + // Wer're done. Set flag to make sure that we don't check other entries. + updated = true; + }); }; - const intersectionOptions = {}; - const intersectionObserver = new IntersectionObserver(intersectionCallback, intersectionOptions); + + const isInViewport = (element) => { + if (!element) { + return false; + } + const rect = element.getBoundingClientRect(); + return ( + rect.bottom >= 0 && + rect.top <= (window.innerHeight || document.documentElement.clientHeight) + ); + } + + const intersectionObserver = new IntersectionObserver(intersectionCallback, { }); sections.forEach((section) => { intersectionObserver.observe(section); From b91740cd8ad5c65bbf8956d46d436f6715335bf0 Mon Sep 17 00:00:00 2001 From: Volker Schaefer Date: Wed, 6 Dec 2023 14:42:28 +0100 Subject: [PATCH 3/5] feat: adjusts rootMargin for better behavior --- src/assets/scripts/main.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/assets/scripts/main.js b/src/assets/scripts/main.js index 7debdaaf1..e6dc089c1 100644 --- a/src/assets/scripts/main.js +++ b/src/assets/scripts/main.js @@ -322,7 +322,9 @@ const addScrollSpy = () => { ); } - const intersectionObserver = new IntersectionObserver(intersectionCallback, { }); + const intersectionObserver = new IntersectionObserver(intersectionCallback, { + rootMargin: '-1px', + }); sections.forEach((section) => { intersectionObserver.observe(section); From cd56cb1d1660bbebca3f425802ac67315c55eb0d Mon Sep 17 00:00:00 2001 From: Volker Schaefer Date: Thu, 7 Dec 2023 18:45:41 +0100 Subject: [PATCH 4/5] feat: adjusts scrollspy so it observes intersecting of headlines --- src/_layouts/kurzbericht.11ty.js | 4 +- src/assets/scripts/main.js | 129 ++++++++++++------------------- 2 files changed, 53 insertions(+), 80 deletions(-) diff --git a/src/_layouts/kurzbericht.11ty.js b/src/_layouts/kurzbericht.11ty.js index ea28bf05c..dc798763a 100644 --- a/src/_layouts/kurzbericht.11ty.js +++ b/src/_layouts/kurzbericht.11ty.js @@ -15,9 +15,9 @@ module.exports = { const status = item.data.meta && item.data.meta.status ? `is-${item.data.meta.status}` : ''; return ` -
+
- ${item.data.title} + ${item.data.title} ${utils.getOpenInNewWindowLink(item, data)}${utils.getEditLink(item, data)} ${meta} ${item.content} diff --git a/src/assets/scripts/main.js b/src/assets/scripts/main.js index 7400caeff..b2d457cc3 100644 --- a/src/assets/scripts/main.js +++ b/src/assets/scripts/main.js @@ -230,8 +230,7 @@ const addScrollToTop = () => { /* Scrollspy ############################################################################ */ - -const addScrollSpy = () => { +const addScrollSpy = () => { const inlineNavigation = document.querySelector("[data-js-scrollspy]"); if(!inlineNavigation) return; @@ -243,94 +242,68 @@ const addScrollSpy = () => { } if(viewportSize !== "large") return; - const sections = document.querySelectorAll("[data-js-scrollspy-section]"); + const headlines = document.querySelectorAll('h2[id], h3[id]'); + const arrayHeadlines = Array.from(headlines); + let lastActiveElementId = false; const intersectionCallback = (entries, observer) => { - let updated = false; entries.forEach(entry => { - // if the menu was aleady updated by a previous entry, we don't need to check the other entries. - if (updated) { - return; - } - let activeBlock; // the block that should be highlighted in the menu. - if(!entry.isIntersecting) { - - // This element has just left the viewport. This means at least one of 2 possibilities: - // 1. the next element has its top aligned with the top of the viewport. - // In this case the next element should be activated. - // 2. the previous element is (at least partly) visible. - // In this case we need to keep looking at previous elements until - // we find the highest one that is still visible. - if (isInViewport(entry.target.nextElementSibling)) { - // possibility 1. - activeBlock = entry.target.nextElementSibling; - - } else if (isInViewport(entry.target.previousElementSibling)) { - // possibility 2. - activeBlock = entry.target.previousElementSibling; - while ( isInViewport(activeBlock.previousElementSibling) ) { - activeBlock = activeBlock.previousElementSibling; - } - } - } else { - // This element just entered the viewport. 2 possibilities: - // 1. The top of the element moved in from the bottom. - // In this case nothing needs to happen. - // 2. The bottom of the element moved in from the top. - // In this case the current element should also become active. - if (!isInViewport(entry.target.nextElementSibling)) { - // possibility 1. - return; - - } else if (!isInViewport(entry.target.previousElementSibling)) { - // possibility 2. - activeBlock = entry.target; + if (entry.isIntersecting) { + let position = (entry.boundingClientRect.top + (entry.boundingClientRect.top - entry.boundingClientRect.bottom)); + if (position < 0) { + console.log('enter viewport top') + if (arrayHeadlines.indexOf(entry.target) > 0) { + if (lastActiveElementId) { + setStatusNavElement(lastActiveElementId, false); + } + const previousElement = arrayHeadlines[arrayHeadlines.indexOf(entry.target) - 1]; + setStatusNavElement(previousElement, true); + lastActiveElementId = previousElement; + } + } else { + console.log('enter viewport bottom'); + } + } else { + + if (entry.intersectionRatio < 0) { + } else { + if (entry.boundingClientRect.top < 1) { + if (lastActiveElementId) { + setStatusNavElement(lastActiveElementId, false); + } + //console.log('leave viewport top'); + console.log(entry.target) + setStatusNavElement(entry.target, true); + lastActiveElementId = entry.target; + } else { + } + } } - } - - // remove active class on all elements. - inlineNavigation.querySelectorAll('.is-active').forEach((item) => { - item.classList.remove('is-active'); - }); - // Couldn't deternmine an active block. So don't do anything. - if (!activeBlock) { - return; - } - - // get id of the intersecting section - const id = activeBlock.getAttribute('id'); - - // find matching link and add appropriate class - const navElement = inlineNavigation.querySelector(`[data-scrollspy-target="${id}"] a`); - - if(!navElement) return; - navElement.classList.add('is-active'); - - // Wer're done. Set flag to make sure that we don't check other entries. - updated = true; }); }; - - - const isInViewport = (element) => { - if (!element) { - return false; - } - const rect = element.getBoundingClientRect(); - return ( - rect.bottom >= 0 && - rect.top <= (window.innerHeight || document.documentElement.clientHeight) - ); - } const intersectionObserver = new IntersectionObserver(intersectionCallback, { - rootMargin: '-1px', + rootMargin: '-1px 0px 0px 0px', + threshold: [1], }); - sections.forEach((section) => { - intersectionObserver.observe(section); + headlines.forEach((headline) => { + intersectionObserver.observe(headline); }); + + const setStatusNavElement = (target, isActive) => { + const id = target.getAttribute('id'); + if(!id) return; + const navElement = inlineNavigation.querySelector(`[data-scrollspy-target="${id}"] a`); + if(!navElement) return; + if(isActive) { + navElement.classList.add('is-active'); + } else { + navElement.classList.remove('is-active'); + } + } }; + /* Größe des Viewports ermitteln ############################################################################ */ From 63e9fea6fa006e08228b1c3b6992d1f644254d7a Mon Sep 17 00:00:00 2001 From: Volker Schaefer Date: Thu, 7 Dec 2023 18:48:36 +0100 Subject: [PATCH 5/5] refactor: removes console.log and empty else branches --- src/assets/scripts/main.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/assets/scripts/main.js b/src/assets/scripts/main.js index b2d457cc3..5fde114c8 100644 --- a/src/assets/scripts/main.js +++ b/src/assets/scripts/main.js @@ -250,7 +250,6 @@ const addScrollSpy = () => { if (entry.isIntersecting) { let position = (entry.boundingClientRect.top + (entry.boundingClientRect.top - entry.boundingClientRect.bottom)); if (position < 0) { - console.log('enter viewport top') if (arrayHeadlines.indexOf(entry.target) > 0) { if (lastActiveElementId) { setStatusNavElement(lastActiveElementId, false); @@ -259,22 +258,16 @@ const addScrollSpy = () => { setStatusNavElement(previousElement, true); lastActiveElementId = previousElement; } - } else { - console.log('enter viewport bottom'); - } + } } else { - if (entry.intersectionRatio < 0) { } else { if (entry.boundingClientRect.top < 1) { if (lastActiveElementId) { setStatusNavElement(lastActiveElementId, false); } - //console.log('leave viewport top'); - console.log(entry.target) setStatusNavElement(entry.target, true); lastActiveElementId = entry.target; - } else { } } }