What are the steps to create an endless scrolling feature?

I'm trying to create a slider with a horizontal scrolling effect, but I've hit a roadblock. How can I make the slider scroll infinitely? In my code, you can see that after Item 6, it stops scrolling and I have to scroll backward. However, I want it to loop back to Item 1 again, similar to this example: where the scrolling is infinite.

Can anyone assist me with this?

let container = document.querySelector(".container")
let container1 = document.querySelector(".container1")

window.onscroll = ()=>{
  container.style.left = `${-window.scrollY}px` 
  container1.style.right = `${-window.scrollY}px` 
  
}
let currentpos = container.getBoundingClientRect().left
let currentpos1 = container1.getBoundingClientRect().left


let callDisort = () =>{
  let newPos = container.getBoundingClientRect().left;
  let newPos1 = container1.getBoundingClientRect().left;
  let diff = newPos - currentpos;
  let speed = diff * 0.50
  container.style.transform = `skewX(${speed}deg)`
  currentpos = newPos
  container1.style.transform = `skewX(${speed}deg)`
  currentpos = newPos

  requestAnimationFrame(callDisort)
}
console.log(currentpos)


callDisort()
*{
  margin:0;
  padding:0;
  box-sizing:border-box;
  font-family: arial;
}
html,body{
  height:3000px;
   overflow-X:hidden;
}

.container{
  position:fixed;
  display:flex;
  justify-content: space-between;
  top:30vh;
  width: 3000px;
  transition:transform 0.15s;
  will-change:transform;
  border:2px solid green;
 
}
.container1{
  position:fixed;
  display:flex;
  justify-content:space-evenly;
  top:45vh;
  width: 3000px;
  transition:transform 0.15s;
  will-change:transform;
  border:2px solid green;
}

.box{
  position:relative;

}
.box h2{
  font-size:4em;
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8>
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>

  <div class="container">
    <div class="box one">
      <h2>Item 1</h2>
    </div>
    <div class="box two">
      <h2>Item 2</h2>
    </div>
    <div class="box three">
      <h2>Item 3</h2>
    </div>
    <div class="box four">
      <h2>Item 4</h2>
    </div>
    <div class="box five">
      <h2>Item 5</h2>
    </div>
    <div class="box six">
      <h2>Item 6</h2>
    </div>
  </div>

      <div class="container1">
    <div class="box one">
      <h2>Item 1</h2>
    </div>
    <div class="box two">
      <h2>Item 2</h2>
    </div>
    <div class="box three">
      <h2>Item 3</h2>
    </div>
    <div class="box four">
      <h2>Item 4</h2>
    </div>
    <div class="box five">
      <h2>Item 5</h2>
    </div>
    <div class="box six">
      <h2>Item 6</h2>
    </div>
  </div>
  

    
</body>
</html>

Answer №1

In the example shared at , a method is used that has its limitations and is not truly infinite. This limitation stems from the maximum values supported by the css properties left and transform: translate3d(). However, for normal use cases, this approach proves to be sufficient. More information on the maximum allowed negative value for CSS left property can be found here.

The method involves adjusting the position of each box once it goes out of view based on the scrolling direction, bringing it back behind the "last" or ahead of the "first" using transform: translate3d() and left: ....

If you want to explore this method further, I recommend trying it out on jsfiddle or running the code snippet in "Full Page" view due to potential scrolling behavior challenges when dealing with unscrollable iframe children within scrollable parents.

Update:

  • An additional speed detection routine has been implemented to accommodate faster/slower scrolling.
  • A selector issue related to the observer in the JavaScript portion has been fixed.

const eventHandler = (e) => {
  document.querySelectorAll(".boxes-container").forEach(container => {
    const cur = +container.dataset.cur || 0;
    container.dataset.before = container.dataset.cur;
    container.dataset.scrollspeed = (+container.dataset.scrollspeed || 0) +1;
    setTimeout(() => {
        container.dataset.scrollspeed = +container.dataset.scrollspeed -1;
    }, 33 * +container.dataset.scrollspeed);
    let moveByPixels = Math.round(e.deltaY / (6 - Math.min(+container.dataset.scrollspeed,5)));
    if (container.dataset.direction == "invert") {
      moveByPixels *= -1;
    }
    container.style.left = `${cur + -moveByPixels}px`;
    container.dataset.cur = cur + -moveByPixels;
  });
};

window.addEventListener("wheel", eventHandler);
window.addEventListener("mousewheel", eventHandler);

const observer = new IntersectionObserver((entries, opts) => {
  entries.forEach(entry => {
    entry.target.classList.toggle('visible', entry.isIntersecting);
  });
  document.querySelectorAll(".boxes-container").forEach(container => {
    const before = (+container.dataset.before || 0),
      current = (+container.dataset.cur || 0),
      diff = before - current,
      boxes = [...container.querySelectorAll(".box")],
      visible = [...container.querySelectorAll(".box.visible")],
      first = boxes.indexOf(visible[0]),
      last = boxes.indexOf(visible[visible.length - 1]),
      adjust = (by) => {
        container.dataset.cur = +container.dataset.cur + by;
        container.dataset.before = +container.dataset.before + by;
        container.style.left = +container.dataset.cur + 'px';
      };
    if (diff >= 0) {
      if (first > 0) { // move the first to the end
        const box = boxes[0];
        box.parentNode.append(box);
        adjust(box.clientWidth);
      }
    } else {
      if (last == 0 || first == 0) { // move the to first
        const box = boxes[boxes.length - 1];
        box.parentNode.prepend(box);
        adjust(-box.clientWidth);

      }
    }
  })
}, {
  threshold: new Array(101).fill(0).map((n, i) => +(i / 100).toFixed(2))
});
document.querySelectorAll(".boxes-container .box").forEach(el => observer.observe(el));
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: Sans-Serif;
}

