Итак, как же управлять скроллом? Да на самом деле все лежит в банальных математических вычислениях высоты (ширины). Разберемся детальнее:

HTML

<div class="item" data-color="#CD5C5C"></div>
<div class="item" data-color="#98FB98"></div>
<div class="item" data-color="#FF1493"></div>
<div class="item" data-color="#FFFF00"></div>
<div class="item" data-color="#00CED1"></div>
<div class="item" data-color="#8A2BE2"></div>
<div class="item" data-color="#191970"></div>
<div class="item" data-color="#778899"></div>
<div class="item" data-color="#FFD700"></div>
<div class="item" data-color="#000000"></div>

Имеем 10 блоков с классом item и data-color. Этот дата-атрибут нужен, чтобы задать нужный цвет блоку.

CSS

* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}

body {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 100px;
}

.item {
width: 500px;
height: 500px;
border: 1px solid #000;
border-radius: 4px;
margin-bottom: 100px;
transition: all 0.3s ease-in-out;
}

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

JS

let $item = $('.item');
let $window = $(window);

function viewedItem() {
let scrollTop = $window.scrollTop();

let scrollBottom = scrollTop + $window.height();

$item.each((index, item) => {
let $currentItem = $(item);

let itemOffsetTop = $currentItem.offset().top;

let itemOffsetBottom = itemOffsetTop + $currentItem.height();

if (scrollTop < itemOffsetBottom && scrollBottom > itemOffsetTop && !$currentItem.attr('data-viewed')) {
let color = $currentItem.attr('data-color');
$currentItem.css('background-color', color);
$currentItem.attr('data-viewed', true);
}
});
}

viewedItem();

$(window).scroll(() => {
viewedItem();
});

Магия здесь! Поехали разбираться)

  1. Создаем функцию viewedItem. Сразу получаем текущий скролл от начала документа в переменную scrollTop. И после этого получаем так же определение scrollBottom, сложив наш скролл и высоту окна браузера. Таким образом мы сможем ловить события не когда начало экрана туда попало, а еще и когда конец. Но об этом дальше.
  2. Пробегаемся по всем нашим блокам с помощью each. Получаем в переменную $currentItem текущий блок и тут же рассчитываем для него расстояние от начала документа и расстояние + высоту. (стоит сделать ремарку — для статичных блоков мы высчитываем это значение один раз, оно сохраняется и все. А вот переменная scrollTop постоянно меняется, т.к. мы использовали там scrollTop, а не offset().top).
  3. А дальше — простые сравнения. Смотрим, чтобы расстояние скролла окна было всегда меньше чем расстояние + высота блока, а так же расстояние + высота окна были больше чем расстояние блока от начала. Таким образом, как только блок попадает в поле видимости — с ним что-то происходит.
  4. И да, там есть еще одно условие — проверяется data-viewed у блока. Напомню, что изначально его нет у блоков. По сути, это просто дата-атрибут — флаг, который позволяет скрипту понять, что над этим блоком уже действия произвелись, значит его трогать уже не надо.
  5. Ну и в самом условии просто берем у текущего элемента его data-color и даем background-color: текущий цвет.
  6. Вызываем функцию один раз чтобы при загрузке страницы уже сработало один раз наше действие. А затем вызываем ее на скролл, чтобы на каждый скролл проверялось наше условие. Вот и все)

И конечно, пен: