I'm working on a react project and I want to highlight the sidebar nav list when the corresponding section is visible while scrolling, and I used useEffect and IntersectionObserver for that and add an active class to the sidebar nav item with the code below.
The problem is that some of the sections are not 100% height of the viewport, causing multiple sidebar nav list items to highlight simultaneously and I do not want that. I want only a single nav item to have the active class.
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const id = entry.target.getAttribute('id');
if (entry.isIntersecting) {
document
.querySelector(`.sidebarList li a[href="#${id}"]`)
.classList.add('active');
} else {
document
.querySelector(`.sidebarList li a[href="#${id}"]`)
.classList.remove('active');
}
});
});
document.querySelectorAll('section[id]').forEach((section) => {
observer.observe(section);
});
return () => observer.disconnect();
});
I think you will have to use useRef instead of using your querySelector.
Related
So here is the code
What is wrong with it?
it should trigger a block if I click on this block (burger) the nav-active function should be triggered.
Here is the JS part:
const navSlide = () => {
const burger = document.querySelector('.burger');
const nav = document.querySelectorAll('.nav-links');
burger.addEventListener('click',()=>{
nav.classList.toggle('nav-active');
});
}
navSlide();
My CSS
.nav-active {
transform: translate(0%);
}
I added the javascript file at the end of my html file:
<script src="../static/java_file.js"></script>
Since document.querySelectorAll() returns a NodeList object, you need to iterate every nav link using .forEach() or using a loop like for:
const navSlide = () => {
const burger = document.querySelector('.burger');
const nav = document.querySelectorAll('.nav-links');
burger.addEventListener('click',() => {
// nav is a collection, so we use .forEach()
nav.forEach(n => n.classList.toggle('nav-active'));
});
}
navSlide();
Small tipps: Use your in-browser developer tools (opened usually by pressing F12) to check for the exact exception messages. Also: don't call your javascript-file 'java_file.js'.
I know there are a few similar questions but I have waded through them without any luck.
My event listeners (both "click") work great, but only once, after a page refresh. It is just code to slide the hidden nav menu out for a mobile device screen.
Here is the JS code:
/*Menu responsive code*/
const hamburger = document.getElementById('menuIcon');
const closeMenu = document.getElementById('closeNav');
const navUL = document.getElementById('navUL');
hamburger.addEventListener('click', () =>{
navUL.classList.toggle('show');
});
closeMenu.addEventListener('click', () =>{
navUL.classList.toggle('hidden');
})
Again, this problem has to do with the javascript, not the CSS/HTML since it works great that one time (unless I am completely wrong). Also new to javascript.
hamburger and closeMenu are my ionicons used/the buttons. the show and hidden classes just translateX between 0% and 100%.
In your case with 2 classes you should remove the other class when you are adding the new one.
if you are using only one class you can use toogle but with 2 classes yo have to use add and remove together.
exemple here :
const hamburger = document.getElementById('menuIcon');
const closeMenu = document.getElementById('closeNav');
const navUL = document.getElementById('navUL');
hamburger.addEventListener('click', () =>{
navUL.classList.add('red');
navUL.classList.remove('blue');
});
closeMenu.addEventListener('click', () =>{
navUL.classList.add('blue');
navUL.classList.remove('red');
})
https://codepen.io/pen/?editors=1111
So, I have a little project that showcases a set of images, small-size. And when you click on one of them, it expands. Basically, JavaScript adds an HTML class of "active" to an element, which transforms it.
const panels = document.querySelectorAll('.panel');
/*adding an event for every image panel that makes
panel active on click */
panels.forEach(panel => {
panel.addEventListener('click', () => {
removeActiveClasses()
panel.classList.add('active')
})
})
//a function that removes active class from a panel
function removeActiveClasses() {
panels.forEach(panel => {
panel.classList.remove('active')
})
}
So, when you click on the element, it removes all the .active classes from every other one, and then adds it to a target element. It works perfectly, but I want to be able to delete a class from an element that is currently active, so when I click on an expanded picture, it collapses back. Changing panel.classList.add to panel.classList.toggle obviously doesn't work, because it first removes all active classes and then "toggles" it (or adds, because there is none). How can I delete a class from active element on click, while remaining the other functionality?
Solution: Thanks to CBroe I've managed to make it work. The code now looks like the following:
const panels = document.querySelectorAll('.panel');
/*adding an event for every image panel that makes
panel active on click */
panels.forEach(panel => {
panel.addEventListener('click', () => {
const isActive = panel.classList.contains('active')
removeActiveClasses()
panel.classList.toggle('active', !isActive)
})
})
//a function that removes active class from a panel
function removeActiveClasses() {
panels.forEach(panel => {
panel.classList.remove('active')
})
}
A much more efficient way would be to :
document.getElementsByClassName('panel').addEventListener('click', function() {
for (let ele of panels) {
ele.classList.remove('active')
}
let panel = this
panel.classList.includes('active') ? panel.classList.remove('active') : panel.classList.add('active')
)
I have an Infinite Scroll with React.js
It is literally Infinite, so I can't measure How many is it.
<div className="InfiniteScroll">
<div ref={observer} className="item">item</div>
<div ref={observer2} className="item">item</div>
{/* Actually I am using map() */}
{...}
</div>
I can add IntersectionObserver on any div.
const observer = new IntersectionObserver(() => {
console.log('Found it');
},{
root: divRef.current,
rootMargin: ...,
});
When I want to know the currently visible divs, How can I know that with this?
Add IntersectionObserver on each div looks not reasonable.
If I want to invoke some function with 1,000th div or random div, how can I achieve it?
you can set any div with code
new IntersectionObserver(function(div) {
div.isIntersecting && do_something();
}).observe(div);
I would suggest you attach a ref to the very last div. So when that last div is intersecting, then you can invoke your function there.
Attaching ref to the last div
{list.map((item, index) => {
if (item.length === index + 1) {
return <div ref={lastItemElementRef} key={item}>{item}</div>
} else {
return <div key={item}>{item}</div>
}
})}
Now when you reach this last div you can have an external API call for infinite scroll and invoke some function
Intersecting logic
const observer = useRef()
const lastItemElementRef = useCallback(node => {
if (loading) return
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
// logic for triggering set state and API call - Infinite scroll
// some other function invokation
}
})
if (node) observer.current.observe(node)
}, [loading, otherdependencies])
1) I am trying to Auto scroll to the next item in listgroup. For example if user answer the first question it should auto scroll to the second question. (React) and onSubmit it should scroll to the first not answered question
2) When user view this list in mobile view the YES or NO Radio button should display in center and also SUBMIT AND CLEAR BUTTON (BOOTSTRAP)
3) How to know which item is selected from the drop down and display it in console.
Code
There are a number of ways this can be achieved. One way would be to add a method that scrolls to an item in your form, via "vanilla js", and then use that in both your onInputChanged on onSubmut methods.
You could defined this function in your component as:
// Scrolls the list to a list item by list item index
scrollToItemByIndex = (index) => {
// Find the list item element (by index), and scroll wrapper element
const scrollItem = document.querySelector(`[scrollIndex="${ (index) }"]`)
const scrollWrapper = document.querySelector(`[scrollWrapper="scrollWrapper"]`)
if(scrollItem && scrollWrapper) {
// If list item found in DOM, get the top offset
const itemRect = scrollItem.offsetTop // [UPDATED]
const wrapperRect = scrollWrapper.offsetTop // [UPDATED]
// Scroll the wrapper to the offset of the list item we're scrolling to
scrollWrapper.scrollTo(0, itemRect - wrapperRect)
}
}
You onInputChange function could then be updated as follows:
onInputChange = ({ target }) => {
const { cards } = this.state;
const { options } = this.state;
const nexState = cards.map((card, index) => {
if (card.cardName !== target.name) return card;
const options = card.options.map(opt => {
const checked = opt.radioName === target.value;
return {
...opt,
selected: checked
}
})
// [ADD] When input changes (ie something is set), scroll to next item
this.scrollToItemByIndex( index + 1 )
const style = options.every(option => !option.selected) ? 'danger' : 'info'
return {
...card,
style,
options
}
});
this.setState({ cards: nexState })
}
Also, your onSubmit would be updated to scroll to any form items that are not valid:
onSubmit = () => {
this.state.cards.forEach((card, index) => {
var invalid = card.options.every(option => !option.selected)
if (invalid) {
card.style = 'danger'
// [ADD] this item has invalid input, so scroll to it
this.scrollToItemByIndex(index)
}
else {
card.style = 'info'
}
});
...
}
Finally, you'd need to update your component's render method with the following, to ensure that the query selectors above function correctly:
<ul class="nav nav-pills nav-stacked anyClass" scrollWrapper="scrollWrapper">
and:
{cards.map((card, idx) => (<ListGroup bsStyle="custom" scrollIndex={idx}>
...
</ ListGroup >)}
[UPDATED] A full working sample can be found here:
https://stackblitz.com/edit/react-z7nhgd?file=index.js
Hope this helps!