Intersection observer function scope - javascript

I am having problems with intersection observer. I want to add the classList "percent-animate" to only the element with the dataset.id = "percentage-status". However, this classList is also being added to the elements underneath it. If i return out of the if block for the percentageBar.dataset.id === "percentage-status" if statement, then the if block underneath doesn't run. Any ideas how to solve this?
const inLeft = document.querySelector(".in-left");
const inRight = document.querySelector(".in-right");
const animate = document.querySelector(".percent-animate");
const percentageBar = document.querySelector(".container__percent-progress");
const skillsObserver = new IntersectionObserver(
function (entries, skillsObserver) {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
if (percentageBar.dataset.id === "percentage-status") {
entry.target.classList.add("percent-animate");
console.log("it worked");
}
if (entry.isIntersecting) {
entry.target.classList.add("mergeLeft");
entry.target.classList.add("mergeRight");
skillsObserver.unobserve(entry.target);
return;
}
});
},
{
threshold: 0.5,
}
);
skillsObserver.observe(percentageBar);
skillsObserver.observe(inLeft);
skillsObserver.observe(inRight);

Related

Why callback not detecting latest value of class instance variable

I have a web component which is basically a class:
class NavList extends HTMLElement {
_wrapper;
_observer;
_observerActive = true;
get observerState() {
return this._observerActive;
}
render() {
this._wrapper.innerHTML = "";
const activeList = window.location.hash.slice(1);
const container = htmlToElement(`<nav class="navlist"></nav>`);
for (let list in veritabani) {
container.appendChild(
htmlToElement(
`<a href=#${list} class="nav-entry ${
activeList === list ? "active" : ""
}">${list}</div>`
)
);
}
// prevent observer from changing hash during smooth scrolling
container.addEventListener("click", this.disableObserver);
this._wrapper.appendChild(container);
}
observe() {
let options = {
root: document.querySelector(".check-list"),
rootMargin: "0px",
threshold: 0.4,
};
let observer = new IntersectionObserver(
this.observerCallback.bind(this),
options
);
this._observer = observer;
const targets = document.querySelectorAll("check-list");
console.log("observer target:", targets);
for (let target of targets) {
observer.observe(target);
}
}
observerCallback(entries, observer) {
console.log("observer active?", this.observerState);
entries.forEach((entry) => {
if (entry.isIntersecting && this.observerState) {
const targetListName = entry.target.getAttribute("list");
console.log(entry, targetListName);
window.location.hash = targetListName;
this.render();
}
});
}
disableObserver() {
this._observerActive = false;
console.log("observer disabled", this._observerActive);
function enableObserver() {
this._observerActive = true;
console.log("observer enabled", this._observerActive);
}
const timer = setTimeout(enableObserver, 2000);
}
connectedCallback() {
console.log("hash", window.location.hash);
// wrapper for entire checklist element
const wrapper = this.appendChild(
htmlToElement(
`<span class="navlist-wrapper ${window.location.hash}"></span>`
)
);
this._wrapper = wrapper;
this.render();
setTimeout(() => {
this.observe();
}, 1);
}
// more code below
As you can see, I have an intersection observer and I am trying to disable its callback when an anchor is clicked.
The observer detects elements on the page and changes the URL hash so that the visible element name is highlighted on the navlist, this works fine but interferes with the function of the navlist since clicking on navlist entry should also scroll the page to that element!
My solution is to disable the intersection observer's callback after a navlist entry is clicked using setTimout:
disableObserver() {
this._observerActive = false;
console.log("observer disabled", this._observerActive);
function enableObserver() {
this._observerActive = true;
console.log("observer enabled", this._observerActive);
}
const timer = setTimeout(enableObserver, 2000);
}
The above code sets an instance variable to false after a click on navlist, the variable changes state to false but the observer's callback does not see the change and uses the old state which is true by default.
My Question: Why is this happening? and how can I fix it?
I tried delaying the callback function thinking that it is being activated before the state change, but it did not work.
UPDATE: Here is a link to a live demo of what I am doing
I found a solution though I still do not quite understand whats happening.
The solution is to move the flag _observerActive outside of the class Navlist:
let OBSERVER_STATE = true;
class NavList extends HTMLElement {
_wrapper;
_observer;
render() {
this._wrapper.innerHTML = "";
const activeList = window.location.hash.slice(1);
const container = htmlToElement(`<nav class="navlist"></nav>`);
for (let list in veritabani) {
console.log(`active? ${list}===${activeList}`);
container.appendChild(
htmlToElement(
`<a href=#${list} class="nav-entry ${
activeList === list ? "active" : ""
}">${list}</div>`
)
);
}
// prevent observer from changing hash during smooth scrolling
container.addEventListener("click", this.disableObserver);
const addButton = htmlToElement(
`<button class="nav-add">
<span class="nav-add-content">
<span class="material-symbols-outlined">add_circle</span>
<p>Yeni list</p>
</span>
</button>`
);
addButton.addEventListener("click", this.addList.bind(this));
this._wrapper.appendChild(container);
this._wrapper.appendChild(addButton);
}
disableObserver() {
OBSERVER_STATE = false;
console.log("observer disabled", this.OBSERVER_STATE);
function enableObserver() {
OBSERVER_STATE = true;
console.log("observer enabled", OBSERVER_STATE);
}
const timer = setTimeout(enableObserver, 2000);
}
addList() {
const inputGroup = htmlToElement(`
<div class="input-group">
</div>`);
const input = inputGroup.appendChild(
htmlToElement(`
<input placeholder="Liste Adi Giriniz"></input>`)
);
const button = inputGroup.appendChild(
htmlToElement(`
<button>✔</button>`)
);
button.addEventListener("click", () =>
this.addNewCheckList(input.value)
);
input.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
console.log(input.value);
this.addNewCheckList(input.value);
}
});
const addButton = document.querySelector(".nav-add");
console.log(this._wrapper);
this._wrapper.replaceChild(inputGroup, addButton);
}
addNewCheckList(baslik) {
veritabani[baslik] = {};
const checkListContainer = document.querySelector(".check-list");
const newCheckList = htmlToElement(`
<check-list
baslik="${baslik} Listem"
list="${baslik}"
placeholder="�� A��klamas�..."
></check-list>`);
checkListContainer.appendChild(newCheckList);
this._observer.observe(newCheckList);
this.render();
newCheckList.scrollIntoView();
}
observe() {
let options = {
root: document.querySelector(".check-list"),
rootMargin: "0px",
threshold: 0.4,
};
let observer = new IntersectionObserver(
this.observerCallback.bind(this),
options
);
this._observer = observer;
const targets = document.querySelectorAll("check-list");
console.log("observer target:", targets);
for (let target of targets) {
observer.observe(target);
}
}
observerCallback(entries, observer) {
console.log("observer active?", OBSERVER_STATE);
entries.forEach((entry) => {
if (entry.isIntersecting && OBSERVER_STATE) {
const targetListName = entry.target.getAttribute("list");
window.location.hash = targetListName;
this.render();
}
});
}
connectedCallback() {
console.log("hash", window.location.hash);
// wrapper for entire checklist element
const wrapper = this.appendChild(
htmlToElement(
`<span class="navlist-wrapper ${window.location.hash}"></span>`
)
);
this._wrapper = wrapper;
this.render();
setTimeout(() => {
this.observe();
}, 1);
}
}
If I understand correctly, you want to
Create a nav list that renders a link for each anchor (id) on a page.
When a target scrolls into view, highlight the associated link and update the location hash
When clicking on a link in the Navbar, scroll to the target and update the location hash
You don't have to keep track of the IntersectObserver state and you don't have to disable it. Just use pushState() instead of location.hash to update the hash. https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
index.html
<head>
<style>
/* Makes sure that the first section scrolls up enough to trigger the effect */
section { scroll-margin: 20px }
</style>
</head>
<body>
<div class="sidebar">
<nav-bar></nav-bar>
</div>
<div class="content">
<section id="One">
<header><h1>One</h1></header>
<p> A bunch of text</p>
</section>
<!-- A Bunch of Sections -->
</div>
</body>
component.js
export class NavList extends HTMLElement {
#template = `
<style>
.visible {
background-color: orange;
}
</style>
<menu></menu>
`;
constructor() {
super();
this.shadow = this.attachShadow({mode: "open"});
}
connectedCallback() {
const li = document.createElement('li');
const a = document.createElement('a');
this.tmpl = document.createRange().createContextualFragment(this.#template);
this.menu = this.tmpl.querySelector('menu');
this.anchors = document.querySelectorAll('[id]');
this.observer = new IntersectionObserver( entries => {
const entry = entries.shift();
const id = entry.target.getAttribute('id');
const link = this.menu.querySelector(`a[href="#${id}"]`);
Array.from(this.menu.querySelectorAll('a')).map(a => a.classList.remove('visible'));
link.classList.add('visible');
history.pushState({}, '', `#${id}`);
}, {threshold: 1});
for (let anchor of this.anchors) {
const item = li.cloneNode();
const link = a.cloneNode();
const id = anchor.getAttribute('id');
link.setAttribute('href', `#${id}`);
link.innerText = id;
link.addEventListener('click', evt => this.clicked(evt));
item.append(link);
this.menu.append(item);
this.observer.observe(anchor);
}
this.render();
}
disconnectedCallback() {
this.observer.disconnect();
}
clicked(evt) {
evt.preventDefault();
const target = evt.target.getAttribute('href');
const elem = document.querySelector(target);
elem.scrollIntoView({behavior: "smooth"});
history.pushState({}, '', `#${target}`);
}
render() {
this.shadow.append(this.tmpl);
}
}
customElements.define('nav-list', NavList);

