предисловие
Это старомодное требование, очень популярное интерактивное требование в эпоху jq. В интернете тоже много информации.Цель написания данной статьи здесь в основном решение некоторых проблем по недостаткам этих материалов:
- Данные навигации потолка и прокрутки независимы, и они анализируются независимо как независимые функции. Я собираюсь проанализировать функцию потолка как одну из функций прокрутки навигации как полный набор решений.
- При разборе потолочной и скролл-навигации большинство пишут планы с позиции верхней навигации, но на самом деле эта функция может появиться где угодно на странице, планы, написанные в этих материалах, лишены расширения сцены. Так как он будет продлен,Необходимо учитывать множество деталей.
- Внедрение навигации с прокруткой часто является отсылкой к традиционному мониторингу.
scroll
Методы иposition: sticky
Метод вводится независимо, позволяя читателям выбрать один. Здесь я объединим два метода, позволю браузеру решить, какой метод использовать, и попытаюсь применить самый простой метод оптимизации.sticky
метод. - практический, раскрыть
sticky
Завеса тайны, дающая понимание, которое отличается от многих источников, указывая на некоторые вводящие в заблуждение описания. -
sticky
в сочетании с традиционными методами.
Давайте посмотрим на общий эффект и практические цели этого времени. В этой статье реализовано руководство по влиянию изменения изображения, чтобы понять схему навигации прокрутки + всасывания потолка, которая будет выражена. Таким образом, вызывая объяснение из практики важнее, чем просто абстрагирование абстрактной схемы, проще для понимания.
С тех пор, как я сделал этот эффект снова через много лет, из горячего jq года наступила эра большего количества фреймворков, я, наконец, инкапсулировал компонент vue, чтобы каждый мог выбрать и использовать.
необходимость
Вот относительно сложный пример,Давайте не будем зацикливаться на этом эффекте верхней панели навигации, его относительно просто добиться, на самом деле этот эффект может появиться в любом месте на странице., хотя реализация по сути такая же, есть еще много мелких деталей, на которые нужно обратить внимание.
Цель, которую мы хотим достичь, — это эффект gif в предисловии выше.
Давайте посмотрим на картинку нижеhtml
структура
<html>
<body>
<div class="left-section"></div>
<div class="right-section">
<div class="top-section"></div>
<div class="nav-bar-wrap">
<ul class="nav-bar">
<li data-content="content1" class="active">导航1</li>
<li data-content="content2">导航2</li>
<li data-content="content3">导航3</li>
</ul>
</div>
<div class="nav-content-container">
<div class="content content1">导航1内容</div>
<div class="content content2">导航2内容</div>
<div class="content content3">导航3内容</div>
</div>
</div>
</body>
</html>
Как видите, панель навигацииnav-bar
, это позиция в середине страницы.
Давайте посмотрим на некоторые ключиcss
стиль (краткое содержание)
html {
height: 100%;
overflow: hidden;
}
body {
padding-top: 24px; /* 这是重点 */
height: 100%;
overflow: auto;
}
.left-section {
display: inline-block;
width: 300px;
}
.right-section {
position: relative; /* 这是重点 */
display: inline-block;
margin-left: 24px;
width: 700px;
}
.nav-bar-wrap {
margin: 16px 0;
height: 55px; /* 这是重点 */
}
.nav-bar {
height: 100%;
}
Вышеуказанные настройки, точка к точке, чтобы сказать эффект
- в основном пусть
body
Становится контейнером прокрутки, элементом, которому принадлежит полоса прокрутки. -
left-section
иright-section
занимать левую и правую стороны страницы - Панель навигации
.nav-bar
родительским элементомnav-bar-wrap
Пакет, цель состоит в том, чтобы занять место! когда панель навигации.nav-bar
После всасывания потолка установитеposition: fixed
, вне документооборота, если нет места для этого родительского элемента, содержимое страницы заполнит вакансию, и страница будет очень негладкой в момент эффекта потолка! -
.right-section
уже настроенposition
, стал навигационным содержимым и панелью навигацииoffsetParent
, а не контейнер с прокруткойbody
Ну, это то, что нужно знать, иbody
уже настроенpadding-top: 24px
.
Приведенные выше настройки создают более сложную ситуацию навигации с прокруткой.
Как только ты подойдешьdemoОтпусти, ты видишь это сейчасcss
все случаи,js
Мы продолжаем говорить о некоторых частях.
Традиционный метод прокрутки мониторинга
Мы по-прежнему вводим два метода сначала по отдельности, а затем объединяем их вместе в конце. Здесь мы в основном говорим о традиционном методе прокрутки.
Идеи:
- монитор
scroll
Событие, когда расстояние прокрутки достигает условия для достижения вершины (на основе панели навигацииoffsetTop
делать расчетные суждения), панель навигации настроена наposition: fixed
; - Запишите содержимое, соответствующее каждой навигации
offsetTop
, как правило, когда расстояние прокрутки больше или равно соответствующему содержимомуoffsetTop
При , установите выбранное состояние панели навигации; - Щелкните элемент навигации на панели навигации, чтобы установить контейнер прокрутки.
scrollTop
, обычно устанавливается на содержаниеoffsetTop
;
Вышеупомянутая самая простая идея. Конечно, будет много деталей, на которые нужно обратить внимание, мы разберемся в этих деталях в следующей пошаговой реализации:
- Когда прокрутка вызвана контейнером, а не содержимым, соответствующим навигации
offsetParent
(объяснение позжеoffsetParent
), так что судитеscrollTop
с содержаниемoffsetTop
, требуются дополнительные расчеты. - Когда панель навигации не находится на верхнем уровне в структуре DOM страницы, как верхняя навигация, а ее ширина больше ширины экрана, например, при сравнении небольшой навигации под div в дереве DOM страницы, когда потолок должен быть выполнен, в связи с установкой
position:fixed;
, если значения ширины и высоты являются относительными величинами, такими как проценты, относительный ориентир изменился, и необходимо разобраться с его шириной и высотой. - Когда содержимое на странице изменяется, например при загрузке данных, страница перестраивается и перерисовывается, в это время должна обновляться сама панель навигации и контейнер, в котором находится соответствующий контент каждой навигации.
offsetTop
, в противном случае это повлияет на последующий расчет и решение. Это важнее, ведь многие страницы уже не статичны, и обновляется не вся страница целиком, а частично. - Когда экран браузера изменится (
resize
), так как это может вызвать перекомпоновку и перерисовку, все же необходимо обновить саму панель навигации и контейнер, в котором находится соответствующий контент каждой навигации.offsetTop
, в противном случае это повлияет на последующий расчет и решение. - Когда полоса прокрутки достигает нижней части, если условие для выбора последней навигации на панели навигации не выполняется, принудительно выбирается последняя навигация. Это небольшая оптимизация во взаимодействии.
offsetParent
Сначала объясните, что такоеoffsetParent
, потому что элементы, использованные позжеoffsetTop
основан на элементахoffsetParent
содержитpadding
Рассчитывается в пределах области.
Обратите внимание, что здесь есть частный случай, когда
offsetParent
даbody
при расчетеoffsetTop
Отbody
изmargin
площадь на счету. Это не упоминается в официальной информации, но я нашел это, когда практиковался.
Ближайший к элементу устанавливаетсяposition
заrelative
,absolute
,sticky
элемент-предок элемента , который является элементомoffsetParent
, если такой настройки в элементе-предке нет, то ближайшийtd
,th
,table
илиbody
элементoffsetParent
.
элементы следующих трех случаевoffsetParent
нулевой:
- Элемент или его родительский элемент установил
display: none
- Элемент устанавливает себя
position:fixed
(Firefox или обратно<body>
) - Элемент
<body>
или<html>
решение
Чтобы всем было легко понять, нижеследующее написано просто и грубо.
привязать слушателя
Сначала мы определяем некоторые необходимые переменные
var navBar = document.querySelector('.nav-bar');
var menu = document.querySelectorAll('.nav-bar li');
var scrollContainer = document.querySelector('body');
var offsetTops = {}; // 存储各个部分的offsetTop
Для контейнера прокрутки, в данном случае тела, создайте привязки событий прокрутки клавиш:
scrollContainer.addEventListener('scroll', handleScroll);
Стоит отметить, что еслиhtml
Если это контейнер, в котором находится свиток, то привяжите слушателяscroll
объектwindow
,использоватьhtml
привязка не работает
window.addEventListener('scroll', handleScroll);
Затем мы получаем панель навигации и каждый навигационный контент.offsetTop
.
calcTop(true);
/**
* 计算页面的各个offsetTop
* @param {Boolean} recalNav - 是否计算导航栏的offsetTop
*/
function calcTop(recalNav) {
recalNav && (offsetTops.navBar = navBar.offsetTop);
['content1', 'content2', 'content3'].forEach(item => {
offsetTops[item] = document.querySelector('.' + item).offsetTop;
});
}
Следует отметить, что до тех пор, пока содержимое страницы изменяется (например, запрос на загрузку данных, а затем рендеринг данных в html), этот метод необходимо вызывать один раз, чтобы убедиться, чтоoffsetTop
Значения актуальны.
среди ключевыхhandleScroll
Это точка, давайте объясним это шаг за шагом:
function handleScroll() {
var top = scrollContainer.scrollTop; // 获取当前滚动条滚动距离
// 这是控制导航栏吸顶 - 吸顶,
// 为什么要减去24呢?
// 因为说了,滚动容器body并不是导航栏和导航内容的offsetParent,所以scrollContainer.scrollTop的距离是从body的paddingTop开始算的
if ((top - 24) >= offsetTops.navBar) {
navBar.style.position = 'fixed';
navBar.style.top = 0;
// 由于变成了`fixed`,基于的父元素变化了,得重新设置下样式,为了维持原来样式的样子
navBar.style.left = '124px';
navBar.style.width = '300px';
navBar.style.height = '55px';
}
// 这是控制导航栏吸顶 - 取消吸顶
if ((top - 24) < offsetTops.navBar) {
navBar.style.position = 'static';
navBar.style.width = '100%';
navBar.style.height = '100%';
}
resetNavSelect();
// 这是吸顶之后用来做衡量的距离值,为什么要加31呢?
// 在不吸顶的情况下,导航指定的内容只要滚动到body顶部就算到了该内容了的导航了,即滚动了【内容的offsetTop + body的paddingTop】的距离
// 但是吸顶之后,只要滚动到吸顶导航栏底部就算到了指定导航内容了,所以相当于只要滚动【内容的offsetTop + body的paddingTop - 吸顶导航栏的高度】的距离就会到达临界值
// 转换成公式来理解,c代表导航内容的offsetTop,s代表滚动的距离,body的paddingTop为24,吸顶导航栏高度为55。只要滚动距离大于等于上面说的临界值,即肯定到达了对应导航。
// 因此公式为: s >= c + 24 - 55, 即 s + 31 >= c 时,到达条件成立,因此滚动容器的scrollTop都要加上31,才是拿来判断的值
var fixedBaseTop = top + 31;
var menuLength = menu.length;
// 滚动条到达底部就选中最后一个导航
if (top + scrollContainer.clientHeight >= scrollContainer.scrollHeight) {
menu[menuLength - 1].className = 'active';
return;
}
// 以下都为依据滚动自动选择对应导航
// 当滚动到导航内容到达吸顶后的导航栏底部之后,且其接着的导航内容尚未到达导航栏之前,即为该导航内容的导航选中情况
// for循环里的执行情况不包括对导航栏最后一个导航做判断
for (var i = 0; i < menuLength - 1; i++) {
if (fixedBaseTop >= offsetTops['content' + (i + 1)] && fixedBaseTop < offsetTops['content' + (i + 2)]) {
menu[i].className = 'active';
return;
}
}
// 这里是对最后一个导航做判断,如果它已到达导航栏底部之时之后,就选中它
if (fixedBaseTop >= offsetTops['content' + (menuLength - 1)]) {
menu[menuLength - 1].className = 'active';
return;
}
// 没有一个情况符合就选中第一个导航。
menu[0].className = 'active';
}
Приведенные выше комментарии к коду ясно показали, что если тело контейнера прокрутки не является offsetParent панели навигации и содержимого навигации, то какой вид расчета смещения необходимо выполнить. Как правило, необходимо учитывать контейнер прокрутки, панель навигации и содержимое навигации.offsetParent
Расстояние между верхней частью панели навигации и после нее считается высотой самой панели навигации.
А при поднятии потолка надо делать дополнение к стилю навигационной панели.Пример здесь относительно простой.До и после поднятия потолка это фиксированное значение px,но когда макет усложняется , ваша навигационная панель Сами ширина и высота рассчитываются на основе исходного процента.В это время навигационной панели должно быть присвоено значение после поднятия потолка.
Затем мы добавляем событие прослушивателя для пересчета панели навигации и содержимого навигации для изменения размера страницы браузера.offsetTop
window.addEventListener('resize', hanldeResize);
function hanldeResize() {
calcTop(true);
}
Поскольку размер страницы браузера изменяется, содержимое страницы также может измениться, что приведет к начальному вычислениюoffsetTop
Он устаревает и требует своевременного обновления, чтобы обеспечить правильное сравнение навигации по прокрутке.
Нажмите, чтобы перейти к содержимому навигации
На данный момент основная основная логика обработана. Последняя часть относительно проста, то есть нажмите на навигацию, и страница прокрутится до местоположения содержимого навигации.
navBar.onclick = selectNav;
/**
* 选择标题跳到对应内容
*/
function selectNav(e) {
var ev = e || event;
var target = ev.target || ev.srcElement; // 兼容IE
this.resetNavSelect();
target.className = 'active';
scrollContainer.scrollTop = offsetTops[target.getAttribute('data-content')] - 31;
}
назначается вышеописанным способомscrollTop
Есть минус 31. Это значение отклонения, объясненное выше, равно 31. Поскольку навигация основана на этом значении отклонения для определения местоположения навигации, разумно сказать, что щелчок по панели навигации для перехода к содержимому навигации также рассчитывается на основе этого значения. значение отклонения. Конечно, у вас есть личность, и думать по-другому — это нормально.
резюме
Пока что на этом введение в реализацию традиционного метода заканчивается. Хотя метод написания вышеприведенного примера не оптимален, он сделан для удобства всеобщего понимания.Вот и все.Позже вы можете посмотреть на метод написания в инкапсулированном компоненте vue.Многие детали рассмотрения находятся вprop
Это также ясно с первого взгляда.
новая функция css - липкая
sticky
как естьposition
Значение , как следует из названия, означает липкость.Разве не очень ярко прилипать к панели навигации?
Некрасивые слова раньше,sticky
Совместимость не очень, сами видитездесь
Честно говоря, есть много источников о введении этой функции, но я чувствую, что описание не очень точное, особенно распространенные говорят, что это междуrelative
иfixed
Переключение между этим описанием, я нахожу его очень вводящим в заблуждение.
Начнем с общего утверждения:
правильный наборsticky
Элемент при выполнении определенных условий будет липким и сохранит исходное положение без изменений, как если бы он был залип, а при невыполнении условий будет такой же, как и обычный эффект.
Итак, каковы условия? я перечисляю
липкое состояние
для установкиposition: sticky
Для элемента , я буду описывать его как «липкий элемент позиционирования» на данный момент в этой статье, а так называемая «липкость» может быть сгенерирована только при соблюдении следующих условий:
- Обязательно установите свойство ориентации (сверху/слева/снизу/справа)
- Липкий позиционированный элемент (без поля) и его ближайший предок
scrolling box
(включая границу, заполнение) расстояние, меньшее или равное установленному порогу атрибута ориентации. Если поля прокрутки нет, оно будет рассчитано в соответствии с областью просмотра. - Как судить о том, достигнут ли порог на основе этого
scrolling box
Событие прокрутки подразумевает, чтоscrolling box
Он должен быть прокручиваемым (в направлении заданного вами свойства направления, если вы хотите прокручивать вертикально, он должен прокручиваться в вертикальном направлении), и он вступит в силу только при отслеживании прокрутки (это означает, что если установленоoverflow: hidden
малоэффективен) и, кроме того, не будет затронут другими предкамиscrolling box
эффект прокрутки. - Видимая область родительского элемента может вмещать липкие позиционированные элементы. Обычно происходит, когда родительский элемент не является контейнером прокрутки. Конкретный случай этого будет объяснен ниже.
Подытожим и упростим описание приведенных выше условий:
Липкие элементы позиционирования должны находиться в прокручиваемом контейнере и должны устанавливать свойство ориентации, которое используется в качестве ближайшего предка.scrolling box
Расстояние сравнивается, и оно вступает в силу, когда оно меньше или равно. Однако под влиянием прокрутки, если видимая область не может вместить липкий элемент позиционирования, липкость также исчезнет.
для вышеперечисленногоscrolling box
, именуемый в этой статье «передвижным контейнером».
Поле прокрутки: содержит полосы прокрутки или установлено
overflow
Контейнер свойства css, обратите внимание, что необходимо установить переполнение, независимо от того, скрыто оно или установить переполнение в одном направлении, например, переполнение-x
Представление
Будем знакомы,sticky
Некоторые из конкретных перфомансов, после прочтения которых вы, вероятно, знаете, что это за процесс и эффект. (обратите внимание на жирную часть шрифта)
Когда вы устанавливаете свойство ориентации, напримерtop: 0
, то в качестве порога используется значение атрибута ориентации. Когда расстояние между закрепленным позиционирующим элементом и областью в пределах границы контейнера прокрутки (то есть поле не включено) равно порогу, элемент закрепляется , то есть позиция не изменится.Это как залипнуть в ту позицию, а потом дистанция меньше порога, и это все та же позиция которая застряла в этот момент.
Обратите внимание, что прилипание означает, что положение не изменится, другие стили, такие как ширина и высота, остаются такими же, как и раньше., что особенно подчеркивается, потому что многие данные будут использоватьfixed
Чтобы выразить это липкое поведение, это не строго, это действительно становитсяfixed
, процентная основа стилей ширины и высоты станет страницей, но липкое поведение здесь,Проценты ширины и высоты основаны на родительском элементе..
Что еще более важно, после возникновения прилипания исходное положение потока документов также будет сохранено и не будет отделено от потока документов. Чтобы мы могли понять,Sticky прилипает к родительскому элементу, а не страница.
Когда липкость не действует
Липкие позиционированные элементы ведут себя какstatic
, увидеть много информации, такой какrelative
, но практика показала, что чем ближеstatic
, потому что свойство установки ориентации не действует до тех пор, пока не вступит в силу липкость, иrelative
Действует свойство ориентации.
Когда липкость вступает в силу
Если расстояние между липким элементом позиционирования и ближайшим контейнером прокрутки меньше заданного порога атрибута ориентации, когда липкий элемент позиционирования не является липким, когда после прокрутки оно становится меньше или равно пороговому значению, будет создан эффект липкости, который отображается как залипание, когда он равен порогу.
Однако, если расстояние между липким элементом позиционирования и ближайшим контейнером прокрутки больше установленного порога атрибута ориентации, когда липкий элемент позиционирования не является липким в начале, установка порога эквивалентна позиционированию, напримерtop: 100px
, тогда липкий элемент позиционирования (исключая поля) сразу же прикрепится к месту на расстоянии 100 пикселей от ближайшего контейнера прокрутки (включая рамку и отступы). Конечно, в это время липкий элемент все еще находится на родительском элементе.
Когда родитель закрепленного позиционированного элемента не является контейнером прокрутки
Вот пример вертикальной прокрутки.
Когда возникает эффект прилипания, продолжайте прокручивать вниз, а родительский элемент будет продолжать прокручиваться вверх (это нормальное поведение).Если собственная блочная модель закрепленного позиционирующего элемента (включая поля) достигает нижней части родительского элемента, продолжайте чтобы прокрутить вниз липкий элемент позиционирования, он будет свернут вверх (из перспективы можно понять, что эффект липкости исчез).
Это можно понять следующим образом: поскольку липкость относится к области родительского элемента, если родительский элемент оборачивает липкий фиксированный элемент, а область полностью свернута, позиционирующий элемент естественным образом будет следовать за родительским элементом, чтобы свернуться.
Это случай последнего пункта в эффективных условиях, упомянутых выше.
(Для этой части описания в других статьях будет описано, что высота родительского элемента больше, чем у элемента, они говорят из заключения, здесь я буду более понятен из природы явления)
применяется к этому примеру
Мы закончили вышеперечисленноеsticky
Пришло время применить это знание к моему примеру выше.sticky
Он используется только для достижения эффекта потолка, поэтому функции других частей (навигация по прокрутке, переход по содержимому навигации и т. д.) по-прежнему необходимы. Ниже приведена только часть, реализующая эффект потолка.
все еще оригинальныйhtml
иcss
, добавьте следующееcss
.nav-bar-wrap {
position: sticky;
/* 24是body的paddingTop */
top: -24px;
}
Готово! Разве это не супер просто~
Этот код css заменяет этот код js в традиционной схеме, и нет необходимости рассчитывать стиль после отсоса потолка:
// 这是控制导航栏吸顶 - 吸顶
if ((top + extraFixed) >= offsetTops.navBar) {
navBar.style.position = 'fixed';
navBar.style.top = 0;
navBar.style.left = '124px';
navBar.style.width = '300px';
navBar.style.height = '55px';
}
// 这是控制导航栏吸顶 - 取消吸顶
if ((top + extraFixed) < offsetTops.navBar) {
navBar.style.position = 'static';
navBar.style.width = '100%';
navBar.style.height = '100%';
}
Сочетание двух
Сочетание этих двух способов означает, что в зависимости от того, поддерживает ли браузерsticky
, чтобы судить об использованииcss
способ эффекта потолка, илиjs
Управляйте потолком.
Но, если честно, если вы четко не знаете, в каком браузере вы разрабатываете страницу (или не имеете этого требования), то вам нужно только написать соответствующий кусок кода во время разработки.
Но если вы не уверены, к какому браузеру он применяется, или если вы хотите адаптироваться к большинству браузеров, их комбинация не избавит вас от усилий по написанию кода, то есть обе реализации должны быть написаны, но также напишите суждение, какой метод фактически используется. Смысл этого только в том, что вы можете использоватьcss
просто используйтеcss
Минимизация операций dom — это оптимизация производительности. Если у вас нет этого занятия, вы можете написать традиционные.
Добавьте проверку, чтобы узнать, поддерживает ли его браузер.sticky
Методы
var isSupportSticky = validateSticky();
/**
* 检查浏览器是否有支持的sticky值,没有返回false,有就添加sticky相关css,实现吸顶
*/
function validateSticky () {
var supportStickyValue = valiateCssValue('position', 'sticky');
if (supportStickyValue) {
var navBarWrap = document.querySelector('.nav-bar-wrap');
navBarWrap.style.position = supportStickyValue;
navBarWrap.style.top = '-24px';
return true;
}
return false;
}
/**
* 检查浏览器是否支持某个css属性值
*/
function valiateCssValue (key, value) {
var prefix = ['-o-', '-ms-', '-moz-', '-webkit-', ''];
var prefixValue = [];
for (var i = 0; i < prefix.length; i++) {
prefixValue.push(prefix[i] + value)
}
var element = document.createElement('div');
var eleStyle = element.style;
for (var j = 0; j < prefixValue.length; j++) {
eleStyle[key] = prefixValue[j];
}
return eleStyle[key];
}
здесьvaliateCssValue
метод, подробно описанный в другой моей статьеjs оценивает и информирует поддержку о свойствах CSS (значениях)
Тогда нам просто нужноhandleScroll
Просто внесите небольшую корректировку в метод и добавьте ее в логику, которая обрабатывает потолок.isSupportSticky
суждение
function handleScroll() {
...
if (!isSupportSticky) {
// 这是控制导航栏吸顶 - 吸顶
if ((top + extraFixed) >= offsetTops.navBar) { ... }
// 这是控制导航栏吸顶 - 取消吸顶
if ((top + extraFixed) < offsetTops.navBar) { ... }
}
...
}
Суммировать
Статья в основном начинается с реализации практического примера для объяснения основных идей, а также различных проблем, которые могут возникнуть в середине, и деталей, которые необходимо рассмотреть. Примеры реализации статьиdemo
Я считаю, что все умные люди, и они могут делать выводы по аналогии, пока они овладевают необходимыми знаниями, какими бы сложными они ни были, они могут столкнуться с этим.
Наконец, он также предоставляет упакованный компонент на основе Vue, из которого вы можете почувствовать абстрактную архитектуру решения.
адрес нпмvue-scroll-nav
github vue-scroll-nav
Пожалуйста, не воспроизводите без разрешения