JS30. Задание 13 Slide in on Scroll

Демо https://js3013.github.io
Код https://github.com/js3013/js3013.github.io

Анимация при скролле есть в  jQuery.
Демо: https://js3012jquery.github.io/
Код: https://github.com/js3012jQuery/js3012jQuery.github.io
На всё про всё ушло полчаса времени. Хоть можно было уложиться минут в 10-15.
Количество кода - три строки


$(function(){
new WOW().init();
});

Подключили jQuery (85кБ), подключили плагин wow (15кБ).
Из дополнительных файлов ещё animate.css (2КБ).
То есть общий вес дополнительного кода около 100кБ, это сильно меньше веса одной картинки. Понятно почему jQuery так популярен.

Замечу, что анимация при скролле хорошо выглядит с небольшими картинками, но если размер фото во всю ширину, анимация такого большого фото выглядит неважно.
И анимация с jQuery определённо симпатичнее, чем с чистым js. Первая реагирует на расстояние картинки от низа страницы, вторая, кажется, зависит от времени.

Посмотрим, насколько сложнее анимация при скролле реализуется на чистом js.

Посмотрела.
Начну с очевидного: любую функциональность в js можно реализовать при помощи разного кода. Чем круче и профессиональнее разработчик, тем проще и короче его код (парадокс, но так и есть).

Код Wess Boss-a, автора курса, занимает 34 строки. Это достаточно много, хоть и не предел. Здесь, например, анимация при прокрутке занимает 70+ строчек кода. Что ещё раз подтверждает мысль о том, что учат в интернете все, кому не лень. В том числе и совсем непрофессионалы.

Кроме того, при прокрутке страницы нужно, чтобы событие прокрутки в js (быстрое) успевало за анимацией DOM (медленная).

Чтобы не допустить конфликта js с DOM, автор использует setTimeOut, замедляя таким образом реакцию браузера на обработку события прокрутки. Из-за этой прописанной в коде задержки, если прокручивать страницу быстро, картинки не появляются вовсе. Не очень хорошо, если не сказать, совсем плохо.

Зато здесь нашла прекрасный код в котором всего 13 строк и который отлично обрабатывает событие прокрутки. Осталось понять как этот код работает и немного улучшить его. Мне не нравится, что анимация происходит только после того, как картинка полностью показалась в окне браузера. Было бы лучше, если бы анимация начиналась когда картинка только частично появилась в окне

window.addEventListener("DOMContentLoaded", anim, false)
window.addEventListener("scroll", anim, false);

function anim() {
    [].forEach.call(document.querySelectorAll('img'), function(el) {
        checkViewport(el) ? el.classList.add("active") : el.classList.remove("active")
    });
}

function checkViewport(a) {
    var b = a.getBoundingClientRect();
    return 0 < b.top && b.top + a.scrollHeight < window.innerHeight
};


1. window.addEventListener("DOMContentLoaded", anim, false)

насколько я понимаю, эта строка всего лишь показывает, что функция anim может начинать работу только когда произошло событие DOMContentLoaded - браузер полностью загрузил HTML и построил DOM-дерево.
То есть, это всего лишь один из многих способов запустить js-код только после построения DOM. Имхо, строкой можно пренебречь, параметр defer при подключении js мне нравится больше

2. window.addEventListener("scroll", anim, false);

Когда происходит событие "scroll" (прокрутка страницы), выполняется функция anim. О значении последнего false не догадываюсь и где прочитать не знаю.

Нашла. Этот третий параметр называется useCapture, является факультативным (не обязательным), его значение по умолчанию равно false, а вот если мы напишем true, это будет означать, что захват нужно начинать немедленно. Стал необязательным только после Firefox 6.0. То есть false можно спокойно удалить?

Проверила. Без false строка работает ничуть не хуже

window.addEventListener("scroll", anim);

3-7.

function anim() {
    [].forEach.call(document.querySelectorAll('img'), function(el) {
        checkViewport(el) ? el.classList.add("active") : el.classList.remove("active")
    });
}


function anim() - функция анимации. Её задача добавить картинке  класс "active" (el.classList.add("active")), когда она появилась в окне браузера (checkViewport(el) ) и убрать этот класс (el.classList.remove("active")), когда картинка вышла за пределы окна.

[].forEach.call - это безобразие нужно для того, чтобы использовать метод forEach для результатов поиска document.querySelectorAll('img'), которые, как известно, являются объектом NodeList, и для которого раньше метод forEach не работал. Но сейчас-то он работает. Попробую изменить код:

var imgs = document.querySelectorAll("img");

function anim() {
    imgs.forEach(function(img) {
        checkViewport(img) ? img.classList.add("active") : img.classList.remove("active")
    });
}



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

var imgs = document.querySelectorAll("img");

Затем создаю функцию  anim, которая для каждой картинки imgs.forEach вызывает анонимную функцию с аргументом img, внутри которой один единственный тернарный оператор, проверяющий появилась ли картика в окне браузера checkViewport(img)? и в зависимости от этого добавляющего или удаляющего у него класс "active".

11-14. Последняя, самая сложная функция function checkViewport(a)

function checkViewport(a) {
    var b = a.getBoundingClientRect();
    return 0 < b.top && b.top + a.scrollHeight < window.innerHeight
};


function checkViewport(a) - проверяет находится ли картинка в окне браузера. Параметр этой функции а это и есть наша картинка.

Дальше мы создаём переменную b

var b = a.getBoundingClientRect();

Метод Element.getBoundingClientRect() возвращает размер элемента и его позицию относительно окна.

Возвращаемое значение — это объект TextRectangle, содержащий свойства только для чтения left, top, right и bottom, описывающие бокс с границами в пиксельном измерении. Значения top и left даются относительно верхнего левого угла порта просмотра. mdn

То есть b содержит значение высоты элемента в окне браузера
Дальше мы проверяем несколько вещей

0 < b.top - я бы написала b.top > 0 - высота элемента больше 0. То есть элемент отображается в окне браузера. Уже хорошо

b.top + a.scrollHeight < window.innerHeight - высота элемента который отображается в окне браузера вместе с общей высотой элемента меньше, чем  высота видимого окна браузера? Уму непостижимо, что же он здесь определяет.

Если предположить, что нужно определить был ли элемент полностью прокручен, то для этого достаточно строки

a.scrollHeight - a.scrollTop === a.clientHeight

Нет, неправильно. Буду думать дальше.
Как-то ничего не придумалось (
Зато код переделала так, как хотела

function checkViewport(a) {
    var b = a.getBoundingClientRect();
    return -0.5 * a.scrollHeight < b.top && b.top + 0.5 * a.scrollHeight < window.innerHeight;
};


Теперь элемент анимируется когда половина его появилась в окне браузера снизу (b.top + 0.5 * a.scrollHeight < window.innerHeight) и когда половина скрылась в окне браузера сверху (-0.5 * a.scrollHeight < b.top)