Intersection Observer API, counter and scope of a variable + Developing a slider

let options= {};
let counter = 0;

function calculateVisibleDiv(entries) { 
  entries.forEach(entry=> {
    if (entry.isIntersecting) {
        counter = counter + 1;
        console.log(counter);
    } 
  })
}
console.log(counter);

let observer= new IntersectionObserver(calculateVisibleDiv,options);
const recurAll = document.querySelectorAll(".recur");
recurAll.forEach(recur => {
  observer.observe(recur);
});

Intersection observer api is working fine, and doing its job, but when the variable counter ios not updating globally, and 2 fires before the 1 ↓

Is there a way to update counter globally so that updated value is available outside the function.

I may be missing the obvious but you only ever call Number 2 once at the start and you call it before any functions have taken place so obviously it will be zero and then it never gets called again.

If you were to check in the dev tools console and write console.log(counter) it would return 3 (or whatever the current value is.

2 Likes

Sir, this is the position:

@PaulOB’s comments can be confirmed with clearer console.logs.

function calculateVisibleDiv(entries) { 
    entries.forEach(entry=> {
        if (entry.isIntersecting) {
            counter = counter + 1
            console.log('handler', counter)
        } 
    })
}
console.log('global', counter)
2 Likes

If I rephrase the question how can the counter valuee be globally updated to the new value, different from “0”?

It is being updated, just a time after the global code has finished executing.

Just to demonstrate I have amended your codepen and added a button, which when clicked will output the current global counter.

2 Likes
let options= {};
let counter = 0;

function calculateVisibleDiv(entries) { 
  entries.forEach(entry=> {
    if (entry.isIntersecting) {
        counter = counter + 1;
        console.log(counter);
    } 
  })
}
setTimeout(function(){console.log(counter)},1500);

let observer= new IntersectionObserver(calculateVisibleDiv,options);
const recurAll = document.querySelectorAll(".recur");
recurAll.forEach(recur => {
  observer.observe(recur);
});

I added this:

setTimeout(function(){console.log(counter)},1500);

This helped me to understand what is happening.
not_moving

Click Here

2 Likes

That was my first thought :slight_smile:

Glad you’ve got it.

2 Likes

if (entry.isIntersecting)

For above If I want to take the negated version(not):

if (! entry.isIntersecting) Is that wrong or such kind of sysntax doesn’t exists?

some are intersecting, and those that are not intersecting I have to do something with them.

That syntax does exist, but what are you trying to achieve? I’m just asking because you possibly have the options settings to consider?

Initilally all div has active class, but those div’s that are not intersecting the browser I want to delete the active class from them, so that I can build further logic with slider.

I also tried entry.isIntersecting == false but that doesn’t work to generate logic for non intersecting div’s.

function calculateVisibleDiv(entries) { 
  entries.forEach(entry=> {
    if (entry.isIntersecting) {
        counter = counter + 1;
        console.log(entry.target);
    } 
    if (entry.isIntersecting == false) {
      entry.target.classList.remove('.active');
    }
  })
}

Unfortunately it doesn’t appear to work that way.

On initial setup of the observers it fires as you would expect on all of the images out of view. Then on scrolling down it only fires on an image going out of view. In short without utilising the DOM you don’t have access to the other elements (Not that I can see anyway).

My first thought is why do they have to all be active before scrolling? It would obviously be easier to toggle the active class on intersection of one image. Maybe I am missing something, but it seem the logic needs to be flipped.

edit: I am scrabbling around with this a bit myself, so others here may have a more qualified opinion.

2 Likes

Those were my thoughts also. Surely you can just add the class to the image that intersects and then you can still access anything else because they won’t have that class.

2 Likes

I have finally created a slider here: https://codepen.io/codeispoetry/pen/OJOdQze?editors=0110

With its JS code, which looks like →

let options= {
  threshold:[1]
};
let counter = 0;
let totalSlides = 0;

function calculateVisibleDiv(entries) { 
  entries.forEach(entry=> {
    totalSlides = totalSlides + 1;
    if (entry.isIntersecting) {
      counter = counter + 1;
      console.log(entry.target);
    } 
  })
}

setTimeout(function() {
  var totalDiv = document.querySelectorAll('.active');
  console.log(counter);
  for (let i = counter; i < totalSlides; i++) {
    totalDiv[i].classList.remove('active');
    totalDiv[i].classList.add('right');
  } 
  
},1500);



let observer= new IntersectionObserver(calculateVisibleDiv,options);
const recurAll = document.querySelectorAll(".recur");
recurAll.forEach(recur => {
  observer.observe(recur);
});

// setTimeout(function(){console.log(counter)},1500);


// Slider logic for next

const next = document.querySelector('.next');
next.addEventListener("click", clickNext);
function clickNext(ev) {
  ev.preventDefault();
  const currentVisible = document.querySelectorAll('.active:not(.left):not(.right)'); 
  const onlyRight = document.querySelectorAll('.right');
  const whichLesser = Math.min(counter, onlyRight.length);
  for (let i = 0; i < whichLesser; i++) {
    onlyRight[i].classList.remove('right');
    onlyRight[i].classList.add('active');
    currentVisible[i].classList.add('left');
    currentVisible[i].classList.remove('active');    
  } 
}


// Slider logic for prev

const prev = document.querySelector('.prev');
prev.addEventListener("click", clickPrev);
function clickPrev(ev) {
  ev.preventDefault(); 
  const currentVisible = document.querySelectorAll('.active:not(.left):not(.right)'); 
  const onlyLeft = document.querySelectorAll('.left');
  const whichLesser = Math.min(counter, onlyLeft.length);
  for (let i = 0; i < whichLesser; i++) {
    onlyLeft[onlyLeft.length - 1 - i].classList.remove('left'); 
    onlyLeft[onlyLeft.length - 1 - i].classList.add('active');
    currentVisible[currentVisible.length -1 - i].classList.remove('active');  
    currentVisible[currentVisible.length -1 - i].classList.add('right');   
  }
}

I wanted to design a slider, which should capture the number of sliding units based on the number of slides visible in the viewport out of the total number of slides in the DOM, and based on that same number of items should slide on next/previous clicks.

Case 1, for example: On 1400PX view port 3 items are visible then on next/prev 3 items should slide.

Case 2, for example: On 800PX view port 2 items are visible then on next/prev 2 items should slide.

This case list is not exhaustive, they are just a couple of possibilities. The idea was that the Intersection Observer API should make decisions on how many items to slide based on the current number of items visible.

Initially active class is set on all, but this code ensure that active class should be withdrawn to elements which are not visible to Intersection Observer API:

setTimeout(function() {
  var totalDiv = document.querySelectorAll('.active');
  console.log(counter);
  for (let i = counter; i < totalSlides; i++) {
    totalDiv[i].classList.remove('active');
    totalDiv[i].classList.add('right');
  } 
  
},1500);

P.S. Counter is the number of divs visible to Intersection Observer API

But there will be situations when multiple of counters will not exist such as:

3counter + 1
3counter + 2 etc

So, I have put a condition on the logic:

const whichLesser = Math.min(counter, onlyRight.length);
const whichLesser = Math.min(counter, onlyLeft.length);

The above will ensure that in the absence of quantities less than “counter” only that number should slide so that not to make viewport empty of div’s

But there is a problem:

  1. Last element does not scroll, but when
  2. I open it in console/developer mode and it does scrolls.

I have reproduced it here.

I don’t think that’s working very well as there’s some fuzzy logic in there and the divs are flying backwards and forwards due to the method you are using to slide them.

It would be easier if you had an extra wrapper or two and then you could slide the wrapper by 100% which would slide all the divs that were visible each time rather than trying to slide a number of individual divs.

Also I think you only need to know how many items are in the viewport at any given time and then you can work out how many times you can slide them. For example if you had 3 items in the viewport and you have 12 elements then you only need to slide 4 times. If you keep a counter and then check this each time you slide (in case the screen was resized and less/more were in the viewport) then you could act accordingly.

Here’s a rough demo of what I mean but instead of using the Intersection Observer to find how many items are visible I simply grab the css custom variable that dictates how many are visible. After all it’s the css that dictates how many are visible anyway.

This is only a quick and dirty demo but of you resize the screen it goes from 4 across to one across but still slides a screenful at a time whatever. Instead of using the css variable to get the ratio I’m sure you could use the instersection observer to get the same ratio although it would be more code.

I’m not sure if that helps much as I got lost in the logic of your actual code although I notice that there is an error in the console at certain points.

1 Like

Your solution is definitely worth studying. I will study it tommorow. While I was not logged into the sitepoint.com I tried and modified my code. Here is the new codepen →
https://codepen.io/codeispoetry/pen/OJOqjLp

I have simplified the JS logic, but problem is in my CSS approach.
On next the div’s doesnt appear to be sliding, but as if they are appearing and disappearing. However on prev the sliding animation is there, otherwise the divs are moving as anticipated.

Animation in this part needs some redo:

.recur {
	flex: 0 0 calc((100% - 60px - 10px)/3);
	transition: transform 0.5s;
}


.recur.left {
	transform: translateX(-100vw);
	position: absolute;
}

slider 2.zip (1.7 KB)

Your solution is definitely worth studying. I will study it tommorow. While I was not logged into the sitepoint.com I tried and modified my code. Here is the new codepen →
https://codepen.io/codeispoetry/pen/OJOqjLp

I have simplified the JS logic, but problem is in my CSS approach.
On next the div’s doesnt appear to be sliding, but as if they are appearing and disappearing. However on prev the sliding animation is there, otherwise the divs are moving as anticipated.

Animation in this part needs some redo:

.recur {
	flex: 0 0 calc((100% - 60px - 10px)/3);
	transition: transform 0.5s;
}


.recur.left {
	transform: translateX(-100vw);
	position: absolute;
}

slider 2.zip (1.7 KB)

This will not always be image slider, but something else: news slider with snippet slidings, for example. I am just rehearsing a general solution.

There are 3 CSS problems in your approach that I can see:

  1. You you slide the active items to the left but you make them position:absolute. This removes them from the flow and instantly the other items on the page will occupy that space (i.e. no animation).

  2. The images themselves will also change size because they are no longer constrained by the flex layout but an absolutely placed parent instead.

Just change the transition duration to 5s and you will see what’s happening more clearly.

Also just for testing you may find it easier to number the images like this:

body {
  counter-reset: my-sec-counter;
}
.recur:before{
  counter-increment: my-sec-counter;
  content: "Img " counter(my-sec-counter);
  position:absolute;
  font-size:2rem;
  color:#000;
  background:#fff;
}
  1. You are using magic numbers which will not work if you change the layout to two or four across instead of 3.

flex: 0 0 calc((100% - 60px - 10px)/3);

I thought the idea was to automate the process so the 60px needs to go and use an automatic method as in my example. Also the 3 should be a css custom variable so that you can easily change the layout in the media query by just re-declaring the variable in the media query. (Indeed in my example I use that variable to work out how many items fit across and I don’t need to query anything else as that tells all that is needed. I added an autoplay to my demo here and you can open and close the window to see it working for different numbers across.)

I know you want to continue with your current method but I can’t see an easy way to slide the items to the left individually. I still think you should slide the wrapper which will move all the elements and you can just move the wrapper left and right as required (it will require a couple of extra wrappers to set this up though).:slight_smile:

1 Like

Hi there, Can you please provide some HTML prototype to accomplish this?

Your code is very good, and also easy to comprehend: https://codepen.io/paulobrien/pen/OJOdaNy?editors=0010

I am studying it.