How can I animate a vertical line while scrolling down the page?

I'm attempting to create a dynamic vertical line that adjusts based on the position of elements on the webpage. Check out this example of what I'm aiming for: https://i.sstatic.net/JpK3FQI2.gif

My attempt at achieving this looks something like:

document.addEventListener('DOMContentLoaded', function() {
    const line = document.querySelector('.vertical-line');
    const sections = document.querySelectorAll('section');

    let options = {
        root: null,
        rootMargin: '0px',
        threshold: 0.1
    };

    let observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                entry.target.classList.remove('hidden');
            } else {
                entry.target.classList.add('hidden');
            }
        });
    }, options);

    sections.forEach(section => {
        observer.observe(section);
    });

    let scrollDirection = 'down';
    let lastScrollTop = 0;

    window.addEventListener('scroll', () => {
        let st = window.scrollY || document.documentElement.scrollTop;

        if (st > lastScrollTop) {
            scrollDirection = 'down';
        } else {
            scrollDirection = 'up';
        }
        lastScrollTop = st <= 0 ? 0 : st;

        if (scrollDirection === 'down') {
            line.style.transform = 'translateX(50vw)';
        } else {
            line.style.transform = 'translateX(-50vw)';
        }
    });
});
.row {
    display: flex;
    justify-content: space-between;
}

.col {
    padding-left: 8.125rem;
    padding-right: 8.125rem;
    align-self: center;

    flex: 1;
    padding: 20px;
    position: relative;
}<br>
.vertical-line {
    content: '';
    border-left: 1px solid var(--primary-text);
    border-right: 1px solid var(--primary-text);
    padding: 0;
    position: absolute;
    height: 48.75rem;
    right: 50%;

    transition: transform 1s;
}

@keyframes moveLine {
    0% { transform: translateX(0); }
    100% { transform: translateX(50px); }
} */

.hidden {
    visibility: hidden;
}
<section id="s1">
    <div class="page-container">
        <div class="row">
            <div class="col">
                <div>
                    <p class="about-text-title">Label1</p>
                    <p class="about-text">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Provident nulla iure quod autem amet, ipsa beatae
                        voluptatibus maiores adipisci quae, expedita deserunt laborum architecto corporis dolore. Voluptates tenetur nisi
                        cupiditate?</p;
                </div>
            </div>
            <div class="col vertical-line"></div>
            <div class="col">
                <p>Video frame</p>
            </div>
        </div>
    </div>
</section>
<section id="s2">
    <div class="container">
        <div class="row">
            <div class="col">
                <p>Label2</p>
                <p class="about-text">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Provident nulla iure quod autem amet, ipsa beatae
                        voluptatibus maiores adipisci quae, expedita deserunt laborum architecto corporis dolore. Voluptates tenetur nisi
                        cupiditate?</p>
            </div>
            <div class="col">
                <p>Another video frame</p>
            </div>
        </div>
    </div>
</section>

The line moves in response to scrolling, but it only shifts to the edge of the screen and returns to its default left position when scrolling stops. Additionally, it doesn't overlay section 1 elements nor reveal section 2 elements.

I'm struggling to figure out how to achieve the desired effect. Essentially, I want the line to act as a "pagination" feature, scrolling the page and updating the displayed elements.

If anyone could provide guidance on accomplishing this task, I would greatly appreciate it. Apologies if my explanation is unclear, as I have limited knowledge of html, css, js.

I attempted to replicate the provided example using javascript and css, but the outcome was not as intended.

Answer №1

This solution may not cover all scenarios, as certain details are unclear. For instance, should the animation continue if the user scrolls up? What happens if the user scrolls while the animation is still active? Are there other elements on the page that could affect this layout?


I have made adjustments to the CSS by including a vertical line at the top and applying position: fixed to both the #vertical-line and section > .container elements. I assumed that the video should remain visible even when scrolling to the last section.

I have omitted the bouncing animation for now, leaving it to be implemented later. However, the code provided here offers guidance on how to approach this task.

const verticalLineEl = document.getElementById('vertical-line');
const sections = document.querySelectorAll('section');

let checkingVerticalScroll = false;
let lastKnownScrollPosition = window.scrollY;
let sectionHeight;

