Как применить ко многим элементам: JavaScript эффект прокрутки для элемента, только когда родительский элемент находится в области просмотра - PullRequest
1 голос
/ 25 января 2020

У меня есть набор 100vh разделов с child изображениями и текстом. Я хотел бы применить прокрутку transform к тексту, чтобы создать эффект параллакса, который перемещает текст по изображению. Я заставил его работать на один элемент, используя vanilla javascript, однако я не совсем понимаю, как применить один и тот же эффект к нескольким разделам с одинаковым class. В настоящее время эффект применяется ко всем divs, независимо от того, находится ли их родительский элемент section в области просмотра.

Можете ли вы помочь мне отредактировать javascript таким образом, чтобы дочерний элемент div с class scroll преобразовывался, только если его родительский элемент section находится в области просмотра?

Спасибо,

    window.addEventListener('scroll', function(e) {
        var section = document.querySelectorAll('.section');
        var length = section.length

        for (var i = 0; i < length; i++) {
            var bounding = section[i].getBoundingClientRect();
            if (
                bounding.top >=0 || bounding.bottom >=0
            ) {
                const target = section[i].querySelector('.scroll');
                var rate = window.pageYOffset * -0.5;
                target.style.transform = 'translate3d(0px, '+rate+'px, 0px)'
            } 
          }
    });
/*!
Theme Name: Frozen Land
Author: Will Caulfield
Author URI: http://caulfield.co/
Description: Description
Version: 1.0.0
License: GNU General Public License v2 or later
License URI: LICENSE
Text Domain: frozenland.co
*/
body {
  font-family: 'Lato', sans-serif;
}

p, i {
  color: #9c5f89;
}

h1 {
  color: white;
  text-transform: uppercase;
  letter-spacing: 2px;
  font-weight: 800;
  font-size: calc(70px + 0.3vw);
  margin: 10px 0px 25px 0px;
}

h3 {
  color: white;
  text-transform: uppercase;
  letter-spacing: 2px;
  font-weight: 800;
  font-size: calc(30px + 0.3vw);
  margin: 10px 0px 25px 0px;
}

main {
  background: -webkit-gradient(linear, left top, left bottom, from(#f9c4cc), to(#f27aaa));
  background: linear-gradient(#f9c4cc, #f27aaa);
}

footer {
  background-color: #c9db79;
  height: 500px;
}

section.local {
  height: 300px;
}

section.hero {
  height: 100vh;
}

@-webkit-keyframes floatIce {
  0% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-15px);
            transform: translatey(-15px);
  }
  100% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
}

@keyframes floatIce {
  0% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-15px);
            transform: translatey(-15px);
  }
  100% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
}

@-webkit-keyframes floatText {
  0% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-10px);
            transform: translatey(-10px);
  }
  100% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
}

@keyframes floatText {
  0% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-10px);
            transform: translatey(-10px);
  }
  100% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
}

@-webkit-keyframes floatLand {
  0% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-5px);
            transform: translatey(-5px);
  }
  100% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
}

@keyframes floatLand {
  0% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-5px);
            transform: translatey(-5px);
  }
  100% {
    -webkit-transform: translatey(0px);
            transform: translatey(0px);
  }
}

.hero img {
  margin-top: 100px;
  margin-bottom: 100px;
  width: 500px;
}

div.hero {
  position: relative;
  top: -50px;
}

.hero-ice {
  width: 200px;
  -webkit-transform: translatey(0px);
          transform: translatey(0px);
  -webkit-animation: floatIce 6s ease-in-out infinite;
          animation: floatIce 6s ease-in-out infinite;
}

.hero-text {
  -webkit-transform: translatey(0px);
          transform: translatey(0px);
  -webkit-animation: floatText 6s ease-in-out infinite;
          animation: floatText 6s ease-in-out infinite;
}

.hero-land {
  width: 250px;
  -webkit-transform: translatey(0px);
          transform: translatey(0px);
  -webkit-animation: floatText 6s ease-in-out infinite;
          animation: floatText 6s ease-in-out infinite;
}

.candy-float {
  width: 50px;
}

section.cone {
  height: 100vh;
}

.cone img {
  width: 300px;
}

nav img {
  width: 300px;
  margin-top: 15px;
}

section.froyo {
  height: 100vh;
  margin-top: 150px;
}

.froyo img {
  width: 400px;
}

.froyo div {
  position: absolute;
}

section.toppings {
  height: 100vh;
  margin-top: 150px;
}

.toppings img {
  width: 400px;
}

