MrCycling
MrCycling

Reputation: 3004

javascript trigger event when div at top of parent div

I am wanting to trigger an event with vanilla javascript when an ID reaches the top of the parent div. Have seen examples of doing such with jQuery, but I don't want to load that much for a simple effect. Plus most examples deal with the top of the viewport, whereas I have a main div that is about 90% of the viewport height, with an info div above.

I am working with a long webpage, with several sections, that scrolls within the main div. When the title of each section <h2 id="title1">bla bla bla</h2> reaches the top of the main div, I want to trigger a simple show/hide of the title <div id="info1">bla bla bla</div> shown in the info div. The show / hide is simple enough:

 function chngInfo(x) {
    const targets = document.querySelectorAll('[id^="info"]');
    for (let i = targets.length; i--;) {
      targets[i].style.display = 'none';
    }
    document.getElementById('info' + x).style.display = 'block';
  }

It is triggering the function that has got me stymied. One further complication is that the height of the main div varies depending on device, so can't easily do a formula based on viewport height.

It would be major bonus if a single function could look for any titleX as it passes the top while scrolling down or passes the bottom while scrolling up.

Upvotes: 1

Views: 2475

Answers (3)

elrrrrrrr
elrrrrrrr

Reputation: 944

Not very clear about the needs. I think you could try

  1. getBoundingClientRect to decide show which content
  2. detect scroll stop event, FYI link

Hope it helps, thanks.

function entry() {
  // 1. detect the main bound
  const mainRect = document.querySelector('#main').getBoundingClientRect();
  console.log('main', mainRect.y);

  // 2. get all the title
  const titles = document.querySelectorAll('h2');
  // hideAll
  titles.forEach(t => {
    t.nextElementSibling.style.display = 'none';
  });
  const first = Array.from(titles).find((t, i) => {
    const rect = t.getBoundingClientRect();
    rect.y > 0 && console.log('show', i);
    return rect.y > 0;
  });
  window.t = first;
  first.nextElementSibling.style.display = 'block';

}

var timer = null;
window.addEventListener('scroll', function() {
    if(timer !== null) {
        clearTimeout(timer);
    }
    timer = setTimeout(entry, 150);
}, false);


// init show;
entry();
<html>
<style>
  #main {
    background: green;
    height: 1800px;
  }
  #info1 {
    display: none;
  }
  h2 {
    height: 150px;
  }
  div {
    height: 500px;
    background: red;
  }
</style>
  <body>
    <div id="main">
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    <h2 id="title1">bla bla bla</h2>
    <div id="info1">bla bla bla</div>
    </div>
  </body>
</html>

Upvotes: 1

volt
volt

Reputation: 1003

I use a scroll event listener in combination with a bit of throttling to avoid some of the performance pitfalls of listening to scroll events.

If you run the snippet below, you'll notice that it provides you with a hook where you can call some logic that only gets called once when the element reaches the top of the viewport. If the user scroll back up, it will reset.

I also left some comments in the code in case it helps.

// from 
// @peduarte 
// https://gist.github.com/peduarte/969217eac456538789e8fac8f45143b4
function throttle(func, wait = 100) {
  let timer = null;
  return function(...args) {
    if (timer === null) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
      }, wait);
    }
  };
}

// throttle scroll by this much (ms)
const SCROLL_THROTTLE = 200;

// get our elements
const wrapper = document.querySelector('.wrapper');
const target = document.querySelector('.this-one');

// reserve this so we can overwrite it later
let reachedTop;

// function that fires when we scroll
const checkOffset = () => {
  // get out offsets
  const wrapperOffset = wrapper.scrollTop + wrapper.offsetTop;
  const targetOffset = target.offsetTop;
  // try to do some work
  if (wrapperOffset >= targetOffset && !reachedTop) {
    reachedTop = true;
    // element reached top do more work...
    console.log(reachedTop);
  } else if (wrapperOffset <= targetOffset && reachedTop) {
    reachedTop = false;
    // user scrolled up and element longer at top...
    console.log(reachedTop);
  }
}

// throttle our function
const throttledScroll = throttle(checkOffset, SCROLL_THROTTLE);

// call the throttled function when we scroll
wrapper.addEventListener('scroll', throttledScroll);
.this-one {
  color: blue;
}

.wrapper {
  border: 1px solid red;
  height: 50vh;
  overflow: auto;
  margin-top: 2em;
  padding: 2em;
  display: inline-block;
}
<div class="wrapper">

  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p class="this-one">Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>
  <p>Example</p>

</div>

Upvotes: 2

Ayman Morsy
Ayman Morsy

Reputation: 1419

I think There Two way to achieve that :

  • first: using scroll event with offsetTop , scrollY properties to get the point you need to reach ... but it have some performance issues :

var myTarget = document.querySelector('.target')

window.addEventListener('scroll', function() {
  if(myTarget.offsetTop - window.scrollY <= 0){
    myTarget.style.color = "red"
  }
})
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1 class='target'>Target elemnt example</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>
<h1>hello</h1>

  • second: using intersection Observer

this link have good explanation and examples : https://webdesign.tutsplus.com/tutorials/how-to-intersection-observer--cms-30250

Upvotes: 2

Related Questions