How can I reset a CSS position sticky element using JavaScript?

I have created a page where each section fills the entire screen and is styled using CSS position: sticky; to create a cool layered effect. Check it out here:

https://codesandbox.io/s/ecstatic-khayyam-cgql1?fontsize=14&hidenavigation=1&theme=dark

Everything works great so far.

The issue arises when navigating between these sections with JavaScript. When trying to use the menu, you'll notice that the links work on sections that are not yet "sticky", but you can't navigate back up the page.

I suspect the problem lies in el.getBoundingClientRect(), which causes the top value of a sticky element to always be zero once it becomes sticky.

To navigate around this issue, I'm currently using a small library called Jump.js. However, even with vanilla JS, the problem remains due to the calculations involved when an element becomes sticky.

Is there a way to determine the original position of each section before it became sticky? This would allow me to manually set the scroll position for smoother navigation.

Although I'm working with Vue.js, the core issue pertains to CSS and JS interactions, unaffected by the framework.

App.vue

<template>
  <main id="app">
    <ul class="menu">
      <li @click="goTo('A')">Section A</li>
      <li @click="goTo('B')">Section B</li>
      <li @click="goTo('C')">Section C</li>
      <li @click="goTo('D')">Section D</li>
      <li @click="goTo('E')">Section E</li>
    </ul>
    <SectionItem id="A" color="red"/>
    <SectionItem id="B" color="green"/>
    <SectionItem id="C" color="blue"/>
    <SectionItem id="D" color="orange"/>
    <SectionItem id="E" color="purple"/>
  </main>
</template>

<script>
import jump from "jump.js";
import SectionItem from "./components/SectionItem.vue";

export default {
  name: "App",
  components: {
    SectionItem
  },
  methods: {
    goTo(id) {
      jump(`#${id}`, {
        duration: 300
      });
    }
  }
};
</script>

SectionItem.vue

<template>
  <div :id="id" class="SectionItem" :style="styles">
    <p>I am section item: {{ id }}</p>
  </div>
</template>

<script>
export default {
  name: "SectionItem",
  props: {
    id: {
      type: String,
      required: true
    },
    color: {
      type: String,
      required: true
    }
  },
  computed: {
    styles() {
      return {
        backgroundColor: this.color
      };
    }
  }
};
</script>

<style>
.SectionItem {
  position: sticky;
  top: 0;
  width: 100%;
  min-height: 100vh;
  padding: 20px;
  color: white;
  border: 10px solid white;
}
</style>

If anyone has any solutions that could help resolve the auto-scrolling issues in both directions, I would greatly appreciate your insights. Thank you!

Answer №1

Below is the suggested code for your goTo function:

goTo(id) {
  const element = document.querySelector(`#${id}`);
  element.style.position = "static";
  requestAnimationFrame(() => {
    jump(`#${id}`, {
      duration: 300
    });
    element.style.removeProperty('position');
  });
}

View it live here.

The purpose of this code is to ensure proper positioning of the element by temporarily setting its position to static using inline style. This process helps with accurate calculations before reverting the styling in the upcoming animation frame, allowing the element to be displayed as intended by the CSS of the application. The swift execution within a single frame makes the change imperceptible to human eyes.

Just a quick demonstration - for a more Vue-like implementation, consider utilizing refs for improved readability.

If adjusting the element's position briefly seems disruptive and you prefer a gentler approach towards rendered elements, another strategy involves cloning the entire DOM (sans events). Apply the same technique on the clone instead, then transfer the desired value back to the original page. While this method may consume more memory, it minimizes potential side effects on the actual page, ensuring correct layout, painting, and rendering without unintended consequences from factors like viewport intersection listeners, affix plugins, scroll events, etc...

Answer №2

By implementing JumpJS's feature that allows passing in a number instead of a selector, we can retrieve the initial section tops in the mounted() function and utilize them in the gotoId() method.

The jump(number) function requires a number relative to the page's scroll position obtained from location(), which is calculated as

window.scrollY || window.pageYOffset
. To convert this into an absolute value, we need to pass the negative of it to the offset option.

export default {
  name: "App",
  components: {
    SectionItem
  },
  data() {
    return {
      tops: {}
    }
  },
  mounted() {
    this.tops = this.$children.reduce((acc, child) => {
      acc[child.id] = child.$el.getBoundingClientRect().top;
      return acc;
    }, {})
  },
  methods: {
    goTo(id) {
      const offset = -(window.scrollY || window.pageYOffset); // converting jump to an absolute value
      jump(this.tops[id], { duration: 300, offset });
    }
  }
};

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

Timeout error for WebSocket connection on JavaScript client

My first attempt at using websockets is not going as planned. Since my IP address changes frequently, I decided to make the following websocket call on the server-side: $echo = new echoServer("myurl.com","9000"); On the client-side, I'm making the f ...

When using iOS, the video compressing process stops automatically if the screen is no longer active while the file input

