20 августа 2020 г.

Прокрутка

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

Например:

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

Вот небольшая функция для отображения текущей прокрутки:

window.addEventListener('scroll', function() {
  document.getElementById('showScroll').innerHTML = pageYOffset + 'px';
});

В действии:

Текущая прокрутка = прокрутите окно

Событие scroll работает как на window, так и на других элементах, на которых включена прокрутка.

Предотвращение прокрутки

Как можно сделать что-то непрокручиваемым?

Нельзя предотвратить прокрутку, используя event.preventDefault() в обработчике onscroll, потому что он срабатывает после того, как прокрутка уже произошла.

Но можно предотвратить прокрутку, используя event.preventDefault() на событии, которое вызывает прокрутку, например, на событии keydown для клавиш pageUp и pageDown.

Если поставить на них обработчики, в которых вызвать event.preventDefault(), то прокрутка не начнётся.

Способов инициировать прокрутку много, поэтому более надёжный способ – использовать CSS, свойство overflow.

Вот несколько задач, которые вы можете решить или просмотреть, чтобы увидеть применение onscroll.

Задачи

важность: 5

Создайте бесконечную страницу. Когда посетитель прокручивает её до конца, она автоматически добавляет текущие время и дату в текст (чтобы посетитель мог прокрутить ещё).

Как тут:

Пожалуйста, обратите внимание на две важные особенности прокрутки:

  1. Прокрутка «эластична». Можно прокрутить немного дальше начала или конца документа на некоторых браузерах/устройствах (после появляется пустое место, а затем документ автоматически «отскакивает» к нормальному состоянию).
  2. Прокрутка неточна. Если прокрутить страницу до конца, можно оказаться в 0-50px от реальной нижней границы документа.

Таким образом, «прокрутка до конца» должна означать, что посетитель находится на расстоянии не более 100px от конца документа.

P.S. В реальной жизни мы можем захотеть показать «больше сообщений» или «больше товаров».

Открыть песочницу для задачи.

Основа решения – функция, которая добавляет больше дат на страницу (или загружает больше материала в реальной жизни), пока мы находимся в конце этой страницы.

Мы можем вызвать её сразу же и добавить как обработчик для window.onscroll.

Самый важный вопрос: «Как обнаружить, что страница прокручена к самому низу?»

Давайте используем координаты относительно окна.

Документ представлен тегом <html> (и содержится в нём же), который доступен как document.documentElement.

Так что мы можем получить его координаты относительно окна как document.documentElement.getBoundingClientRect(), свойство bottom будет координатой нижней границы документа относительно окна.

Например, если высота всего HTML-документа 2000px, тогда:

// когда мы находимся вверху страницы
// координата top относительно окна равна 0
document.documentElement.getBoundingClientRect().top = 0

// координата bottom относительно окна равна 2000
// документ длинный, вероятно, далеко за пределами нижней части окна
document.documentElement.getBoundingClientRect().bottom = 2000

Если прокрутить 500px вниз, тогда:

// верх документа находится выше окна на 500px
document.documentElement.getBoundingClientRect().top = -500
// низ документа на 500px ближе
document.documentElement.getBoundingClientRect().bottom = 1500

Когда мы прокручиваем до конца, предполагая, что высота окна 600px:

// верх документа находится выше окна на 1400px
document.documentElement.getBoundingClientRect().top = -1400
// низ документа находится ниже окна на 600px
document.documentElement.getBoundingClientRect().bottom = 600

Пожалуйста, обратите внимание, что bottom не может быть 0, потому что низ документа никогда не достигнет верха окна. Нижним пределом координаты bottom является высота окна (выше мы предположили, что это 600), больше прокручивать вверх нельзя.

Получить высоту окна можно как document.documentElement.clientHeight.

Для нашей задачи мы хотим знать, когда нижняя граница документа находится не более чем в 100px от неё (т.е. 600-700px, если высота 600).

Итак, вот функция:

function populate() {
  while(true) {
    // нижняя граница документа
    let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom;

    // если пользователь прокрутил достаточно далеко (< 100px до конца)
    if (windowRelativeBottom < document.documentElement.clientHeight + 100) {
      // добавим больше данных
      document.body.insertAdjacentHTML("beforeend", `<p>Дата: ${new Date()}</p>`);
    }
  }
}

Открыть решение в песочнице.

важность: 5

Создайте кнопку «наверх», чтобы помочь с прокруткой страницы.

Она должна работать следующим образом:

  • Пока страница не прокручена вниз хотя бы на высоту окна – кнопка невидима.
  • Когда страница прокручена вниз больше, чем на высоту окна – появляется стрелка «наверх» в левом верхнем углу. Если страница прокручивается назад, стрелка исчезает.
  • Когда нажимается стрелка, страница прокручивается вверх.

Как тут (слева-сверху, прокрутите):

Открыть песочницу для задачи.

важность: 4

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

Для этого мы решили не показывать изображения сразу, а заменять их на «макеты», как тут:

<img src="placeholder.svg" width="128" height="128" data-src="real.jpg">

То есть, изначально, все изображения – placeholder.svg. Когда страница прокручивается до того положения, где пользователь может увидеть изображение – мы меняем src на значение из data-src, и таким образом изображение загружается.

Вот пример в iframe:

Прокрутите его, чтобы увидеть загрузку изображений «по требованию».

Требования:

  • При загрузке страницы те изображения, которые уже видимы, должны загружаться сразу же, не ожидая прокрутки.
  • Некоторые изображения могут быть обычными, без data-src. Код не должен касаться их.
  • Если изображение один раз загрузилось, оно не должно больше перезагружаться при прокрутке.

P.S. Если можете, реализуйте более продвинутое решение, которое будет загружать изображения на одну страницу ниже/после текущей позиции.

P.P.S. Достаточно обрабатывать вертикальную прокрутку, горизонтальную не требуется.

Открыть песочницу для задачи.

Обработчик onscroll должен проверить, какие изображения видимы, и показать их.

Мы также можем запустить его при загрузке страницы, чтобы сразу обнаружить видимые изображения и загрузить их.

Код должен выполниться, когда документ загружен, чтобы у него был доступ к его содержимому.

Можно разместить его перед закрывающим тегом </body>:

// ...содержимое страницы выше...

function isVisible(elem) {

  let coords = elem.getBoundingClientRect();

  let windowHeight = document.documentElement.clientHeight;

  // верхний край элемента виден?
  let topVisible = coords.top > 0 && coords.top < windowHeight;

  // нижний край элемента виден?
  let bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;

  return topVisible || bottomVisible;
}

Функция showVisible() использует проверку на видимость, реализованную в isVisible() для загрузки видимых картинок:

function showVisible() {
  for (let img of document.querySelectorAll('img')) {
    let realSrc = img.dataset.src;
    if (!realSrc) continue;

    if (isVisible(img)) {
      img.src = realSrc;
      img.dataset.src = '';
    }
  }
}

showVisible();
window.onscroll = showVisible;

P.S. В решении этой задачи есть также вариант isVisible, который предварительно загружает изображения, находящиеся в пределах одной страницы выше/ниже от текущей прокрутки документа.

Открыть решение в песочнице.

Карта учебника

Комментарии

перед тем как писать…
  • Если вам кажется, что в статье что-то не так - вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.