Do something when element passes under fixed header - javascript

I'm trying to do something that I thought was relatively simple, but is apparently not. I want to do something when one (of many) elements passes underneath another. In my case, I'm going to do something when any .section passes underneath #fixed-header. The problem is that it still appears to be detecting when the element enters the viewport, not when it intersects with the header.
I suspect I'm misunderstanding some part of how intersectionObserver works with fixed elements… but I just can't sort it out.
const sections = document.querySelectorAll('.section');
const header = document.getElementById('#fixed-header');
const options = {
root: header,
rootMargin: "0px",
threshold: [0]
};
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Do some stuff when this happens
console.log(`${entry.target.textContent} has passed under the header`);
}
});
}, options);
sections.forEach(section => {
observer.observe(section);
});
<header id="fixed-header">Header</header>
<div class="sections">
<div class="section">Section 1</div>
<div class="section">Section 2</div>
<div class="section">Section 3</div>
<div class="section">Section 4</div>
<div class="section">Section 5</div>
</div>
#fixed-header {
height: 100px;
position: fixed;
top: 0;
width: 100%;
background-color: lightgray;
}
.section {
min-height: 500px;
border: 1px solid red;
}
.sections {
margin-top: 100px;
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
I know there are a million questions on this, and yes, I've looked at many of them, but I just can't get this working. Thanks!

Related

How to make an invisible clickable area that would toggle scrolling (JavaScript)?

I'm making a small website built entirely with flexbox columns and rows. One of the rows is horizontally scrollable — howewer, I want it to be scrolled not only with scrolling, but also by clicking either on left or right half of the visible part of the row. Plus, hovering on the left side should change cursor to cursor: w-resize and on the left side — to cursor: e-resize.
see screenshot
see a sketch of what i'm trying to achieve
<div class="project">
<div class="project-images">
<div id="project-image"><img id="image-standart-height" style="max-height: 400px" src="/Users/andrei/Desktop/All Work/portfolio and cv/my website/images/bahorvesna.png" alt=""></div>
<div id="project-image"><img id="image-standart-height" style="max-height: 400px" src="/Users/andrei/Desktop/All Work/portfolio and cv/my website/images/bahorvesna.png" alt=""></div>
<div id="project-image"><img id="image-standart-height" style="max-height: 400px" src="/Users/andrei/Desktop/All Work/portfolio and cv/my website/images/bahorvesna.png" alt=""></div>
<div id="project-image"><img id="image-standart-height" style="max-height: 400px" src="/Users/andrei/Desktop/All Work/portfolio and cv/my website/images/bahorvesna.png" alt=""></div>
</div>
<div class="project-text">
Bahor/Vesna
</div>
</div>
#project-image {
flex: 0 0 auto;
max-height: 400px;
}
#image-standart-height {
max-height: 400px;
}
.project-text {
display: flex;
gap: 20px;
align-items: baseline;
}
I was thinking of creating an invisible layer on a different z-axis level, but I assume it would be difficult not to ruin the flexbox.
Is it possible to make a function in JS to detect when a mouse is entering a certain area of the visible part of the div, change cursor and make the area clickable?
You can also do it without adding any extra div's, by checking the position of the mouse pointer relative to the container. Try it out by clicking the left and right half of the box in the example.
If you have multiple elements, all you need to do is loop the set of elements and put the two event listeners inside the loop.
const containers = document.querySelectorAll('.container');
containers.forEach((container) => {
container.addEventListener('click', (e) => {
const clientRect = e.currentTarget.getBoundingClientRect();
if ((e.pageX - clientRect.left) < clientRect.width / 2)
console.log('Clicked left half');
else
console.log('Clicked right half');
});
container.addEventListener('mousemove', (e) => {
const clientRect = e.currentTarget.getBoundingClientRect();
if ((e.pageX - clientRect.left) < clientRect.width / 2)
container.style.cursor = 'w-resize';
else
container.style.cursor = 'e-resize';
});
});
.container {
width: 300px;
height: 100px;
border: 1px solid #000;
margin: 50px auto;
}
<div class="container"></div>
<div class="container"></div>
<div class="container"></div>
<div class="container"></div>
<div class="container"></div>

