2

In my previous post I asked for help in make some section fixed while scrolling in Vanilla ES6 without frameworks.

Simple HTML structure:

<header class="forewords"><h1>Lorem some</h1>
</header>

<div class="wrapper">
 <section class="project" id="item1">this is section 1</section>
 <section class="project" id="item2">this is section 2</section>
 <section class="project" id="item3">this is section 3</section>
</div>

<footer class="endings"><h1>Lorem something.</h1>
</footer>

This is the core function that does the work:

function pinElement() {

  // Reset all styles 
  projects.forEach((project) => {
    document.body.style.paddingTop = 0;
    project.classList.remove('fixed');
  });

  // Get the index of the project that is closest to top
  const valueClosestToScrollY = Math.max.apply(Math, projectsOffsetTop.filter((offsetTop) => offsetTop <= window.scrollY));
  const idx = projectsOffsetTop.indexOf(valueClosestToScrollY);


  // Otherwise, we set the appropriate styles and classes
  if (window.scrollY >= projectsOffsetTop[idx]) {
    document.body.style.paddingTop = `${projectsHeight[idx]}px`;
    projects[idx].classList.add('fixed');
  } 

};

window.addEventListener('scroll', pinElement);

Right now I'm experiencing two kind of logic issues, the reason why I choose to open another topic.

First of all, every time i scroll first I call the classList.remove('.fixed') function on everything and suddenly I call classList.add('.fixed') on the selected element.

This cause a "flickering" effect during all the scroll where the class in removed/added continuosly (having a negative impact on the overall performance I guess).

Is there a way to solve this?

Second issue is that I've added an event listener to update the values of offsets and heights of my elements when somebody resize the windows.

function updateProjectsOffsetTop() {
  projectsOffsetTop = projects.map(project => project.offsetTop);
  projectsHeight = projects.map(project => project.offsetHeight);
};

window.addEventListener('resize', updateProjectsOffsetTop);

This works fine if I resize the windows before starting scrolling. But if i resize the windows after starting my scroll everything broke.

I can't figure out a way to update the value live and pass them to the scroll function. I would like to solve this code with Vanilla ES6 if it's possible, because I'm trying to learn Javascript starting from that spec.

I know that maybe this are simple issue but I'm here to learn :)

Thanks in advance and find attached the complete JS Fiddle.

const projects = Array.from(document.querySelectorAll('.project'));
    let projectsOffsetTop = projects.map(project => project.offsetTop);
    let projectsHeight = projects.map(project => project.offsetHeight);

    function updateProjectsOffsetTop() {
      projectsOffsetTop = projects.map(project => project.offsetTop);
      projectsHeight = projects.map(project => project.offsetHeight);
    };


    function pinElement() {

      // Reset all styles 
      projects.forEach((project) => {
        document.body.style.paddingTop = 0;
        project.classList.remove('fixed');
      });

      // Get the index of the project that is closest to top
      const valueClosestToScrollY = Math.max.apply(Math, projectsOffsetTop.filter((offsetTop) => offsetTop <= window.scrollY));
      const idx = projectsOffsetTop.indexOf(valueClosestToScrollY);


      // Otherwise, we set the appropriate styles and classes
      if (window.scrollY >= projectsOffsetTop[idx]) {
        document.body.style.paddingTop = `${projectsHeight[idx]}px`;
        projects[idx].classList.add('fixed');
      } 

    };

    window.addEventListener('resize', updateProjectsOffsetTop);
    window.addEventListener('scroll', pinElement);
html {
      box-sizing: border-box;
    }

    *, *::before, *::after {
      box-sizing: inherit;
      margin: 0;
      padding: 0;
    }

    header, footer {
      width: 100%;
      padding: 10%;
      background-color: grey;
      position: relative;
    }

    .project {
      width: 100%;
      height: 100vh;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      top: 0;
    }

    #item1 {background-color: yellow;}
    #item2 {background-color: blue;}
    #item3 {background-color: red;}


    .fixed {
      position: fixed;
    }
<header class="forewords"><h1>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Harum soluta ipsam quaerat cupiditate neque, necessitatibus amet nihil perferendis sunt minus! Exercitationem nulla inventore, aut beatae magnam, totam et minus hic.</h1>
  </header>

  <div class="wrapper">
    <section class="project" id="item1">this is section 1</section>
    <section class="project" id="item2">this is section 2</section>
    <section class="project" id="item3">this is section 3</section>
  </div>

  <footer class="endings"><h1>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repudiandae vel, perferendis ullam totam recusandae sed repellendus cum! Molestiae, aut ut sequi eos quidem nam quo est, ad tempora inventore odit.</h1>
  </footer>
mdash
  • 153
  • 2
  • 12
  • Without reading it all, have you looked at position sticky in CSS? – powerbuoy May 04 '18 at 15:10
  • I'm not noticing the error you're describing. This may be speed dependent or content heavy dependent causing a slowdown in the rendering. It might be best to wrap the part of the code that adds and removes the fixed class inside a call to `requestAnimationFrame` to avoid flickering(do the rest of the logic, then just quickly remove then immediately readd the class at render) – Robert Mennell May 04 '18 at 15:10
  • @powerbuoy he states in the question that even though there might be other simpler ways to solve this, he wants to do it with JS for learning purposes – Robert Mennell May 04 '18 at 15:11
  • Oh, I probably should've read it all then :P – powerbuoy May 04 '18 at 15:12