Use multiple SpeechRecognition() constructors to listen in multiple languages at the same time

I've setup a test script that successfully transcribes what I am saying, both in english and german if I set the lang property beforehand.
However, I want it to automatically recognize which language I am speaking. Since SpeechRecognition does not support this, my idea was to just use multiple constructors at the same time, each with their own lang property and then just use the transcription with the highest confidence score.
However, this does not seem to work, as chrome does not transcribe anything if i start both constructors at once. In fact, I have to close chrome alltogether before it will start working again.
Any ideas? Here is the full code, if I uncomment the commented lines it will not work.
function listenEN(){
navigator.webkitGetUserMedia(
{ audio: true },
() => {},
() => {},
)
let triggerPhrase = "Computer"
triggerPhrase = triggerPhrase ? triggerPhrase.toLowerCase() : "Alexa"
let voices = window.speechSynthesis.getVoices()
const voiceEN = voices.find((voice) => voice.name === 'Google US English')
const voiceDE = voices.find((voice) => voice.name === 'Google DE German')
async function notifyStartListening() {
const utterance = new SpeechSynthesisUtterance("beep!");
utterance.rate = 2;
utterance.pitch = 1.5;
utterance.voice = voiceEN;
speechSynthesis.speak(utterance);
}
const recognitionEN = new webkitSpeechRecognition()
recognitionEN.lang = 'en-US'
recognitionEN.continuous = true
const recognitionDE = new webkitSpeechRecognition()
recognitionDE.lang = 'de-DE'
recognitionDE.continuous = true
try {
let isActive = false
function startListening() {
notifyStartListening();
isActive = true
}
recognitionEN.addEventListener('result', async () => {
recognitionEN.lang = 'en-US'
const transcriptEN = event.results[event.results?.length - 1][0].transcript
console.log(transcriptEN)
if (isActive) {
let instruction = transcriptEN
if (transcriptEN.trimStart().startsWith(triggerPhrase)) {
instruction = transcriptEN.trimStart().substring(triggerPhrase.length)
}
isActive = false
return
}
const trimmed = transcriptEN.trimStart().trimEnd().toLowerCase()
if (trimmed.startsWith(triggerPhrase)) {
const instructionEN = trimmed.substring(triggerPhrase.length)
if (instructionEN && instructionEN?.length > 2) {
let confidenceEN = document.createElement('p')
confidenceEN.innerHTML = event.results[event.results?.length - 1][0].confidence
document.body.appendChild(confidenceEN);
} else {
startListening()
}
}
})
recognitionDE.addEventListener('result', async () => {
recognitionDE.lang = 'de-DE'
const transcriptDE = event.results[event.results?.length - 1][0].transcript
console.log(transcriptDE)
if (isActive) {
let instruction = transcriptDE
if (transcriptDE.trimStart().startsWith(triggerPhrase)) {
instruction = transcriptDE.trimStart().substring(triggerPhrase.length)
}
isActive = false
return
}
const trimmed = transcriptDE.trimStart().trimEnd().toLowerCase()
if (trimmed.startsWith(triggerPhrase)) {
const instructionDE = trimmed.substring(triggerPhrase.length)
if (instructionDE && instructionDE?.length > 2) {
let confidenceDE = document.createElement('p')
confidenceDE.innerHTML = event.results[event.results?.length - 1][0].confidence
document.body.appendChild(confidenceDE);
} else {
startListening()
}
}
})
recognitionEN.addEventListener('error', (event) => {
console.log(event)
})
recognitionEN.onend = function () {
recognitionEN.start()
}
recognitionEN.start()
/*recognitionDE.addEventListener('error', (event) => {
console.log(event)
})
recognitionDE.onend = function () {
recognitionDE.start()
}
recognitionDE.start() */
} catch (e) {
console.error(e)
}
}

