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

Firebase scheduled function continues to encounter a persistent issue with an "UNAUTHENTICATED" error being consistently thrown

I have created a firebase-function that is scheduled to retrieve data from an external API source and save it in Firestore. const functions = require("firebase-functions"); const admin = require("firebase-admin"); const { default: Axios ...

Text input field with uneditable text displayed at the end

Click here to view the input field I am looking to create an input field that always displays a "%" at the end of the input. Here is what my react component looks like currently: <StyledBaseInput type="text" ...

How to retrieve the value of a selected radio button in an AngularJS radio button group that uses ng-repeat

In the following code snippet, I am trying to retrieve the value when any of the radio buttons is selected: <label ng-repeat="SurveyType in SurveyTypes"> <input type="radio" name="SurveyTypeName" ng-model="surveyData.SurveyTypeN ...

What are the reasons behind the pagination with numbers not functioning properly in Vue?

I've encountered an issue with my Vue application using Vuex while implementing pagination. The problem lies in the fact that the events stored are not being displayed, and neither are the page numbers for pagination. Another issue is that the paginat ...

Changing the tooltip background color in FullCalendar 6 and Bootstrap 5: A step-by-step guide

My FullCalendar agenda displays events with colored backgrounds. However, I want the tooltips for these events to have matching background colors as well. Currently, the tooltips always show up with a black background. Is there a way to dynamically change ...

Ways to transmit data from PHP to JavaScript

I have a file called "hotlaps.php" where I've created a JavaScript script: echo "<body onload=\"myFunction(".$array_1.",".$array_2.",".$array_3.");\">"; In my "hotlaps.js" file, I have the following function: function myFunction(arr ...

Don't forget to expand or collapse

I'm struggling to make this work. My goal is to initially display 50 characters and show the rest when the "read more" button is clicked, and vice versa with "read less." Another problem I've noticed is that when I click the back browser button, ...

"Transferring an array of data to a Laravel controller via AJAX: A step-by-step guide

Is there a way to send arrays of data to the back-end all at once? The Problem 1. This is what I'm currently sending: array:5 [ "_token" => "S5s5ZTTnnP93MyXgCql0l9vhHsiqt5VWaFyEedXj" "product_id" => "21" "specification_id" => "6" " ...

Is there a way to fetch a file using the fs readFile function in node.js without explicitly stating the file name?

I'm currently facing a challenge in retrieving a file from the file system to send it via API to the client. I am using Express.js for my backend and utilizing the 'fs' library. My goal is to achieve this without explicitly specifying the fi ...

Acquire HTML table information in array format using JavaScript

I am searching for a script that can extract the 2D array of rows and columns from an HTML table within a div element. The desired array output should be: [ ["Company", "Contact", "Country"], ["Alfreds Futterkiste", "Maria Anders", "Germany"], ...

Using Leaflet to set the view based on a GeoJSON dataset

I am working on incorporating a leaflet.js map with a square shape imported from an external GeoJSON file. I have determined the exact coordinates for the center of the square, and now I need to convert these coordinates in order to pass them correctly t ...

Enhance Your Website with Stunning Bootstrap Icons

I have been attempting to utilize this bootstrap signup template here, but upon rendering it, the icons for the user, email, password, and so forth are not appearing as expected. <link href="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email ...

Waiting for response in AngularJS Controller and setting callback

I have developed an AngularJS application with controllers.js and factories.js that I am excited about. However, I am facing a challenge when trying to manipulate the values within the controller (retrieved from the factories). The issue is that these val ...

Struggling to make Vue.js transition effects function properly?

I'm having trouble getting a vue.js (version 1) transition to work. I copied the code from their official website, but it's not running the javascript console.logs! Vue.transition('fade', { css: false, enter: function ( ...

Additional pixels positioned beneath my HTML table

Currently, I am working on a Joomla website using the Helix3 framework, but have run into an issue that I can't seem to resolve. There seems to be some additional pixels below my HTML table at the top of the page where it says "::: ÉLŐ KÖZVETÍTÉ ...

Using Selenium to continuously scroll to the bottom of the webpage

Selenium: I am new to WebDriverJS. I have experimented with this method in Java. Long repeat = 0l, scrollHeight = 0l, returnHeight = 0l; while(true){ if (repeat == 0) { returnHeight = (Long) jse.executeScript("var scroll =document.documentEle ...

Updating the language of the months displayed in the popup calendar

I am attempting to change the language of the clickDate function from English (Jan, Dec, etc.) to another language. However, I am struggling to find a way to do so because I only have access to the scripts provided below. The idate is in the format 2015073 ...

Pass an object along with the rendering of the page using ExpressJS and Mongoose

After reading through this post: mongoose find all not sending callback I am currently working on sending an Object along with a page in my nodejs/expressjs application, instead of just responding with JSON data. This is the route for my page: //Get lat ...

Tips for cropping an image using CSS with dimensions specified for width, height, and x and y coordinates

As I work on implementing a cropping feature using react-easy-crop, my goal is to store the user's image along with pixel coordinates that dictate their preferred cropping area. My objective is to utilize the parameters provided by react-easy-crop in ...

"XMLHttpRequest 206 Partial Content: Understanding the Importance of Partial

I need help with making a partial content request using an XMLHttpRequest object in JavaScript. Currently, I am trying to load a large binary file from the server and want to stream it similar to how HTML5 video is handled. While setting the Range header ...