Edit in JSFiddle

<div id="app">
  <p class="debug">{{ debug }}</p>
  <div class="item-container" @scroll="infiniteScroll">
    <div v-for="item in items" class="item-card">
      <div class="thumbnail"></div>
      <h3 class="title">{{ item.title }}</h3>
    </div>
    <div class="loader"><p>Now loading......</p></div>
  </div>
</div>
body {
  background-color: white;
  font-size: 62.5%;
  
  padding: 10px;
}

.item-container {
  width: 100%;
  height: 100vh;
  
  padding: 0 20px;
 
  overflow-y: auto;
}

.item-card {
  width: 80%;
  height: 80px;
}

.item-card { 
  display: flex;
  margin-bottom: 20px;

  > .thumbnail {
    background-color: #C7E7EF;

    width: 80px;
    height: 80px;
  }
  
  > .title {
    margin-left: 10px;
    font-size: 2rem;
    color: #00BADD;
  }
}

.loader {
  width: 100%;
  height: 50px;
  text-align: center;
  position: relative;
  overflow: hidden;
  
  &::after {
    content: '';
    display: block;
    width: 100%;
    height: 100%;
    background-color: white;
    
    position: absolute;
    top: 0;
    transform: translateX(30%);
    animation: loading 1.5s infinite;
  }
  
  > p {
    font-size: 2rem;
    color: #AAAAAA;
  }
}

@keyframes loading {
  100% {
    transform: translateX(70%);
  }
}

.debug {
  position: fixed;
  top: 0;
  right: 0;
  
  background-color: rgba(170,170,170,.25);
  z-index: 1;
}
// mock
function api (page, limit) {
  const items = [...Array(limit)].map((a, i) => {
    return {
      title: `page: ${page}, limit: ${limit}, ${i.toString().repeat(10)}`
    };
  });
  
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(items);
    }, 2000);
  });
}

const app = new Vue({
  el: '#app',
  data () {
    return {
      page: 0,
      limit: 10,
      items: [],
      debug: 'debug'
    }
  },
  async created () {
    this.fetch();
  },
  methods: {
    async fetch () {
      const items = await api(this.page, this.limit);
      this.items.push(...items);
      this.page++;
    },
    infiniteScroll (event) {
      this.debug = [
        `scrollTop: ${event.target.scrollTop}`,
        `offsetHeight: ${event.target.offsetHeight}`,
        `scrollHeight: ${event.target.scrollHeight}`,
        `content: ${this.items.length}`
      ].join(' | ');
    
      // スクロールの現在位置 + 親(.item-container)の高さ >= スクロール内のコンテンツの高さ
      if ((event.target.scrollTop + event.target.offsetHeight) >= event.target.scrollHeight) {
        this.fetch();
      }
    }
  }
})