.toppings div {
  position: absolute;
}
/*# sourceMappingURL=style.css.map */
<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

    <!-- Custom CSS -->

    <link rel="stylesheet" href="css/style.css">

    <!-- Googl Fonts -->

    <link href="https://fonts.googleapis.com/css?family=Lato:400,900&display=swap" rel="stylesheet">

    <!-- Font Awesome -->

    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.12.0/css/all.css" integrity="sha384-REHJTs1r2ErKBuJB0fCK99gCYsVjwxHrSU0N7I1zl9vZbggVJXRMsv/sLlOAGb4M" crossorigin="anonymous">

    <!-- Scripts -->

    <title>Hello, world!</title>
  </head>
  <body>

    <main>

        <section class="hero section text-center d-flex flex-column justify-content-center align-items-center position-relative">
            <img class="scroll" data-rate="-0.5" src="img/logo.png" />
        </section>

        <section class="cone section text-center d-flex flex-column justify-content-center align-items-center position-relative">
            <img src="img/cone.png"/>
            <div class="scroll" data-rate="-0.5">
                <h3>delicious</h3>
                <h1>ice cream</h1>
            </div>
            
        </section>

        <section class="froyo section text-center d-flex flex-column justify-content-center align-items-center position-relative">
            <img src="img/froyo.png"/>
            <div class="scroll" data-rate="-0.5">
                <h3>Frozen</h3>
                <h1>Yogurt</h1>
            </div>
        </section>

        <section class="toppings section text-center d-flex flex-column justify-content-center align-items-center position-relative">
            <img src="img/toppings.png" />
            <div class="scroll">
                <h3>and lots of</h3>
                <h1>toppings!</h1>
            </div>
        </section>

    </main>

    <footer class="container-fluid">
        <div class="row">
            <div class="col">
            </div>
            <div class="col text-center pt-5">
                <p>10911 Lindbrook Drive<br/>Los Angeles, CA 90024</p>
                <p>(310) 824-8191</p>
                <p>&copy; 2020</p>
            </div>
            <div class="col">
                
            </div>
        </div>

    </footer>

    <script>

        // window.addEventListener('scroll', function(e) {
        //     var section = document.querySelector('.section');
        //     var bounding = section.getBoundingClientRect();
        //     if (
        //         bounding.top >=0 || bounding.bottom >=0
        //     ) {
        //         const target = section.querySelector('.scroll');
        //         var rate = window.pageYOffset * -0.5;

        //         target.style.transform = 'translate3d(0px, '+rate+'px, 0px)'
        //     } else {
        //         console.log("Not in Viewport!");
        //     }
        // });

        

        // window.addEventListener('scroll', function(e) {
        //     const target = document.querySelectorAll('.scroll');

            

        //     //var scrolled = window.pageYOffset;
        //     //var rate = scrolled * -0.5;

        //     //target.style.transform = 'translate3d(0px, '+rate+'px, 0px)'
        //     var index = 0, length = target.length;
        //     for (index; index < length; index++) {
        //         var pos = window.pageYOffset * target[index].dataset.rate;
                
        //         target[index].style.transform = 'translate3d(0px, '+pos+'px, 0px)';
        //     };
        // });
    </script>





    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>

    <!-- Other Scripts -->
    <script src="/js/main.js"></script>
    
  </body>
</html>

1 Ответ

2 голосов
/ 25 января 2020

Используйте IntersectionObserver API для наблюдателя, если элементы видны или нет. Этот API-интерфейс отслеживает указанные вами элементы и вызывает обратный вызов всякий раз, когда элемент наблюдаемый входит или выходит из области просмотра. Таким образом, вы можете создать список элементов, которые находятся в области просмотра.

Чтобы помочь с этим, создайте Set. Наборы принимают любой тип значения и сохраняют его в списке, например массив с наддувом, но только с уникальными значениями. Поэтому ни один элемент не будет там дважды.

В intersectionCallback проверьте, покидает ли элемент область просмотра, и добавьте его в набор. Если он покидает экран, удалите его из набора.

Тогда вместо того, чтобы зацикливаться на каждом .section l oop над набором с разделами, которые в данный момент находятся в представлении. Содержимое этого набора постоянно изменяется в зависимости от того, где в данный момент находится позиция прокрутки.

В обратном вызове события scroll вычисляется позиция раздела относительно верхней части экрана. Вам нужно будет смещать каждое преобразование в секцию, чтобы получить правильное положение ваших элементов параллакса.

Надеюсь, это поможет вам.
Дайте мне знать, если что-нибудь здесь неясно или я не помог вам в этом.

Приветствия!

Приложение