How do I iterate over an async function without completely skipping over all the steps in the function?

Here is the link to my repo's github page, so you can properly see what I mean.
I am currently having an issue with my triviaGame function when trying to make it recursive, but it's sort of "backfiring" on me in a sense.
You'll notice after you answer the first question, everything seems fine. It goes to the next question fine. After that though, it seems like the iterations of it double? The next answer it skips 2. After that, 4. And finally the remaining 2 (adding up to 10, due to how I am iterating over them).
How might I be able to correctly iterate over a recursive function, so it correctly calls all 10 times, and then returns when it is done?
Been struggling with this for hours, and just can't seem to get it to work. My javascript code is below, sorry for any headaches that it may give you. I know I make some questionable programming decisions. Ignore some of the commented out stuff, it's not finished code yet. I'm a beginner, and hope that once I learn what's going on here it will stick with me, and I don't make a stupid mistake like this again.
const _URL = "https://opentdb.com/api.php?amount=1&category=27&type=multiple";
const _questionHTML = document.getElementById("question");
const _answerOne = document.getElementById("answer-1");
const _answerTwo = document.getElementById("answer-2");
const _answerThree = document.getElementById("answer-3");
const _answerFour = document.getElementById("answer-4");
const btns = document.querySelectorAll("button[id^=answer-]");
var runCount = 1;
var correct = 0;
// Credits to my friend Jonah for teaching me how to cache data that I get from an API call.
var triviaData = null;
async function getTrivia() {
return fetch("https://opentdb.com/api.php?amount=1&category=27&type=multiple")
.then((res) => res.json())
.then((res) => {
triviaData = res;
return res;
});
}
// anywhere I want the trivia data:
// const trivia = await getTrivia() --- makes the call, or uses the cached data
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
async function triviaGame() {
const trivia = await getTrivia();
async function appendData() {
let totalAnswers = [
...trivia.results[0].incorrect_answers,
trivia.results[0].correct_answer,
];
// Apparently I need 2 different arrays to sort them because array variables are stored by reference? Learn something new everyday I guess.
let totalAnswers2 = [...totalAnswers];
let sorted = shuffleArray(totalAnswers2);
// Ensures the proper symbol shows instead of the HTML entities
const doc = new DOMParser().parseFromString(
trivia.results[0].question,
"text/html"
);
_questionHTML.textContent = doc.documentElement.textContent;
console.log(trivia.results[0].correct_answer, "- Correct Answer");
// Appends info to the DOM
_answerOne.textContent = sorted[0];
_answerTwo.textContent = sorted[1];
_answerThree.textContent = sorted[2];
_answerFour.textContent = sorted[3];
}
async function checkAnswer() {
btns.forEach((btn) => {
btn.addEventListener("click", (event) => {
console.log(runCount);
if (event.target.textContent === trivia.results[0].correct_answer) {
event.target.style.backgroundColor = "#52D452";
// Disables all buttons after one has been clicked.
btns.forEach((btn) => {
btn.disabled = true;
});
setTimeout(() => {
if (runCount === 10) {
return;
}
runCount++;
correct++;
btns.forEach((btn) => {
btn.disabled = false;
});
btn.style.backgroundColor = "";
document.getElementById(
"amount-correct"
).textContent = `${correct}/10`;
triviaGame();
}, 2000);
} else {
event.target.style.backgroundColor = "#FF3D33";
btns.forEach((btn) => {
btn.disabled = true;
});
// document.getElementById("correct-text").textContent =
// trivia.results[0].correct_answer;
// document.getElementById("correct-answer").style.visibility =
// "visible";
setTimeout(() => {
if (runCount === 10) {
return;
}
// document.getElementById("correct-answer").style.visibility =
// "hidden";
btns.forEach((btn) => {
btn.disabled = false;
btn.style.backgroundColor = "";
});
runCount++;
triviaGame();
}, 3500);
}
});
});
}
checkAnswer();
appendData();
}
triviaGame();
Any/All responses are much appreciated and repsected. I could use any help y'all are willing to give me. The past 6 hours have been a living hell for me lol.
It's skipping questions once an answer is clicked because every time a button is clicked, another event listener is added to the button, while the original one is active:
On initial load: triviaGame() runs which makes checkAnswer() run which adds event listeners to each of the buttons.
Event listeners on buttons: 1.
Answer button is clicked, triviaGame() runs which makes checkAnswer() run which adds event listeners to each of the buttons.
Event listeners on buttons: 2.
Answer button is clicked, triviaGame() runs twice (from the 2 listeners attached) which makes checkAnswer() run twice where both invocations adds event listeners to each of the buttons.
Event listeners on buttons: 4.
etc.
To fix this, I moved the content of checkAnswer() outside of any functions so it only ever runs once. However, doing this, it loses reference to the upper scope variable trivia. To resolve this, I used the triviaData variable instead which checkAnswer() would have access to and I change references in appendData() to match this. Now, triviaGame() function only exists to call appendData() function inside it; there is little point in this so I merge the two functions together into one function, instead of two nested inside each other.
const _URL = "https://opentdb.com/api.php?amount=1&category=27&type=multiple";
const _questionHTML = document.getElementById("question");
const _answerOne = document.getElementById("answer-1");
const _answerTwo = document.getElementById("answer-2");
const _answerThree = document.getElementById("answer-3");
const _answerFour = document.getElementById("answer-4");
const btns = document.querySelectorAll("button[id^=answer-]");
var runCount = 1;
var correct = 0;
// Credits to my friend Jonah for teaching me how to cache data that I get from an API call.
var triviaData = null;
async function getTrivia() {
return fetch("https://opentdb.com/api.php?amount=1&category=27&type=multiple")
.then((res) => res.json())
.then((res) => {
triviaData = res;
return res;
});
}
// anywhere I want the trivia data:
// const trivia = await getTrivia() --- makes the call, or uses the cached data
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
async function appendData() {
triviaData = await getTrivia();
let totalAnswers = [
...triviaData.results[0].incorrect_answers,
triviaData.results[0].correct_answer,
];
// Apparently I need 2 different arrays to sort them because array variables are stored by reference? Learn something new everyday I guess.
let totalAnswers2 = [...totalAnswers];
let sorted = shuffleArray(totalAnswers2);
// Ensures the proper symbol shows instead of the HTML entities
const doc = new DOMParser().parseFromString(
triviaData.results[0].question,
"text/html"
);
_questionHTML.textContent = doc.documentElement.textContent;
console.log(triviaData.results[0].correct_answer, "- Correct Answer");
// Appends info to the DOM
_answerOne.textContent = sorted[0];
_answerTwo.textContent = sorted[1];
_answerThree.textContent = sorted[2];
_answerFour.textContent = sorted[3];
}
btns.forEach((btn) => {
btn.addEventListener("click", (event) => {
console.log(runCount);
if (event.target.textContent === triviaData.results[0].correct_answer) {
event.target.style.backgroundColor = "#52D452";
// Disables all buttons after one has been clicked.
btns.forEach((btn) => {
btn.disabled = true;
});
setTimeout(() => {
if (runCount === 10) {
return;
}
runCount++;
correct++;
btns.forEach((btn) => {
btn.disabled = false;
});
btn.style.backgroundColor = "";
document.getElementById(
"amount-correct"
).textContent = `${correct}/10`;
appendData();
}, 2000);
} else {
event.target.style.backgroundColor = "#FF3D33";
btns.forEach((btn) => {
btn.disabled = true;
});
// document.getElementById("correct-text").textContent =
// trivia.results[0].correct_answer;
// document.getElementById("correct-answer").style.visibility =
// "visible";
setTimeout(() => {
if (runCount === 10) {
return;
}
// document.getElementById("correct-answer").style.visibility =
// "hidden";
btns.forEach((btn) => {
btn.disabled = false;
btn.style.backgroundColor = "";
});
runCount++;
appendData();
}, 3500);
}
});
});
appendData();
<div id="amount-correct"></div>
<h1 id="question"></h1>
<button id="answer-1"></button>
<button id="answer-2"></button>
<button id="answer-3"></button>
<button id="answer-4"></button>

