В этом руководстве мы рассмотрим, как создать эффект наложения карточек, используя закрепленную позицию CSS и API-интерфейс Intersection Observer.
Основная идея: мы создаем список элементов карты, которые фиксируются при прокрутке. Когда карта зафиксирована, мы уменьшаем ее масштаб и переводим для создания стопки.
Этот урок вдохновлен анимацией карточек на сайте navigator.com.
Структура HTML - это список элементов карты:
<ul class="stack-cards js-stack-cards">
<li class="stack-cards__item js-stack-cards__item">
<!-- Content here -->
</li>
<li class="stack-cards__item js-stack-cards__item">
<!-- Content here -->
</li>
<!-- additional card items here -->
</ul>
Мы можем использовать закрепленное значение свойства CSS position
и применить его к элементам .stack-cards__item:
.stack-cards__item {
position: sticky;
top: var(--space-sm);
transform-origin: center top;
}
Примечание. В приведенном выше фрагменте мы используем переменную интервала --space-sm
, определенную в структуре (значение по умолчанию - 0,75em).
Поскольку элемент .stack-cards__item
имеет закрепленную позицию, как только смещение между ним и окном просмотра становится равным --space-sm (top: var (- space-sm))
, элемент становится фиксированным. По умолчанию каждая карта имеет значение translateY
, равное промежутку между картами. Следовательно, даже если карты имеют одинаковое верхнее значение, они смещены (offset = translateY
).
Мы также изменили источник преобразования элемента карты; нам это понадобится для создания эффекта стекирования при уменьшении карты.
Давайте воспользуемся API-интерфейсом Intersection Observer, чтобы определить, когда элементы карточки входят в область просмотра, и изменить их значение преобразования на основе прокрутки.
Мы можем определить объект StackCards
, который мы используем для инициализации эффекта наложения:
var StackCards = function(element) {
this.element = element;
this.items = this.element.getElementsByClassName('js-stack-cards__item');
this.scrollingListener = false;
this.scrolling = false;
initStackCardsEffect(this);
};
function initStackCardsEffect(element) {
// we'll create the effect here
};
var stackCards = document.getElementsByClassName('js-stack-cards'),
intersectionObserverSupported = ('IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype),
reducedMotion = Util.osHasReducedMotion();
if(stackCards.length > 0 && intersectionObserverSupported && !reducedMotion) {
for(var i = 0; i < stackCards.length; i++) {
new StackCards(stackCards[i]);
}
}
Эффект будет работать, только если поддерживается API-интерфейс Intersection Observer (intersectionObserverSupported === true
) и если Reduces Motion не включен (мы используем служебную функцию osHasReducedMotion
, чтобы проверить это).
Функция initStackCardsEffect
определяет, когда карты входят в область просмотра:
function initStackCardsEffect(element) { // use Intersection Observer to trigger animation
var observer = new IntersectionObserver(stackCardsCallback.bind(element));
observer.observe(element.element);
};
function stackCardsCallback(entries) { // Intersection Observer callback
if(entries[0].isIntersecting) { // cards inside viewport - add scroll listener
if(this.scrollingListener) return; // listener for scroll event already added
stackCardsInitEvent(this);
} else { // cards not inside viewport - remove scroll listener
if(!this.scrollingListener) return; // listener for scroll event already removed
window.removeEventListener('scroll', this.scrollingListener);
this.scrollingListener = false;
}
};
function stackCardsInitEvent(element) {
element.scrollingListener = stackCardsScrolling.bind(element);
window.addEventListener('scroll', element.scrollingListener);
};
function stackCardsScrolling() {
if(this.scrolling) return;
this.scrolling = true;
window.requestAnimationFrame(animateStackCards.bind(this));
};
function animateStackCards() {
// apply transform values to different card elements
};
Когда элемент .js-stack-cards
находится внутри области просмотра (entry [0] .isIntersecting == true в функции stackCardsCallback ()
), мы просматриваем событие прокрутки окна и соответственно обновляем значение преобразования каждого элемента карт (animateStackCards ( )
функция):
function animateStackCards() {
var top = this.element.getBoundingClientRect().top;
for(var i = 0; i < this.items.length; i++) {
// cardTop/cardHeight/marginY are the css values for the card top position/height/Y offset
var scrolling = this.cardTop - top - i*(this.cardHeight+this.marginY);
if(scrolling > 0) { // card is fixed - we can scale it down
this.items[i].setAttribute('style', 'transform: translateY('+this.marginY*i+'px) scale('+(this.cardHeight - scrolling*0.05)/this.cardHeight+');');
}
}
this.scrolling = false;
};
В функции animateStackCards ()
мы проверяем, зафиксирована ли карта (scrolling > 0
), и уменьшаем ее масштаб.
Конец! 🎬