如何应用于多个元素:JavaScript 仅当父元素在视口中时才对元素产生滚动效果

How to apply to many elements: JavaScript scroll effect on element only when parent element is in viewport

我有一组 100vh 个部分,其中包含 child 个图像和文本。我想对文本应用滚动 transform 以创建将文本移动到图像上的视差效果。我已经使用 vanilla javascript 将它用于单个元素,但是,我不能完全正确地将相同的效果应用于具有相同 class 的多个部分。现在,效果将应用于所有 divs,无论它们的父级 section 是否在视口中。

你能帮我编辑 javascript 使得具有 class scroll 的子 div 只有在其父 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>

使用 IntersectionObserver API 观察元素是否在视图中。此 api 监视您指定的元素并在 observed 元素进入或离开视口时触发回调。这样您就可以创建视口中元素的列表。

为了帮助创建 Set。 Sets 接受任何类型的值并将其存储在一个列表中,就像一个增压数组,但只有唯一的值。所以没有元素会在那里两次。

intersectionCallback 中检查元素是否离开视口并将其添加到集合中。如果它离开屏幕,将其从集合中移除。

然后而不是遍历每个 .section 循环遍历包含当前在视图中的部分的集合。此集合的内容会根据当前滚动位置不断变化。

scroll 事件回调中计算该部分相对于屏幕顶部的位置。您需要 偏移 对部分的每个转换以获得视差元素的正确位置。

希望对您有所帮助。
如果这里有任何不清楚的地方或者我没有以任何方式帮助您,请告诉我。

干杯!

附录

我觉得性能有点不稳定,所以我查看了代码以进行改进。第一个是向 scroll 事件侦听器添加 passive listener,告诉侦听器不要等待 event.preventDefault()。因为它不会等待,所以它会更快地循环到下一次触发 scroll 事件。

然后是计算。每次触发 scroll 事件时都会计算偏移量。但是偏移量在滚动时不会改变,所以先计算它并在需要时使用它是有意义的。选择 target 元素也是如此,该元素也保持不变。

所以我添加了两个 Map 对象,它们在该部分与其偏移量 + 目标之间保持联系。所以现在滚动函数将根据当前正在循环的 section 获得先前计算的偏移量和目标。

所有这些添加将显着提高滚动性能并使您的视差效果感觉流畅。我知道它有很多代码,我不希望所有的东西都能立即理解,但如果你愿意,我可以试着解释一下所有的东西,然后 why/how 你就可以使用它。

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>