.boxes-container {
  position: fixed;
  display: flex;
  flex-wrap: nowrap;
  flex-direction: row;
  white-space: nowrap;
  min-width: -min-content;
}

.v30 {
  top: 30vh;
}

.v60 {
  top: 60vh;
}

.box {
  position: relative;
  margin: 0 !important;
  padding: 0 50px;
}

.box h2 {
  font-size: 5rem;
}
<div class="boxes-container">
  <div class="box">
    <h2>0</h2>
  </div>
  <div class="box">
    <h2>1</h2>
  </div>
  <div class="box">
    <h2>2</h2>
  </div>
  <div class="box">
    <h2>3</h2>
  </div>
  <div class="box">
    <h2>4</h2>
  </div>
  <div class="box">
    <h2>5</h2>
  </div>
  <div class="box">
    <h2>6</h2>
  </div>
</div>

<div class="boxes-container v30" data-direction="invert">
  <div class="box">
    <h2>A</h2>
  </div>
  <div class="box">
    <h2>B</h2>
  </div>
  <div class="box">
    <h2>C</h2>
  </div>
  <div class="box">
    <h2>D</h2>
  </div>
  <div class="box">
    <h2>E</h2>
  </div>
  <div class="box">
    <h2>F</h2>
  </div>
  <div class="box">
    <h2>G</h2>
  </div>
</div>

<div class="boxes-container v60">
  <div class="box">
    <h2>0</h2>
  </div>
  <div class="box">
    <h2>1</h2>
  </div>
  <div class="box">
    <h2>2</h2>
  </div>
  <div class="box">
    <h2>3</h2>
  </div>
  <div class="box">
    <h2>4</h2>
  </div>
  <div class="box">
    <h2>5</h2>
  </div>
  <div class="box">
    <h2>6</h2>
  </div>
</div>

Answer №2