1 Answers1

1

You need to make sure you don't have intermitant renders between removal and addition of the class. Those redraws are causing it to rerender at different points, making it flicker. Try nesting the removal and set of the fixed class inside of a call to request animation frame. In fact your pinning function is small enough you could probably set it all inside a call to it like so:

function pinElement() {
  window.requestAnimationFrame(() => {
    // Reset all styles 
    projects.forEach((project) => {
      document.body.style.paddingTop = 0;
      project.classList.remove('fixed');
    });

    // Get the index of the project that is closest to top
    const valueClosestToScrollY = Math.max.apply(Math, projectsOffsetTop.filter((offsetTop) => offsetTop <= window.scrollY));
    const idx = projectsOffsetTop.indexOf(valueClosestToScrollY);


    // Otherwise, we set the appropriate styles and classes
    if (window.scrollY >= projectsOffsetTop[idx]) {
      document.body.style.paddingTop = `${projectsHeight[idx]}px`;
      projects[idx].classList.add('fixed');
    }
  });

};

window.addEventListener('scroll', pinElement);

Updated your snippet in this answer to test your comment, appears to be working just fine for me: EDIT: Just noticed my above example is missing a closure. fixed

const projects = Array.from(document.querySelectorAll('.project'));
    let projectsOffsetTop = projects.map(project => project.offsetTop);
    let projectsHeight = projects.map(project => project.offsetHeight);

    function updateProjectsOffsetTop() {
      projectsOffsetTop = projects.map(project => project.offsetTop);
      projectsHeight = projects.map(project => project.offsetHeight);
    };


    function pinElement() {
      window.requestAnimationFrame(() =>{
      // Reset all styles 
      projects.forEach((project) => {
        document.body.style.paddingTop = 0;
        project.classList.remove('fixed');
      });

      // Get the index of the project that is closest to top
      const valueClosestToScrollY = Math.max.apply(Math, projectsOffsetTop.filter((offsetTop) => offsetTop <= window.scrollY));
      const idx = projectsOffsetTop.indexOf(valueClosestToScrollY);


      // Otherwise, we set the appropriate styles and classes
      if (window.scrollY >= projectsOffsetTop[idx]) {
        document.body.style.paddingTop = `${projectsHeight[idx]}px`;
        projects[idx].classList.add('fixed');
      } 
      })

    };

    window.addEventListener('resize', updateProjectsOffsetTop);
    window.addEventListener('scroll', pinElement);
html {
      box-sizing: border-box;
    }

    *, *::before, *::after {
      box-sizing: inherit;
      margin: 0;
      padding: 0;
    }

    header, footer {
      width: 100%;
      padding: 10%;
      background-color: grey;
      position: relative;
    }

    .project {
      width: 100%;
      height: 100vh;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      top: 0;
    }

    #item1 {background-color: yellow;}
    #item2 {background-color: blue;}
    #item3 {background-color: red;}


    .fixed {
      position: fixed;
    }
<header class="forewords"><h1>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Harum soluta ipsam quaerat cupiditate neque, necessitatibus amet nihil perferendis sunt minus! Exercitationem nulla inventore, aut beatae magnam, totam et minus hic.</h1>
  </header>

  <div class="wrapper">
    <section class="project" id="item1">this is section 1</section>
    <section class="project" id="item2">this is section 2</section>
    <section class="project" id="item3">this is section 3</section>
  </div>

  <footer class="endings"><h1>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repudiandae vel, perferendis ullam totam recusandae sed repellendus cum! Molestiae, aut ut sequi eos quidem nam quo est, ad tempora inventore odit.</h1>
  </footer>
Robert Mennell
  • 1,954
  • 11
  • 24
  • Thanks, i should try the requestAnimationFrame. The main problem is the resizing issue. everything get messy when I resize my browser after a little bit of scrolling :( – mdash May 04 '18 at 15:35
  • Try the animation frame. If the fixes the flickering then we can move on(it should really be a separate question after the flickering fix is found) – Robert Mennell May 04 '18 at 15:37
  • maybe the performance issue is just my concern, because the pinning is working properly without it. the only thing that's really going wrong is the resizing – mdash May 04 '18 at 18:15
  • @mdash updated your snippet and example above it. Noticed I was missing a closure. Give it a try – Robert Mennell May 04 '18 at 18:15
  • Did it fix the flickering – Robert Mennell May 04 '18 at 18:37
  • If I inspect the dom with chrome I see that the class is flickering when it's on an element. I don't have enough experience to tell if this is the best way to perform this function. Maybe I should add the class fixed, get the element height and the scrollposition and after somebody scroll more than the object height, remove the class. But in terms of "what i see" it's not flickering! – mdash May 04 '18 at 18:40
  • Glad it fixed the flickering problem. The redraw is just indicutave of rendering. Before it was doing it twice. Now it's only doing it once – Robert Mennell May 04 '18 at 18:50
  • @mdash don't forget to ask another question about the resize – Robert Mennell May 04 '18 at 19:26
  • the script work properly on Chrome but on Safari everyting is flickering the same way :( – mdash May 05 '18 at 11:29
  • Then daftari had not implemented request animation frame correctly, which doesn't surprise me. Their javascript environment sucks – Robert Mennell May 05 '18 at 16:56