css scroll-snap: focusing on the element which got snapped to

I implemented a horizontal grid with some cards in them. The grid uses CSS scroll-snap and it works nicely when navigating with mouse/touchscreen.
The problem occurs when the grid is navigated using a keyboard. Pressing tab after navigating through the grid with arrow keys causes the view to jump back to the element that got the focus, not the card which is current snapped to.
My ideal behaviour when pressing tab is, to focus on the card which is currently snapped to.
Any suggestions to make this possible?
As far as I can tell there is currently no way to handle this natively. Nils Schwebel's answer is not going to be very elegant, but it looks like the best way to go.
Here's a working example:
Note: I've added quite a bit of pure decoration to make it easier to understand, so you may need to pick out the relevant parts after some testing.
const main = document.getElementById("Main"),
sections = document.getElementsByClassName("section");
main.addEventListener('scroll', (e) => {
// Grab the position yo are scrolled to (the top of the viewport)
let pos = main.scrollTop;
for (let i = 0, l = sections.length; i < l; i++) {
// Since our stap-align is centered, get the position of the middle of the viewport relative to the current section's top (if your snap items are not full-height, it might require using half the viewport's height instead)
let relativePos = sections[i].offsetTop - pos + (sections[i].offsetHeight / 2);
// Check if the point we found falls within the section
if (relativePos >= 0 && relativePos < sections[i].offsetHeight) {
sections[i].focus();
break;
}
}
});
body {
margin: unset;
}
main {
display: flex;
flex-wrap: wrap;
align-items: stretch;
justify-content: center;
width: 100%;
height: 100vh;
background-color: #222;
overflow-y: auto;
scroll-snap-type: y mandatory;
}
section {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
scroll-snap-align: center;
}
section:focus {
border: none;
outline: none;
}
#s1 {
background-color: #d72748;
}
#s2 {
background-color: #b51f7e;
}
#s3 {
background-color: #e64869;
}
#s4 {
background-color: #e79946;
}
section h2 {
color: white;
}
<main id="Main">
<section class="section" id="s1" tabindex="1" aria-labelledby="a1">
<h2 id="a1">AREA 1</h2>
</section>
<section class="section" id="s2" tabindex="1" aria-labelledby="a3">
<h2 id="a2">AREA 2</h2>
</section>
<section class="section" id="s3" tabindex="1" aria-labelledby="a2">
<h2 id="a3">AREA 3</h2>
</section>
<section class="section" id="s4" tabindex="1" aria-labelledby="a4">
<h2 id="a4">AREA 4</h2>
</section>
</main>
I would add a scroll listener and just check if the element is at the top of the scroll view. You may be able to modify one of these solutions: How to check if element is visible after scrolling?

locomotive-scroll toggle header onscroll