Я чувствовал, что представление было немного дерганным, поэтому я посмотрел код улучшения. Первым было добавление passive listener в прослушиватель событий scroll, который говорит слушателю не ждать event.preventDefault(). Поскольку он не будет ждать, он будет циклически ускоряться до следующего запуска события scroll.

Затем выполняются вычисления. Каждый раз, когда происходит событие scroll, вычисляется смещение. Но смещение не изменяется при прокрутке, поэтому имеет смысл сначала рассчитать его, а затем использовать при необходимости. То же самое касается выбора элемента target, который также остается неизменным.

Итак, я добавил два Map объекта, которые содержат связь между сечением и их смещением. + цели. Таким образом, теперь функция прокрутки получит ранее рассчитанное смещение и целевое значение на основе текущего section, над которым она зацикливается.

Все эти добавления значительно улучшат производительность прокрутки и сделают ваши эффекты параллакса более плавными. Я знаю, что это много кода, и я не ожидаю, что все сразу имеет смысл, но если вы хотите, я мог бы попытаться объяснить, что все делает и почему / как вы его используете.

const inViewSections = new Set();
const offsetMap = new Map();
const targetMap = new Map();

function calculateOffsetsOfSections(sections, map) {
  const bodyTop = document.body.getBoundingClientRect().top;
  sections.forEach(function(section) {
    const sectionTop = section.getBoundingClientRect().top;
    const offset = sectionTop - bodyTop;
    map.set(section, offset);
  });
}

function mapTargetsOfSections(sections, map) {
  sections.forEach(function(section) {
    const target = section.querySelector('.scroll');
    if (target !== null) {
      map.set(section, target);
    }
  });
}

function observeSections(sections, observer) {
  sections.forEach(function(section) {
    observer.observe(section);
  });
}

function intersectionCallback(entries) {
  entries.forEach(function(entry) {
    if (entry.isIntersecting === true) {
      inViewSections.add(entry.target);
    } else {
      inViewSections.delete(entry.target);
    }
  });
}

const observer = new IntersectionObserver(intersectionCallback, {
  root: null,
  rootMargin: '0px',
  threshold: [0]
});

let sections = document.querySelectorAll('.section');
calculateOffsetsOfSections(sections, offsetMap);
mapTargetsOfSections(sections, targetMap);
observeSections(sections, observer);

window.addEventListener('resize', function(e) {
  calculateOffsetsOfSections(sections, offsetMap);
});

window.addEventListener('scroll', function(e) {
  inViewSections.forEach(function(section) {
    if (offsetMap.has(section) && targetMap.has(section)) {
      const target = targetMap.get(section);
      const offset = offsetMap.get(section);
      var rate = Math.round((window.pageYOffset - offset) * -0.5);
      target.style.transform = 'translate3d(0px, ' + rate + 'px, 0px)';
    }
  });
}, {passive: true});
/*!
Theme Name: Frozen Land
Author: Will Caulfield
Author URI: http://caulfield.co/
Description: Description
Version: 1.0.0
License: GNU General Public License v2 or later
License URI: LICENSE
Text Domain: frozenland.co
*/

body {
  font-family: 'Lato', sans-serif;
}

p,
i {
  color: #9c5f89;
}

h1 {
  color: white;
  text-transform: uppercase;
  letter-spacing: 2px;
  font-weight: 800;
  font-size: calc(70px + 0.3vw);
  margin: 10px 0px 25px 0px;
}

h3 {
  color: white;
  text-transform: uppercase;
  letter-spacing: 2px;
  font-weight: 800;
  font-size: calc(30px + 0.3vw);
  margin: 10px 0px 25px 0px;
}

main {
  background: -webkit-gradient(linear, left top, left bottom, from(#f9c4cc), to(#f27aaa));
  background: linear-gradient(#f9c4cc, #f27aaa);
}

footer {
  background-color: #c9db79;
  height: 500px;
}

section.local {
  height: 300px;
}

section.hero {
  height: 100vh;
}

@-webkit-keyframes floatIce {
  0% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-15px);
    transform: translatey(-15px);
  }
  100% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
}

@keyframes floatIce {
  0% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-15px);
    transform: translatey(-15px);
  }
  100% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
}

@-webkit-keyframes floatText {
  0% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-10px);
    transform: translatey(-10px);
  }
  100% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
}

@keyframes floatText {
  0% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-10px);
    transform: translatey(-10px);
  }
  100% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
}

@-webkit-keyframes floatLand {
  0% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-5px);
    transform: translatey(-5px);
  }
  100% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
}

@keyframes floatLand {
  0% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
  50% {
    -webkit-transform: translatey(-5px);
    transform: translatey(-5px);
  }
  100% {
    -webkit-transform: translatey(0px);
    transform: translatey(0px);
  }
}

