Search code examples
vue.jscounterviewport

How to trigger function on viewport visible with Vue viewport plugin


I am using an counter to display some numbers, but they load up when the page loads, so it loads unless I do some button to trigger it. Found this viewport plugin (https://github.com/BKWLD/vue-in-viewport-mixin) but I weren't able to use it. That's what I need to do, trigger a function when I reach some element (entirely), how to achieve it?


Solution

  • You don't necessarily need a package to do this. Add an event listener to listen to the scroll event, and check if the element is in the viewport every time there's a scroll event. Example code below - note that I've added an animation to emphasize the "appear if in viewport" effect.

    Codepen here.

    new Vue({
      el: '#app',
      created () {
        window.addEventListener('scroll', this.onScroll);
      },
      destroyed () {
        window.removeEventListener('scroll', this.onScroll);
      },
      data () {
        return {
          items: [
            1,
            2,
            3,
            4,
            5,
            6, 
            7, 
            8, 
            9, 
            10, 
            11, 
            12
          ],
          offsetTop: 0
        }
      },
      watch: {
        offsetTop (val) {
           this.callbackFunc()
        }
      },
      methods: {
        onScroll (e) {
          console.log('scrolling')
          this.offsetTop = window.pageYOffset || document.documentElement.scrollTop
        },
        isElementInViewport(el) {
          var rect = el.getBoundingClientRect();
          return (
            rect.top >= 0 &&
            rect.left >= 0 &&
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
            rect.right <= (window.innerWidth || document.documentElement.clientWidth)
          );
        },
        callbackFunc() {
          let items = document.querySelectorAll(".card");
          for (var i = 0; i < items.length; i++) {
            if (this.isElementInViewport(items[i])) {
              items[i].classList.add("in-view");
            }
          }
        }
      }
    })
    .card {
      height: 100px;
      border: 1px solid #000;
      visibility: hidden;
      opacity: 0
    }
    .in-view {
      visibility: visible;
      opacity: 1;
      animation: bounce-appear .5s ease forwards;
    }
    
    @keyframes bounce-appear {
      0% {
        transform: translateY(-50%) translateX(-50%) scale(0);
      }
      90% {
        transform: translateY(-50%) translateX(-50%) scale(1.1);
      }
      100% {
        tranform: translateY(-50%) translateX(-50%) scale(1);
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    
    <div id="app" onscroll="onScroll">
      <div v-for="item in items" class="card">
        {{item}}
      </div>
    </div>

    Another option is to use an intersection observer - I haven't explored this yet but this tutorial seems good: alligator.io/vuejs/lazy-image. Note that you will need a polyfill for IE.