Эффект стекирования карт на CSS и JavaScript Как создать эффект складывания карт

В этом руководстве мы рассмотрим, как создать эффект наложения карточек, используя закрепленную позицию CSS и API-интерфейс Intersection Observer.



ДЕМО
ИСХОДНИКИ


Основная идея: мы создаем список элементов карты, которые фиксируются при прокрутке. Когда карта зафиксирована, мы уменьшаем ее масштаб и переводим для создания стопки.

Этот урок вдохновлен анимацией карточек на сайте navigator.com.

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), и уменьшаем ее масштаб.

Конец! 🎬


Top

🔖 Выбор по тегам ×

💌 Написать сообщение ×

Все поля обязательны для заполнения!