Here are some insights on the solution provided:

  1. If you set the container to display:flex with justify-content:space-around;, the spacing between items will change as you scroll due to closer packing with more items. It's recommended to use justify-content:flex-start; with a fixed width for each .box to achieve better results.

  2. The addition of a debounce function significantly improved and simplified the process, despite the underwhelming console logs.

  3. One thing to watch out for is scrolling too fast, which might cause you to reach the end of the carousel before it fully populates. To address this, consider increasing the delay from 50 to 500 milliseconds.

  4. The debounce essentially controls when new boxes are added based on the elapsed time since the last scroll event. You could also explore using a throttle function instead, limiting the re-population rate in intervals.

  5. Ensure that the HTML,Body height is set to a sufficiently large value - in this case, 30,000. The width of the .container should be set to auto, dynamically adjusting as new items fill the container.

  6. Remember, the $ and $$ functions used here are not jQuery but pure vanilla JavaScript shortcuts for document.querySelector() and document.querySelectorAll().

  7. To experience the demo at its best, view it in full page mode (click on the link at the top right corner of the demo window).

const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);
let kontainer = $(".container");
const boxes = $$('.box');
const debounceDelay = 50; //change to 100 for better performance

const updateCarousel = debounce(function(e){
  console.log(scrollY +' // '+ kontainer.getBoundingClientRect().right)
  const currKontainerWidth = kontainer.getBoundingClientRect().right;
  if ( currKontainerWidth - scrollY < 300 ){
    for (let i=0, j=boxes.length; j > i; i++){
      kontainer.appendChild(boxes[i].cloneNode(true));
    }
  }
}, debounceDelay);

window.addEventListener('scroll', updateCarousel, false);

window.onscroll = () => {
  kontainer.style.left = `${-window.scrollY}px`;
}

function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};
*{
  margin:0;
  padding:0;
  box-sizing:border-box;
  font-family: arial;
}
html,body{
  height:30000px;
   overflow-X:hidden;
}

.container{
  position:fixed;
  display:flex;
  justify-content: flex-start;
  top:30vh;
  width: auto;
  transition:transform 0.15s;
  will-change:transform;
  border:2px solid green;
 
}