.hero img {
  margin-top: 100px;
  margin-bottom: 100px;
  width: 500px;
}

div.hero {
  position: relative;
  top: -50px;
}

.hero-ice {
  width: 200px;
  -webkit-transform: translatey(0px);
  transform: translatey(0px);
  -webkit-animation: floatIce 6s ease-in-out infinite;
  animation: floatIce 6s ease-in-out infinite;
}

.hero-text {
  -webkit-transform: translatey(0px);
  transform: translatey(0px);
  -webkit-animation: floatText 6s ease-in-out infinite;
  animation: floatText 6s ease-in-out infinite;
}

.hero-land {
  width: 250px;
  -webkit-transform: translatey(0px);
  transform: translatey(0px);
  -webkit-animation: floatText 6s ease-in-out infinite;
  animation: floatText 6s ease-in-out infinite;
}

.candy-float {
  width: 50px;
}

section.cone {
  height: 100vh;
}

.cone img {
  width: 300px;
}

nav img {
  width: 300px;
  margin-top: 15px;
}

section.froyo {
  height: 100vh;
  margin-top: 150px;
}

.froyo img {
  width: 400px;
}

.froyo div {
  position: absolute;
}

section.toppings {
  height: 100vh;
  margin-top: 150px;
}

.toppings img {
  width: 400px;
}

.toppings div {
  position: absolute;
}


/*# sourceMappingURL=style.css.map */
<!doctype html>
<html lang="en">

<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

  <!-- Custom CSS -->

  <link rel="stylesheet" href="css/style.css">

  <!-- Googl Fonts -->

  <link href="https://fonts.googleapis.com/css?family=Lato:400,900&display=swap" rel="stylesheet">

  <!-- Font Awesome -->

  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.12.0/css/all.css" integrity="sha384-REHJTs1r2ErKBuJB0fCK99gCYsVjwxHrSU0N7I1zl9vZbggVJXRMsv/sLlOAGb4M" crossorigin="anonymous">

  <!-- Scripts -->

  <title>Hello, world!</title>
</head>

<body>

  <main>

    <section class="hero section text-center d-flex flex-column justify-content-center align-items-center position-relative">
      <img class="scroll" data-rate="-0.5" src="img/logo.png" />
    </section>

    <section class="cone section text-center d-flex flex-column justify-content-center align-items-center position-relative">
      <img src="img/cone.png" />
      <div class="scroll" data-rate="-0.5">
        <h3>delicious</h3>
        <h1>ice cream</h1>
      </div>

    </section>

    <section class="froyo section text-center d-flex flex-column justify-content-center align-items-center position-relative">
      <img src="img/froyo.png" />
      <div class="scroll" data-rate="-0.5">
        <h3>Frozen</h3>
        <h1>Yogurt</h1>
      </div>
    </section>

    <section class="toppings section text-center d-flex flex-column justify-content-center align-items-center position-relative">
      <img src="img/toppings.png" />
      <div class="scroll">
        <h3>and lots of</h3>
        <h1>toppings!</h1>
      </div>
    </section>

  </main>

  <footer class="container-fluid">
    <div class="row">
      <div class="col">
      </div>
      <div class="col text-center pt-5">
        <p>10911 Lindbrook Drive<br/>Los Angeles, CA 90024</p>
        <p>(310) 824-8191</p>
        <p>&copy; 2020</p>
      </div>
      <div class="col">

      </div>
    </div>

  </footer>

  <script>
    // window.addEventListener('scroll', function(e) {
    //     var section = document.querySelector('.section');
    //     var bounding = section.getBoundingClientRect();
    //     if (
    //         bounding.top >=0 || bounding.bottom >=0
    //     ) {
    //         const target = section.querySelector('.scroll');
    //         var rate = window.pageYOffset * -0.5;

    //         target.style.transform = 'translate3d(0px, '+rate+'px, 0px)'
    //     } else {
    //         console.log("Not in Viewport!");
    //     }
    // });



    // window.addEventListener('scroll', function(e) {
    //     const target = document.querySelectorAll('.scroll');



    //     //var scrolled = window.pageYOffset;
    //     //var rate = scrolled * -0.5;

    //     //target.style.transform = 'translate3d(0px, '+rate+'px, 0px)'
    //     var index = 0, length = target.length;
    //     for (index; index < length; index++) {
    //         var pos = window.pageYOffset * target[index].dataset.rate;

    //         target[index].style.transform = 'translate3d(0px, '+pos+'px, 0px)';
    //     };
    // });
  </script>





  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>

  <!-- Other Scripts -->
  <script src="/js/main.js"></script>

</body>

</html>
...