Комбинированная схема навигации с прокруткой + потолок (включая подробное объяснение)

JavaScript

предисловие

Это старомодное требование, очень популярное интерактивное требование в эпоху jq. В интернете тоже много информации.Цель написания данной статьи здесь в основном решение некоторых проблем по недостаткам этих материалов:

  • Данные навигации потолка и прокрутки независимы, и они анализируются независимо как независимые функции. Я собираюсь проанализировать функцию потолка как одну из функций прокрутки навигации как полный набор решений.
  • При разборе потолочной и скролл-навигации большинство пишут планы с позиции верхней навигации, но на самом деле эта функция может появиться где угодно на странице, планы, написанные в этих материалах, лишены расширения сцены. Так как он будет продлен,Необходимо учитывать множество деталей.
  • Внедрение навигации с прокруткой часто является отсылкой к традиционному мониторингу.scrollМетоды иposition: stickyМетод вводится независимо, позволяя читателям выбрать один. Здесь я объединим два метода, позволю браузеру решить, какой метод использовать, и попытаюсь применить самый простой метод оптимизации.stickyметод.
  • практический, раскрытьstickyЗавеса тайны, дающая понимание, которое отличается от многих источников, указывая на некоторые вводящие в заблуждение описания.
  • stickyв сочетании с традиционными методами.

Давайте посмотрим на общий эффект и практические цели этого времени. В этой статье реализовано руководство по влиянию изменения изображения, чтобы понять схему навигации прокрутки + всасывания потолка, которая будет выражена. Таким образом, вызывая объяснение из практики важнее, чем просто абстрагирование абстрактной схемы, проще для понимания.

image
[Сюда лучше поставить гифку, чтобы увидеть эффект]

С тех пор, как я сделал этот эффект снова через много лет, из горячего jq года наступила эра большего количества фреймворков, я, наконец, инкапсулировал компонент vue, чтобы каждый мог выбрать и использовать.

необходимость

Вот относительно сложный пример,Давайте не будем зацикливаться на этом эффекте верхней панели навигации, его относительно просто добиться, на самом деле этот эффект может появиться в любом месте на странице., хотя реализация по сути такая же, есть еще много мелких деталей, на которые нужно обратить внимание.

Цель, которую мы хотим достичь, — это эффект gif в предисловии выше.

image

Давайте посмотрим на картинку ниже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

Пожалуйста, не воспроизводите без разрешения