Scrollspy - Getting element to appear on enter and exit

Hi there,

I’ve created some test content using the Scrollspy plugin which detects when the user “enters” or “exits” visibility of an element. So far I’ve got it to display correctly when you scroll back up past the element, but results are flakey when you try to scroll up past the element and then scroll back down again. What’s doubly strange is that the example works absolutely fine on Codepen but doesn’t during local testing or when I upload to a test area on my webspace.

https://codepen.io/Shoxt3r/pen/LYEZWxR

https://andrewcourtney.co.uk/tester/wowjsblocks/

The expected behaviour is that the element should hide and reappear no matter which way you scroll. As per the console, on the Codepen example the console logs are correctly, while the webspace version doesn’t display an “EXIT” message when you scroll to the top again.

Thoughts please?

Many thanks!

What happens if you use valid well constructed html?

Answer: It starts to work:)

3 Likes

Hey look at that, it works haha.
Always good to get a second pair of eyes to look over something sometimes!!

Would there be a better way to do this type of scroll test without a plugin like Scrollspy? Just trying to optimise it as much as possible…

You’ll need one of the jS experts here to help but I believe you could use the following to do what you want more efficiently.

A very rough demo but any more customisation is beyond my skills :slight_smile:

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Untitled Document</title>
<style>
html {
	box-sizing: border-box;
}
*, *:before, *:after {
	box-sizing: inherit;
}
body {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
html, body {
	margin: 0;
	padding:0;
}
.content-container {
	position: relative;
	padding: 3rem 0;
	min-height: 100vh;
	background: #cfdde8;
	display:flex;
	flex-direction:column;
}
.content-container:nth-child(1), .content-container:nth-child(3) {
	background: red;
}
.content {
	z-index: 1;
	position: relative;
	margin:auto 0 auto 100px;
	width: 200px;
	min-height: 200px;
	background: #f4f4f4;
}
.content__inner {
	position:absolute;
	left:5px;
	top:5px;
	width:100%;
	height:100%;
	z-index:2;
	background: black;
	transform:translateY(105%);
	transition:all 1s ease .5s;
	opacity:0;
}
.inView .content__inner {
	opacity:1;
	transform:translateY(0);
}

</style>
</head>

<body>
<div class="content-container">&nbsp;</div>
<div  id="scrollArea" class="content-container content-container--middle">
  <div class="content">
    <div class="content__inner"></div>
  </div>
</div>
<div class="content-container">&nbsp;</div>
<script>
(function(d) {
  "use strict";
  var observer = new IntersectionObserver(
    function(entries) {
      if (entries[0].intersectionRatio === 1)
        d.querySelector(".content").classList.add("inView");
      else if (entries[0].intersectionRatio === 0)
        d.querySelector(".content").classList.remove("inView");
    },
    { threshold: [0, 1] }
  );

 observer.observe(d.querySelector(".content"));

})(document);
</script>
</body>
</html>
1 Like

I agree, it seems that scrollSpy is just some sort of intersection observer wrapper so why use a plugin for something that can be done pretty much out of the box.

1 Like

Thanks Paul!
I’ve got that example working but I can’t find a way to open it up to any elements with the “content” class - see example below:
https://codepen.io/Shoxt3r/pen/oNgYXWe

In the meantime I’ve found a plugin called Scroll Reveal which encompasses the intersection and parameters to make it easier - demo here: https://scrollrevealjs.org/

The problem is that when you use querySelector you just get the one item but you need querySelectorAll to get all of them.

You would then need to iterate through that collection and apply appropriate rules to each.

Unfortunately I am only on a mobile and on holiday at the moment so hopefully one of the JS gurus on here (@James_Hibbard or others) can update the code for you. :slight_smile:

1 Like

Hey,

Here’s how you can deal with multiple elements with the class ‘content’:

const toggleVisible = (changes) => {
  changes.forEach(change => {
    if (change.intersectionRatio === 1) {
      change.target.classList.add('inView');
    } else if (change.intersectionRatio === 0) {
      change.target.classList.remove('inView');
    }
  });
};

const opts = {
  root: null,
  rootMargin: '0px',
  threshold: [0, 1]
};

const observer = new IntersectionObserver(toggleVisible, opts);
const contentElements = document.querySelectorAll('.content');
contentElements.forEach(el => observer.observe(el));

DEMO

2 Likes

Thanks James :slight_smile:

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.