I am working on a web application that includes a file upload feature for large videos, typically 30 minutes or longer in duration. When a user on an iOS device selects a video to upload, the operating system will automatically compress it before triggerin ...

The Console.log() function displays the current state and value of a promise object within the Q library

Whenever I attempt to print a promise object from Q, the result that I receive is as follows: var Q = require('q'); var defaultPromise = new Q(); console.log('defaultPromise', defaultPromise); defaultPromise { state: 'fulfilled& ...

Mastering data binding with Vue Js is a process that requires dedication and time

I'm a Vue JS beginner and I've created a component that repeats a grid-like section. However, I've noticed that adding a dropdown in the grid is causing a significant increase in load time. As the number of records grows, the load time will ...

Step-by-step guide for sending data using module.exports in a node.js application

Currently, I am working on implementing a feature that will allow users to input data and store it in a database collection. The technologies I am using for this project are Node.js, MongoDB, Mongoose, Express.js, and AJAX. My goal is to capture user inpu ...

Angular Checkbox Single Select

I have a piece of HTML code with ng-repeat that includes checkboxes. <table> <tr ng-repeat="address in contactInfo.Addresses"> <td>{{address.DisplayType}}</td> <td>{{address.AddressLine1}}</td> <td>{ ...

"Here's a simple guide to generating a random number within a specified range

I have encountered a specific issue: Within an 8-column grid, I am attempting to randomly place an item with a random span width. While I have successfully managed to position the item and give it a random width, I am struggling with adjusting the width b ...

Can JavaScript be utilized to dynamically adjust the size of all elements on the screen to a specified percentage of their initial height and width when a certain event occurs?

I'm fairly new to the world of JavaScript, but I have a basic understanding of it. I want to optimize my personal website for mobile devices. I've already taken care of screen orientation and element positioning, everything is centered nicely and ...

Ajax is updating the initial row of an HTML table while the subsequent rows remain unchanged and retain their default values upon modification

I need help with updating the status of a user, similar to what is discussed in this post. The issue I am facing is that only the first row of the table updates correctly. Regardless of the selected value from the dropdown list on other rows, the displaye ...

What is the best way to refresh a page during an ajax call while also resetting all form fields?

Each time an ajax request is made, the page should refresh without clearing all form fields upon loading Custom Form <form method='post'> <input type='text' placeholder='product'/> <input type='number&a ...

HTML- Any suggestions on how to troubleshoot my sticky navbar not functioning properly?

I'm having trouble creating a sticky navbar. I've attempted to use z-index: 999 but it's not behaving as expected. * { margin: 0; padding: 0; } .navbar { display: flex; align-items: center; justify-items: center; position: ...

The notification bar only makes an appearance when necessary

I am currently working on a dynamic piece of code that sends a request to PHP and receives a response. The goal is for the notification bar to fadeIn() and fadeOut() every time there is a new notification. However, I have encountered an issue where the n ...

Struggling to capture a "moment in time" of a form without losing any of the data

My form is highly dynamic, with interacting top-level elements triggering a complete transformation of the lower-level elements. I needed a method to maintain state so that if users partially entered data in one category, switched temporarily to another, a ...

How can I showcase both a username and email address in a Material UI chip?

I'm currently using Material UI chip to show name and email next to each other. However, when the name is long, the email goes beyond the chip boundary. Here's my function that generates the chips: getGuestList() { let {guests} = this.sta ...

Struggling to update the previousCode state with the useState hook in React

I'm having trouble understanding why the state isn't changing when using setPreviousCode in React and JavaScript. I'm trying to store the previously scanned text in the variable previousCode. import React, { useEffect, useState } from " ...

Issue with JQuery on Mobile Devices: Troubles with Dropdown Menu and Loading Function

Having some trouble with my JQuery code on mobile devices. The drop down menu isn't showing up on an iPhone, the load function isn't working to display the additional PHP page on a Samsung Edge 7, and the drop down doesn't seem to be functio ...

Experience the dynamic synergy of React and typescript combined, harnessing

I am currently utilizing ReactJS with TypeScript. I have been attempting to incorporate a CDN script inside one of my components. Both index.html and .tsx component // .tsx file const handleScript = () => { // There seems to be an issue as the pr ...

I am experiencing an issue where my JSON array is only returning the last element. Any suggestions on how to

I am facing an issue with my JSON array and Ajax code. Here is the snippet of my code where I upload an Excel file, convert it to JSON, then save it as a string in my database: function exportExcelToTable() { $('#upload-excel-convert').chang ...

Adding elements to an array appears to cause the previously created object to react

I am encountering a situation where once I add an object to an array, it becomes reactive to any changes made. // actions.js export const addToCart = ({ commit }) => { commit('addToCart'); // successfully updates the state setTimeout ...

"Discover the process of incorporating two HTML files into a single HTML document with the help of

I successfully created both a bar chart and a pie chart using d3.js individually. Now, I am trying to load these charts into another HTML file using jQuery. $(document).ready(function() { console.log("ready!"); $("#piediv").load("file:///usr/local ...