I'm trying out locomotive-scroll for the first time and it's been good so far until I had to try to toggle the header style onscroll because the eventlistener isn't working. I've looked into gsap scrolltrigger and intersection observer but I'm having a difficult time figuring things out because I have little knowledge on the area.
How can I make this work / do something like this on locomotive scroll?
let header = document.querySelector("header");
function toggleHeader(ev) {
if (window.pageYOffset > 300) {
header.classList.add("header--active");
} else {
header.classList.remove("header--active");
}
}
window.addEventListener('scroll', function() {
toggleHeader();
});
HTML/CSS/JS
<div data-scroll-container=''>
<div class='header'>
Header
</div>
<section data-scroll-section=''></section>
<section data-scroll-section=''></section>
</div>
.header {
height: 80px;
background: #f00;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
position: fixed;
width: 100%;
left: 0;
top: 0;
transition: ease 0.4s;
}
.header--active {
height: 40px;
background-color: #000;
}
section {
height: 500px;
border-bottom: 1px solid #f00;
}
const scroller = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true
})
a functional sample:
var el = document.getElementById("test");
var wheel = "nothing";
var scroll = new LocomotiveScroll({
el: el,
smooth: true,
repeat: true,
getDirection: true
});
scroll.on("call", (fun, dir, obj) => {
console.log("call", fun, dir);
});
scroll.on("scroll", (obj) => {
if (obj.direction == wheel) return;
wheel = obj.direction;
console.log("scroll--", obj.direction);
console.log(obj.currentElements['0'],obj.currentElements['1'], Object.keys(obj.currentElements));
});
<script src="https://cdn.jsdelivr.net/npm/locomotive-scroll#4.1.0/dist/locomotive-scroll.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/locomotive-scroll#4.1.0/dist/locomotive-scroll.min.css">
<div id="test" data-scroll-container=''>
<div class='header'>
Header
</div>
<section data-scroll-section=''>
<div data-toggle data-toggle-call="text1">
<h2 class="intro__title" data-scroll data-scroll-call="text0">If I scroll to the second intro section (AND the first intro is in the viewport) it will change to a black background. But if I scroll up it will not change to a white background as the section did not get out from the viewport and the last call
was with a black background.</h2>
<p>You have to completely pass through a section to be able to reverse back the colors.<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></p>
</div>
<div data-toggle data-toggle-call="text2">
<h2 class="intro__title" data-scroll data-scroll-call="text1">If I scroll to the second intro section (AND the first intro is in the viewport) it will change to a black background. But if I scroll up it will not change to a white background as the section did not get out from the viewport and the last call
was with a black background.</h2>
<p>You have to completely pass through a section to be able to reverse back the colors.<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></p>
</div>
</section>
</div>

Anchor to overflow auto (scrollable) content