document.addEventListener('scroll', (event) => {
  lastKnownScrollPosition = window.scrollY;

  if (!checkingVerticalScroll) {
  
    // Perform calculations and updates only during screen refresh
    requestAnimationFrame(() => {
      updateVerticalLineWidth(lastKnownScrollPosition);
      checkingVerticalScroll = false;
    });

    checkingVerticalScroll = true;
  }
});

const updateVerticalLineWidth = (lastKnownScrollPosition) => {
  const currentSection = calculateCurrentSection(lastKnownScrollPosition);

  verticalLineEl.style.width = calculateVerticalLineWidth(currentSection, lastKnownScrollPosition);
  
  setContainerStackingOrder(currentSection);
}

const calculateCurrentSection = (lastKnownScrollPosition) => {
  let currentSection = Math.ceil(lastKnownScrollPosition * sections.length / window.innerHeight);
  currentSection = Math.min(sections.length, currentSection);
  currentSection = Math.max(1, currentSection);

  return currentSection
}

const calculateVerticalLineWidth = (currentSection, lastKnownScrollPosition) => {  
  let widthVerticalLine = 0;
  
  // Skip animation for the last section
  if (currentSection < sections.length) {
    let sectionHeight = sections[currentSection].getBoundingClientRect().height;
    
    const maxWidthVerticalLine = window.innerWidth;

    let scrollInSection = lastKnownScrollPosition - sectionHeight * (currentSection - 1);

    widthVerticalLine = maxWidthVerticalLine * scrollInSection / sectionHeight;

    return widthVerticalLine + 'px';  
  } 
  
  return widthVerticalLine + 'px';
}

const setContainerStackingOrder = (currentSection) => {
  for (let i = 0; i < sections.length; i++) {
    sections[i].firstElementChild.style.zIndex = (currentSection == i + 1) ? 50 : 0;
  }
}
* {
  margin: 0;
  padding: 0;
}

#vertical-line {
  position: fixed;
  top: 50%;
  left: 50%;
  translate: 0% -50%;
  z-index: 100;

  height: 80vh;
  width: 0px;
  border-right: 1px solid #000;

  background-color: white;
}

section {
  min-height: 100vh;
}

section > .container {
  position: fixed;
  top: 50%;
  translate: 0% -50%;
  max-width: 100%;
  padding: 1rem;
  background-color: white;
  
}

.about-text-title {
  font-size: 2rem;
  font-weight: bold;
  padding-bottom: 0.5rem;
}

.row {
  display: flex;
  gap: 2rem;
}

.col {
  display: flex;
  flex-direction: column;
  justify-content: center;
  
  width: 50%;
  flex-basis: 1;
}
<div id="vertical-line"></div>

<section id="s1">
  <div class="container" style="z-index: 50">
    <div class="row">
      <div class="col">
        <div>
          <p class="about-text-title">Label1</p>
          <p class="about-text">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Provident nulla iure quod autem amet, ipsa beatae voluptatibus maiores adipisci quae, expedita deserunt laborum architecto corporis dolore. Voluptates tenetur nisi cupiditate?
          </p>
        </div>
      </div>
      <div class="col">
        <p>Video frame</p>
      </div>
    </div>
  </div>
</section>
<section id="s2">
  <div class="container">
    <div class="row">
      <div class="col">
        <p class="about-text-title">Label2</p>
        <p class="about-text">Lorem ipsum, dolor sit amet consectetur adipisicing elit. Provident nulla iure quod autem amet, ipsa beatae voluptatibus maiores adipisci quae, expedita deserunt laborum architecto corporis dolore. Voluptates tenetur nisi cupiditate?
        </p>
      </div>
      <div class="col">
        <p>Another video frame</p>
      </div>
    </div>
  </div>
</section>

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

What steps can I take to prevent a JavaScript element from clearing everything when clicked?

After clicking on "add more," the first delete function works correctly, but the subsequent delete element deletes everything on the page. I suspect there may be an issue with the 'ele' variable in the JavaScript code. Since I am not very experie ...

Bootstrap glyphicon not in sync with input field alignment

Having an issue with aligning the upper border of a search input field when using Angular with Bootstrap and adding a glyphicon next to it. div.input-group(ng-show='feeds.length > 0') span.input-group-addon.glyphicon.glyphicon-search in ...

Executing JavaScript function on AJAX update in Yii CGridView/CListView

Currently, I am integrating isotope with Yii for my CListView and CGridView pages to enhance their display. While everything functions smoothly, an issue arises when pagination is utilized, and the content on the page is updated via ajax, causing isotope ...