.box{
  position:relative;
  min-width:250px;

}
.box h2{
  font-size:4em;
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>

  <div class="container">
    <div class="box one">
      <h2>Item 1</h2>
    </div>
    <div class="box two">
      <h2>Item 2</h2>
    </div>
    <div class="box three">
      <h2>Item 3</h2>
    </div>
    <div class="box four">
      <h2>Item 4</h2>
    </div>
    <div class="box five">
      <h2>Item 5</h2>
    </div>
    <div class="box six">
      <h2>Item 6</h2>
    </div>
  </div>

    
</body>
</html>

For optimal viewing experience, click "full page" at the top right after running the code snippet.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Step-by-step guide on inserting a div element or hotspot within a 360 panorama image using three.js

As I work on creating a virtual tour using 360 images, I am looking to include hotspots or div elements within the image that can be clicked. How can I store data in my database from the event values such as angle value, event.clientX, and event.clientY wh ...

Display Rails view using JavaScript when clicked

I have a task to create a view where users can click on different buttons and see different content displayed based on the button they choose. I attempted to achieve this using JavaScript, but unfortunately, I couldn't get it to work as intended. Init ...

Leveraging ES6 import declarations within Firebase functions

I've been experimenting with using ES6 in Firebase functions by trying to import modules like "import App from './src/App.js'. However, after adding type:"module" to my package.json, I encountered a strange error with Firebase ...

Combining a name using JavaScript even if certain parts are not available

How can I efficiently combine name elements without any extra white space in Vue.js? let FirstName = "John" let MI = "G" let LastName = "Jones" let Suffix = "Jr." I want to create let fullName = "John G Jones Jr." However, in cases like this: let First ...

What is the preferred choice: using camelCase in CSS with Styled Components or Emotion for standard React development?

When I'm coding with React Native, I use the styled properties that follow most of CSS syntax and are formatted in camel case. For instance: //... <Component style={styles.container} /> //... const styles = StyleSheet.Create({ ...

Adjusting the length of the To, CC, and BCC text fields in web design

I'm developing a webpage that will collect email addresses for user notifications. I want to ensure the length limits of the 'to,' 'cc,' and 'bcc' fields. According to RFC 2821/3696, an email address can be up to 256 cha ...

Performing a function inside a JSON structure

I am dealing with a JSON object that contains a list of functions I need to access and run like regular functions. However, I'm struggling to figure out how to achieve this. Here is what I have attempted: Bootstrapper.dynamic = { "interaction": f ...

Creating a subtle vanishing popup dialog using only CSS (no reliance on jQuery)

Is there a way to add a fade effect to my simple pop-up image using CSS? I've tried various transition properties, but nothing seems to work. Any suggestions would be appreciated. Thanks! ...

Revamp MUI class names with React Material UI's innovative randomization and elimination

Can names be randomized or Mui-classNames removed? https://i.stack.imgur.com/J6A9V.png Similar to the image displayed? (All CSS class names would be jssXXX) Appreciate your assistance. ...

Javascript datatables do not allow for setting a default column sort

I am encountering an issue where I need to sort the results by a specific column on page load. In this case, I want the initial results to be displayed in descending order based on "RecordDate". However, it seems that the server side is blocking any sort ...

Struggling with mapping through a multidimensional array?

I'm facing an issue with using .map() on a nested array. I initially tried to iterate through my stored data using .map(), and then attempted another iteration within the first one to handle the nested array, but it didn't work as expected. The ...

When working with Vuejs, if you try to use "axios" in a .js file, you may encounter an error

I am currently working with VueJS and attempting to send a GET request to my API. I am using axios, but encountering an issue when trying to import it. If I use require for the import, I receive this error: Uncaught ReferenceError: require is not defined. ...

What's the reason behind this file not opening?

When I try to insert this code into files index.html, style.css, and app.js, the page refuses to open. The browser constantly displays a message saying "The webpage was reloaded because a problem occurred." I am using a MacBook Air with macOS Big Sur and a ...

How can I locate specific images within a CSS sprite?

Are you searching for the correct process to pinpoint specific image locations within an image sprite? If, for example, you aim to create 10 div images in your header using the image provided below, how can you determine the exact location of each one? Is ...

Local variable reference getting lost in a loop during a jQuery Ajax call

Currently, I am facing an issue with my jQuery ajax calls within a loop. The problem arises when each ajax call returns and I need to retrieve a specific value associated with the original call. However, due to further iterations in the loop, the value of ...

Setting the second tab as the primary active tab

I am currently working on a script that is well-known, and everything is functioning perfectly. However, I want to change it so that when the page is first opened, it displays the second tab instead of the first one (the first tab being a mail compose tab ...

Is it possible to implement the SCSS CSS preprocessor in Angular 1.6.6, even though it is already utilizing LESS as the current CSS preprocessor?

Is it necessary to compile SCSS and Stylus files into CSS before importing them into my Angular version 1 app? Are there any other alternatives available? Context: I have two applications, one using Angular 4 with SCSS and the other using Angular 1 with L ...

What is the correct way to handle BINDing both before and after POPSTATE events when using AJAX?

When dealing with slow AJAX processes, displaying a "loading..." message to the user was deemed beneficial. I have experience using history.pushState and history.popState, but my loading image does not show up when performing actual AJAX requests upon pres ...

Tips on implementing session control using Ajax and PHP

When trying to login using JavaScript in a web-service created with Cake PHP, everything works fine with a common request (non-ajax). However, when utilizing the code below for the countUpdates after logging in, a 403 forbidden error is encountered. The AJ ...

Is there a built-in method or library for extracting data from JSON strings in programming languages?

Duplicate Query: how to parse json in javascript The data sent back by my server is in JSON format, but it doesn't follow the usual key/value pairs structure. Here's an example of the data I'm receiving: ["Value1","Value2","Value3"] ...