I am building a comment system, I want the user to be able to be redirected directly to the comment (just like stack overflow comment notification) from anywhere else.
Here's what I got so far. (the demo below doesn't display well, please refer to codepen)
const comment_box = document.getElementById('comment-box');
const fourth = document.getElementById('btn4');
const seventh = document.getElementById('btn7');
const tenth = document.getElementById('btn10');
function comment_jump(id){
var el = document.getElementById(id);
// getBoundingClientRect: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
// top (of this comment refer to parent) = top (of this comment refer to page) - top (of container refer to page)
var tp = el.getBoundingClientRect().top - comment_box.offsetTop;
comment_box.scrollTo(0, tp);
}
fourth.addEventListener('click', ()=>{
comment_jump('comment4');
});
seventh.addEventListener('click', ()=>{
comment_jump('comment7');
});
tenth.addEventListener('click', ()=>{
comment_jump('comment10');
});
body{
background: lightgrey;
}
#comment-box{
width: 500px;
height: 300px;
background: grey;
overflow: auto;
}
.comment{
width: 100%;
height: 33.3%;
border: 1px solid;
font-size: 2em;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
button{
font-size: 1.5em;
margin: 2em;
}
button:hover{
opacity: .5;
cursor: pointer;
}
<div id="comment-box">
<div class="comment"></div>
<div class="comment"></div>
<div class="comment"></div>
<div id="comment4" class="comment">4th</div>
<div class="comment"></div>
<div class="comment"></div>
<div id="comment7" class="comment">7th</div>
<div class="comment"></div>
<div class="comment"></div>
<div id="comment10" class="comment">10th</div>
<div class="comment"></div>
<div class="comment"></div>
</div>
<button type="button" id="btn4">4th</button>
<button type="button" id="btn7">7th</button>
<button type="button" id="btn10">10th</button>
The expected output is that when I click 4th, 7th, 10th, it jump to corresponding area.
I will be so glad if someone suggest which part I went wrong.
Might I suggest trying out scrollIntoView as a possible solution?
function comment_jump(id){
var el = document.getElementById(id);
el.scrollIntoView();
}
From Mozilla's docs:
scrollIntoView() method scrolls the element's parent container such that the element on which scrollIntoView() is called is visible to the user
So it will scroll the comment container for you to the desired element.

Javascript scrollIntoView only in immediate parent

How do I make scrollIntoView only scroll the immediate parent (e.g. div with overflow-y: scroll;) and not the whole page?
I have a web interface I'm making for an internal, very-specific purpose. Among other elements on my page is a div with a specified height and overflow-y is scroll.
I have data which will periodically appear in this area and I want it to always be scrolled to the bottom (e.g. the console output of a subprocess on some remote server).
If I use scrollIntoView, it scrolls the overflow-y div..... but also scrolls the whole page.
On a computer with a large monitor, this isn't an issue, but on my laptop with a smaller screen it also scrolls the whole window, which is definitely not the intended/desired behavior.
I think what you might be looking for is
.scrollIntoView({block: "nearest", inline: "nearest"});
Where supported (basically anywhere except IE and SafarIE) this will do the 'least' movement to show the element; so if the outer container is visible, but the target element is hidden inside that container -- then it should scroll the inner container but not the page.
I think you're looking for a combination of scrollTop and scrollHeight. You can use the first to set where you want the div to scroll to, and the second to get the height of all the info in the div:
var scrollyDiv = document.getElementById("container");
scrollyDiv.scrollTop = scrollyDiv.scrollHeight
setInterval(() => {
var textnode = document.createElement("P");
textnode.innerHTML = "Whatever"
scrollyDiv.appendChild(textnode);
scrollyDiv.scrollTop = scrollyDiv.scrollHeight
}, 1000)
#container {
height: 200px;
overflow-y: scroll;
}
#big-content {
height: 400px;
background-color: green;
}
<div id="container">
<div id="big-content"></div>
<p>The bottom</p>
</div>
I tried to reproduce your case and I think that scrollIntoView() will not work as you wish. Try to use scrollTop instead.
Hope it will save your time.
const btn = document.getElementById('js-scroll');
btn.addEventListener('click', () => {
const targets = document.querySelectorAll('.scrollable');
targets.forEach(t => {
// Scroll each item
t.scrollTop = t.scrollHeight;
});
});
.scroll-btn {
position:fixed;
left: 10px;
top: 10px;
padding: 10px;
font-weight: 700;
font-size: 16px;
}
.content {
min-height: 100vh;
background-color: #efefef;
padding: 35px 10px;
}
.scrollable {
margin: 15px; 5px;
padding: 5px;
background-color: #fafafa;
height: 500px;
overflow-y: scroll;
overflow-x: hidden;
}
.scrollable-data {
padding: 10px;
margin-bottom: 1px;
min-height: 600px;
background-color: #55b7ab;
}
<button id="js-scroll" class="scroll-btn">Scroll</button>
<section class="content">
<div class="scrollable">
<div class="scrollable-data">Some data 1</div>
<div class="scrollable-data">Some data 1</div>
</div>
<div class="scrollable">
<div class="scrollable-data">Some data 2</div>
<div class="scrollable-data">Some data 2</div>
</div>
<div class="scrollable">
<div class="scrollable-data">Some data 3</div>
<div class="scrollable-data">Some data 3</div>
</div>
</section>
Solution for React 16
Here is an answer for this problem using React 16, for people who want to have a scrollable element scroll to its bottom upon some event. I was having the problem that scrollIntoView() was causing the whole window to adjust, which was undesirable; just need the scrolling element to scroll to the bottom (like a terminal) on demand.
Add a ref to the element with overflowY: "scroll", and use the formula this.body.current.scrollTo(0, this.body.current.scrollHeight) like so:
constructor() {
...
this.body = React.createRef() // Create a ref to your scrollable element
}
...
componentDidUpdate(prevProps, prevState) { // or whatever action you want
// The Important Part, where you scroll to the y-coord
// that is the total height, aka the bottom.
// .current is important, as you want the version of
// that ref that is rendered right now.
this.body.current.scrollTo(0, this.body.current.scrollHeight)
...
}
...
render() {
return (
<div style={style.container}>
<div ref={this.body} style={style.body}> // React ref tagged here
....
</div>
</div>
}

Categories