首页 > 解决方案 > 在滚动时固定/修复 Vanilla Javascript 中的多个元素

问题描述

我是 javascript 的初学者,我正在尝试使用 es6 规范构建一些东西。

我想在向下滚动页面时从 ScrollMagic 重新创建 pin 效果并固定不同的部分。

所以我有这个简单的 html 标记,带有页眉、页脚和 3 个部分:

<header class="forewords">
 <h1>Some text</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>some text</h1>
</footer>

我附加了一些样式来模拟现实情况。

这里是javascript逻辑:

获取所有项目:

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);
};

window.addEventListener('resize', updateProjectsOffsetTop);

如果滚动大于其偏移量,则最终固定元素。

function pinElement() {

  if (window.scrollY >= projectsOffsetTop[1]) {
    document.body.style.paddingTop = projectsHeight[1] +'px';
    projects[1].classList.add('fixed');
  } else {
    document.body.style.paddingTop = 0;
    projects[1].classList.remove('fixed');
  }

};

window.addEventListener('scroll', pinElement);

但我不能让它与所有项目元素一起工作。即使使用 for 循环。最佳做法是什么?如果可能的话,我想在 Vanilla ES6 中解决这个问题。

找到附件完整的 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() {

      if (window.scrollY >= projectsOffsetTop[1]) {
        document.body.style.paddingTop = projectsHeight[1] +'px';
        projects[1].classList.add('fixed');
      } else {
        document.body.style.paddingTop = 0;
        projects[1].classList.remove('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>

标签: javascriptecmascript-6

解决方案


你提供了一个令人惊叹的 MCVE 来工作,所以非常感谢你花这么多精力和时间来提出一个很好的问题。好消息是你快到了!你的逻辑是合理的,一切都是有道理的。您真正缺少的是:

  • 重置样式的逻辑的正确位置(主体顶部填充和删除fixed类)
  • .project获取最接近但大于滚动高度的元素的索引

您想要在您的pinElement()方法中执行以下操作:

  1. 首先重置/取消修复所有内容
  2. 获取projectsOffsetTop大于scrollY但最接近它的值(这样它将成为我们要固定的元素)
  3. 从中获取.project该值所属元素的索引
  4. 如果索引是-1(即我们没有符合第 2 点标准的元素),则return停止执行。
  5. 否则,我们将执行您在原始方法中的逻辑,但替换1为我们在步骤 3 中确定的索引。

考虑到这一点,这是您稍微重构的pinElement()方法:

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);

  // If index is not found, we don't do anything
  if (idx === -1)
    return;

  // 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');
  }

};

有趣的提示:您可以使用模板文字来执行此操作:

document.body.style.paddingTop = `${projectsHeight[idx]}px`;

…而不是这个:

document.body.style.paddingTop = ${projectsHeight[idx] + 'px';

这是一个概念验证示例:

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);
  
  // If index is not found, we don't do anything
  if (idx === -1)
    return;

  // 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>


附带说明一下,出于性能原因,您可能需要考虑限制/消除滚动事件,以免pinElement()过度调用。


推荐阅读