How do I combine selector and tab functions?

I would like to shorten this code, but can't figure out how.
The code works in the way that when you press the button in the selector, a map point and a text on the bottom of the map appear. It works in this way it is, but I am sure that there is a way to shorten it. I just have not enough knowledge on how to shorten it.
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.select__item').forEach( function(tabBtn) {
tabBtn.addEventListener('click', function(event) {
const path = event.currentTarget.dataset.path
document.querySelectorAll('.sketch__item',).forEach( function(tabContent) {
tabContent.classList.remove('block-active')
})
document.querySelectorAll('.details__item',).forEach( function(tabContent) {
tabContent.classList.remove('block-active')
})
document.querySelectorAll(`[data-target="${path}"]`).forEach( function(tabsTarget) {
tabsTarget.classList.add('block-active')
})
})
})
//*** tabs active
let tabsChange = document.querySelectorAll('.select__item')
tabsChange.forEach(button => {
button.addEventListener('click', function () {
tabsChange.forEach(btn => btn.classList.remove('active__tab'))
this.classList.add('active__tab')
})
})
})
let select = function () {
let selectHeader = document.querySelectorAll('.select__header');
let selectItem = document.querySelectorAll('.select__item');
selectHeader.forEach(item => {
item.addEventListener('click', selectToggle)
});
selectItem.forEach(item => {
item.addEventListener('click', selectChoose)
});
function selectToggle() {
this.parentElement.classList.toggle('is-active');
}
function selectChoose() {
let text = this.innerText,
select = this.closest('.partner__select'),
currentText = select.querySelector('.select__current');
currentText.innerText = text;
select.classList.remove('is-active');
}
};
//*** Tabs
select();
Delegation shortens the code.
If you delegate, you shorten the code. Never loop eventlisteners in a container. Use the container instead
I lost 20 lines and made code easier to debug
NOTE: I did not have your HTML so I may have created some errors or logic issues you will need to tackle
const selectChoose = e => {
const tgt = e.target;
let text = tgt.innerText,
select = tgt.closest('.partner__select'),
currentText = select.querySelector('.select__current');
currentText.innerText = text;
select.classList.remove('is-active');
};
const selectToggle = e => e.target.parentElement.classList.toggle('is-active');
window.addEventListener('load', function() {
const container = document.getElementById('container');
container.addEventListener('click', e => {
const tgt = e.target.closest('.select');
if (tgt) {
const path = tgt.dataset.path;
document.querySelectorAll('.item', ).forEach(tabContent => tabContent.classList.remove('block-active'))
document.querySelectorAll(`[data-target="${path}"]`).forEach(tabsTarget => tabsTarget.classList.add('block-active'))
}
})
const tabContainer = document.getElementById('tabContainer');
//*** tabs active
tabContainer.addEventListener('click', e => {
const tgt = e.target.closest('button');
if (tgt) {
tabContainer.querySelectorAll('.active__tab').forEach(tab => tabclassList.remove('active__tab'))
tgt.classList.add('active__tab')
}
}) const selContainer = document.getElementById('selectContainer');
selContainer.addEventListener('click', e => {
const tgt = e.target;
if (tgt.classList.contains('select__header')) selectToggle(e);
else if (tgt.classList.contains('select__item')) selectChoose(e)
})
})

Add/Remove classes to body on scroll

I have a function that adds a class current to a menu item when its corresponding section comes into view. How would I go about also adding/removing different classes to body as well, based on currently visible section?
Edit: Per suggestion got it working finally with Intersection Observer but still trying to figure out how to add and swap classes to body:
function setColorScheme() {
const nav = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting && entry.intersectionRatio >= 0.55) {
document.querySelector('li.current').classList.remove('current');
var id = entry.target.getAttribute('id');
var newLink = document.querySelector(`[href$='#${id}']`).parentElement.classList.add('current');
//returning error
var newClass = $('body.home').classList.add('.' + id);
}
});
}
const options = {
threshold: 0.55,
rootMargin: '150px 0px'
};
const observer = new IntersectionObserver(nav,options);
const sections = document.querySelectorAll('section.op-section');
sections.forEach((section) => {observer.observe(section);});
}

Categories