4 wrz 2019

Animowany baner w js

animowany baner w js

Co to jest ten baner, to nic innego zdjęcie znajdujące się w div, które przesuwa się z góry na dół i ponownie na górę i tak w nieskończoność.
Oczywiście nic nas nie ogranicza można dodać np. jakiś tekst 🙂
Najpierw prosty html, na potrzeby tego przykładu kontener będzie centrowany w pionie i poziomie na całej stronie.

<div class="container">
  <div class="image-bgr">
    <img decoding="async" src="http://www.grzegorztomicki.pl/images/lwow/992/IMG_0014.jpg" alt="" class="image-responsive">
  </div>
</div>


Teraz odpowiedni styl do wyświetlenia tego zdjęcia centralnie.

* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

html {
  height: 100%;
}

body {
  display: flex;
  height: 100%;
  width: 100%;
  justify-content: center;
  align-items: center;
}

.container {
  width: 100%;
  padding: 0 20px;
}

.image-bgr {
  position: relative;
  width: 100%;
  max-width: 900px;
  height: auto;
  min-height: 220px;
  overflow: hidden;
  margin: auto;
}

.image-bgr img {
  width: 100%;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
}

Najważniejsze klasy to w naszym przykładzie ".image-bgr" oraz ".image-bgr img"
Pierwsza klasa tworzy box o wymiarach max 900px i min wysokości 220px.
Do tego posiada właściwość overflow hidden aby ukryć wszystko co wystaję poza obwód boxa.
Druga klasa, a dokładnie ta sam tylko że z elementem img dotyczy zdjęcia. Zdjęcie to ustawiamy jako position absolute. Centrujemy je tak aby było maksymalnie ustawione na 100% szerokości boxa. Do tego zdjęcie jest centrowane w pionie.

Teraz cześć najważniejsza - javascript

function animBanner(config) {
  // sięgamy po box
  const boxScroll = document.querySelector('.image-bgr');
  // sięgamy po zdjęcie
  const image = document.querySelector('.image-responsive');
  // pobieramy rozmiar zdjęcia a dokładnie wysokość
  const imageHeight = image.clientHeight;
  // pobieramy wysokość boxa
  const boxHeight = boxScroll.clientHeight;
  // pobieramy szerokość okna
  const documentWidth = document.body.clientWidth;
  // ustalamy o ile należy przesunąć zdjęcie względem boxa top/bottom
  const margin = (imageHeight - boxHeight) / 2;

  // za każdym razem usuwamy atrybut style z elementu img
  image.removeAttribute('style');

  // ustawiamy nowy atrybut style
  image.setAttribute('style', `animation: ${config}`);

  // wywołujemy funkcję która usuwa style z head
  animClear(image);
  
  // tutaj sprawdzamy czy szerokość okna jest większa czy równa 600px 
  // jeżeli tak wtedy wywołujemy funkcję która tworzy styl z keyframes
  if (documentWidth >= 600) {
    animStyle(margin);
  }
}

// funkcja czyszcząca style
function animClear() {
  const imageStyle = document.querySelector('#animBanner');
  if (imageStyle) {
    imageStyle.parentNode.removeChild(imageStyle);
  }
}

// funkcja tworzące style, które następnie są dodawane do head strony
function animStyle(margin) {

  const keyFrames = animKeyframes(margin);

  const style = document.createElement('style');
  style.type = 'text/css';
  style.id = 'animBanner'
  style.appendChild(document.createTextNode(keyFrames));
  document.head.appendChild(style)

}

// funkcja tworząca animację
function animKeyframes(margin) {
  const percent = `
    0% {
      -webkit-transform: translate3d(0, -${margin}px, 0); 
      transform: translate3d(0, -${margin}px, 0); 
    }
    50% {
      -webkit-transform: translate3d(0, ${margin}px, 0); 
      transform: translate3d(0, ${margin}px, 0); 
    }
    100% {
      -webkit-transform: translate3d(0, -${margin}px, 0); 
      transform: translate3d(0, -${margin}px, 0);
    }
  `;
  return `@-webkit-keyframes animBanner { ${percent} } @keyframes anim { ${percent} }`;
}

// Stała dodawana do elementu image w boxie
// Za pomocą tego stylu uruchamiamy animację, która trwa 25s
// ease-out sposób animacji, a ostatni prametr infinite to określa 
// że animacja będzie odbywać się w nieskończoność
const config = 'animBanner 25s ease-out infinite';

// po załadowaniu całej strony uruchamiamy funkcję animBanner
// do tej funkcji jest przekazywany config
window.addEventListener('DOMContentLoaded', animBanner(config));

// Ta część jest najważniejsza, w związku z tym że strony są responsive
// Wprowadzona tutaj została opcja zmieniania wielkości boxa jak i zdjęcia
// ale żeby wszystkie elementy się skalowały odpowiednio należy za każdym razem
// po zmniejszeniu okna wywołać na nowo funkcję która od nowa nada nowe marginesy itd.
// Tutaj użyłem małego triku które likwiduje problem z resizem i każdorazowym 
// wywołaniem funkcji. Polega on na tym że dopiero po zmniejszeniu i po 260ms 
// jest wywoływana funkcja, jest to podyktowane optymalizacją.
let resizeTimer;
window.addEventListener('resize', () => {
  clearTimeout(resizeTimer);
  resizeTimer = setTimeout(() => {
    animBanner(config);
  }, 250);
});

Co zyskujemy że animacja odbywa się stylach, jest to płynne przewijanie z szybkością przekraczająca 60fps. Czyli animacja bardzo płynna.

Poniżej cały działający przykład.

See the Pen
slow scrolling of the photo
by Greg (@Tomik23)
on CodePen.