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

Using cookies in JavaScript for saving and fetching data

Currently, I am delving into the world of Javascript and attempting to work with cookies. Although my code appears to be correct, there seems to be an issue that I can't quite pinpoint. function init() { var panel = document.getElementById("panel ...

The 'utf-8' codec is unable to interpret the byte 0x96 at position 227, as it is an invalid start byte

Recently, I began working with Django. Unfortunately, I encountered an error that I haven't been able to solve. Can anyone explain why this error is occurring? Specifically, I am utilizing {% extends "base.html" %} on my login page. Here is the exa ...

I am unable to implement code coverage for Cypress in Debian at the moment

Having trouble obtaining code coverage results using Cypress in my Virtual Machine (Debian Bullseye), but no issues when I run the same project on my Windows machine. On Linux, the code coverage shows up as: Click here to view Index.html inside lcov-repor ...

Transforming dynamic class based on state value from React to TypeScript

I'm trying to implement this React function in TypeScript, but I'm encountering errors. const ListItem = ({ text }) => { let [showMore, setShowMore] = useState(false); return ( <div className="item"> ...

Passing data from input to Angular's $scope (filter)

I'm currently working on my first custom filter, and while the basic principle is functioning (it displays all the CDs of an artist from 7 and up), I'm encountering an issue where the filter doesn't update when I change the value. It consist ...

In the event that the hash consists of just one string, disregard any additional conditional statements

Currently, I am in the process of updating one of my coding playgrounds and facing some issues. If the user has only the "result" string in the hash like this... testhash.html#d81441bc3488494beef1ff548bbff6c2?result I want to display only the result ( ...

Flag is to be set while moving through the input fields

I am currently working on a form with multiple text fields and a text area. I have implemented a loop to go through each of these fields and check if there is a value present. If the field is empty, I set a flag to pass=false. On the other hand, if a value ...

Creating a carousel in AngularJS using ng-repeat without resorting to jQuery

Looking for a solution to display elements on my page with ng-repeat in a carousel-like fashion. Each element should have a photo, short description, and reveal a long description on click. It's important that it's mobile-friendly so users can sw ...

Personalized typeface for stencil.js element

I am currently working on a stencil component and I need to specify a font for it. Here is what I have so far: index.html <body> <sidebar-component webpagename="dashboard"></sidebar-component> </body> <style> * { m ...

Issues with posting form data in AngularJS are occurring

Here is the code I am currently using: On the Angular side vm.onSubmit = function(){ var person = vm.formData.slice(0, 1)[0]; //This line extracts the required fields from the model object which is nested in an array. $http({ ...

Step-by-step guide on accessing values from a JavaScript object

I have a dictionary variable declared within a script tag on an HTML page. My goal is to retrieve the values associated with the "Roads" and "Intersections" keys, which change each time the page is refreshed. By capturing these values, I can then use JavaS ...

Vue2 Component not displaying changes in data updates

I'm facing an issue where a Vue 2 component fails to render upon updating its data: Vue.component('image-slider', { data: function() { return { name : "Image1", src : "https://via.placeholder.com/250" } }, ...

Merging angular-file-upload with multer

I am facing a challenge in integrating the angular file upload plugin with multer to create a fully Single Page Application (SPA). I am currently stuck on uploading multiple files through multer. Below is how my multer options are set up in my node route. ...

CSS margin-left causing issues in Chrome Extension

My current situation is incredibly puzzling, as I am at a loss for what is happening. I developed a Firefox add-on using jQuery and CSS to revamp a website. When attempting to transfer the add-on to Chrome (which was relatively straightforward due to the s ...

The MUI Select component does not support using a Fragment as a child. Please consider using an array instead

I encountered some console errors while working with this react function component, specifically receiving the following error message: The MUI Select component does not accept a Fragment as a child. It is recommended to provide an array instead. functi ...

Change the URL structure from ex.com/forum?id=1 to ex.com/#/forum?id=1 in AngularJS

Hey there! I'm in the process of creating a Forum using AngularJS and need some guidance. First things first! I've successfully established a connection to my database with: <?php session_start(); $db = new mysqli("localhost","root",""," ...

What is the most concise way to align one table row in the middle and another at the top?

Is there a way to align different rows in a table vertically with minimal code? I have a table with 3 rows, and I want the first two rows to be aligned vertically to the top, while the third row should be vertically aligned to the middle. If I try to def ...

Making changes to a variable or option through the props in a different file

In the index.js file, I have implemented getStaticProps() to fetch data from an exported function. This function requires 2 parameters, one for the type of listing and the quantity. export async function getStaticProps() { const type = 0 const data = a ...

React JS component experiencing issues with Material UI modal functionality

Attempting to reproduce the material ui modal example has proven to be a challenge for me. Initially, I encountered an error stating "Cannot read property 'setState' of undefined" which I managed to resolve. However, even after resolving this iss ...

CSS overflow property does not take effect when set to hidden

I'm having trouble understanding why the overflow: hidden property is being overridden by a descendant element. Take a look at this example I've put together http://jsfiddle.net/2fRxc/. HTML <div class="relative"> <div class="overf ...