Закрепить / исправить несколько элементов в Vanilla Javascript при прокрутке

Я новичок в javascript, и я пытаюсь создать что-то со спецификацией es6.

Я хотел бы воссоздать эффекты булавки из ScrollMagic и закрепить другой раздел, пока я прокручиваю страницу вниз.

Итак, у меня есть эта простая разметка 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.

Спасибо

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>

на resize ничего не происходит, да?

Muhammad Usman 02.05.2018 16:35

при изменении размера работает нормально. Я просто хочу знать, как выполнить итерацию последней части кода, той, которая исправляет элемент, для всего раздела :)

mdash 02.05.2018 16:36
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
2
667
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы предоставили потрясающий 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() не вызывался чрезмерно.

Большое спасибо за решение! Мне нужно изучить эту штуку с Math.max, но все остальное совершенно ясно. Я попытался добавить debounce, но количество секунд, необходимых для хорошей работы, не сильно отличается от нормальной частоты обновления, я думаю (с 10 миллисекундами все еще есть некоторые скачки: D) Я не знаю о троттлинге. Может, это лучшее решение.

mdash 02.05.2018 17:56

@mdash А, часть Math.max.apply просто получает максимальное значение из массива чисел :) stackoverflow.com/questions/1669190/… Вы также можете использовать распространение объекта в ES6, если хотите.

Terry 02.05.2018 18:36

Я сделал несколько тестов, и некоторые проблемы все еще остаются. Например, если я изменяю размер окон, когда некоторые элементы уже исправлены, я теряю заголовок, и все ломается. Более того, возможно, то, что я удаляю класс в качестве первой операции функции, а затем возвращаю его каждый раз при прокрутке, является основной проблемой с точки зрения производительности. Может быть, я мог бы отметить фиксированный элемент и снять флаг после того, как второй получит фиксированный класс?

mdash 04.05.2018 14:18

@mdash Вероятно, вам также придется повторно запустить метод pinElement() при изменении размера. Это должно исправить это.

Terry 04.05.2018 22:48

Другие вопросы по теме