Я новичок в 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>
при изменении размера работает нормально. Я просто хочу знать, как выполнить итерацию последней части кода, той, которая исправляет элемент, для всего раздела :)
Вы предоставили потрясающий MCVE для работы, поэтому большое вам спасибо за то, что вы потратили столько усилий и времени, чтобы задать отличный вопрос. Хорошая новость в том, что вы там почти! Ваша логика верна, и все имеет смысл. Что вам действительно не хватает, так это:
fixed
).project
, ближайшего, но превышающего высоту прокруткиЧто вы хотите сделать в своем методе pinElement()
, так это следующее:
projectsOffsetTop
, которое больше, чем scrollY, но самое близкое к нему (чтобы это был элемент, который мы хотим закрепить).project
, которому принадлежит это значение.-1
(т.е. у нас нет элемента, который соответствует критериям в пункте 2), return
и остановите выполнение.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 А, часть Math.max.apply
просто получает максимальное значение из массива чисел :) stackoverflow.com/questions/1669190/… Вы также можете использовать распространение объекта в ES6, если хотите.
Я сделал несколько тестов, и некоторые проблемы все еще остаются. Например, если я изменяю размер окон, когда некоторые элементы уже исправлены, я теряю заголовок, и все ломается. Более того, возможно, то, что я удаляю класс в качестве первой операции функции, а затем возвращаю его каждый раз при прокрутке, является основной проблемой с точки зрения производительности. Может быть, я мог бы отметить фиксированный элемент и снять флаг после того, как второй получит фиксированный класс?
@mdash Вероятно, вам также придется повторно запустить метод pinElement()
при изменении размера. Это должно исправить это.
на
resize
ничего не происходит, да?