How to Alphabetize an Array of Strings Containing Numbers using TypeScript and Angular

I'm working with an array that looks like this: arr = ["100 abc", "ad", "5 star", "orange"]; The goal is to first sort the strings without numbers at the beginning, then sort the strings with numbers added at t ...

Presence detected: Discord bot appears online but is missing from members list

I am embarking on the journey of creating my first Discord bot. After installing NodeJS on my computer, I used the Discord developer tools to create the app, turned it into a bot, obtained the token, selected privileges, and added the bot to my server. I ...

Disappearing Data Mystery in ngFor Array

In my Angular 6 component, I have created a form for adding new users using an HTML table: user-add.component.html [...] <tbody> <tr *ngFor="let value of data.pos; let i = index"> <td><input matInput [(ngModel)]="value.us ...

I'm having difficulty sending AJAX data to PHP, as it returns as undefined

I am attempting to pass a variable through a script using AJAX in order to utilize it in my PHP file. The goal is to pass a selected value with JavaScript and then use that value in an SQL query. In the code below, I tried to use AJAX to pass the variable ...

How can we showcase the data within this loop on the user interface in React JS?

I'm currently working with React JS and I have a question regarding how to display data from a loop in the UI. The code snippet below shows the data being logged to the console, but I want to show it on the actual user interface. Could you please guid ...

The title of the Electron application remains consistent

My application is being packaged using electron-packager, but it's not changing its name and still displays "Electron." It's supposed to use the productName in my package.json file, but for some reason, it doesn't change. Even after creati ...

Continuously summon commitments

After extensive searching online, I am still grappling with this particular issue. Currently, I'm developing an Angular service for an Ionic application. This service's primary function is to download an image. In traditional JavaScript, I would ...

React - Prevent conflicts by overriding existing styles with a new class

I've come across many discussions on this topic, but nothing has worked the way I need it to. In my React component, I'm passing className as a prop. I also have a Higher Order Component (HOC) that takes a component and default style, then return ...

The delete function is not functioning

I need help with implementing a custom Angular directive that includes a delete button to remove itself. When I click the button removeMe, it is not deleting an item from the array. Any suggestions on what might be causing this issue? HTML: <button t ...

Is there a way for a Vue component to interact with a button or checkbox in order to dynamically update tooltip content and button style using a function?

Is it possible for a Vue component to trigger button clicks or checkbox checks in order to update tooltip text and button color using a function? Despite the presence of a handle() function in the code provided, these changes are not currently taking effec ...

How do you modify the up vector of the camera in three.js?

When working with three.js, I encountered a situation where I set the camera's focal point using camera.lookAt(0, 0, 0), but the camera's up vector ended up pointing in a random direction. I have a specific up vector in mind that I want the camer ...

When I refresh the page, I receive the following message: The file "/assets/css/bootstrap.min.css" was blocked because of a MIME type mismatch (X-Content-Type-Options: nosniff) and was identified as

I encountered an issue while testing my MERN stack app. Upon refreshing the browser, I noticed errors on a couple of components: The resource from “http://localhost:3000/profile/assets/css/bootstrap.min.css” was blocked due to MIME type (“text/html ...

Ways to inform an observer of an Object's change?

Is there a way to ensure that an observer receives notification of a change, regardless of Ember's assessment of property changes? While the ember observer pattern typically works well for me, I've encountered a specific issue where I'm unc ...

Utilize SVG images repeatedly while still retaining control over CSS styling

I have a collection of silhouette images that I need to change color for various purposes, such as displaying in different locations or using hover effects. While it's fairly straightforward to achieve this by directly embedding the SVG into the HTML ...

What is the proper way to request permission for allowing others to access the mailto function?

I want to create a feature where users can open email on click using JavaScript. However, I have encountered an issue with using the mailto function in Chrome, as it requires changing the handlers settings to make it work. My query is whether it is possib ...

Unable to remove div element

Currently, I am working on writing a code for a webpage with specific functionalities. I need to display a button and an embedded YouTube video. When the user clicks on the video, they should be redirected to a different link (not YouTube) and the video sh ...

The function `element.all(locator)` seems to be yielding an [object Object] when used in Protractor with

I am currently attempting to retrieve the list of files in a particular folder from my application. However, when I try to access the list, it seems to be returning an object instead. Below is the code snippet where this issue occurs. Can someone help me f ...