基於 IntersectionObserver API 實現無限滾動組件

IntersectionObserver API 是什麼?

Intersection Observer API 提供了一種異步觀察目標元素與祖先元素或頂級文檔 viewport 的交集中的變化的方法。容許你配置一個回調函數,每當目標 (target) 元素與設備視窗或者其餘指定元素髮生交集的時候執行。css

改造 vue-scroll-loader

vue-scroll-loader 是我在使用 element UI 庫過程當中,因爲該庫自帶的無限滾動組件會有莫名其妙的 Bug,提了 issue 也久久沒見修復,因而自(chong)力(fu)更(zao)生(lun)擼(zi)了一個相似的無限滾動組件,足夠簡單、足夠小。html

在使用觀察者 API 以前,vue-scorll-loader 1.x 版本是使用遠古技術經過監聽滾動條實現的,稍有常識的人都知道這種方式會有性能損耗 :-D,現在 Intersection Observer API 兼容性愈來愈好,再加上官方的 polyfill 就能夠生產環境使用了,使人驚喜的是亢餘的舊代碼通過改造以後變爲了短短了幾行。前端

1、實現組件主要結構

這裏我並無使用常規的方式建立一個 warpper 包裹要加載的 list ,而是直接使用一個 loading 動畫放在 list 下面,loding 便是組件自己,這樣組件和 list 解耦,和佈局無關,list 要使用無限滾動直接將此組件放下面便可,而不需額外的修改 list 佈局vue

講到這裏,咱們就來隨手實(fu)現(zhi)一個 loading 動畫吧。 cssfx.dev

HTMLgit

使用 slot 提供用戶自定義動畫的能力github

<template lang="html">
  <div class="loader" v-if="!loaderDisable">
    <slot>
      <svg viewBox="25 25 50 50" class="loader__svg" :style="size">
        <circle cx="50" cy="50" r="20" class="loader__circle" :style="color"></circle>
      </svg>
    </slot>
  </div>
</template>
複製代碼

SCSS數組

<style lang="scss" scoped>
.loader{
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 30px 0;
  &__svg {
    transform-origin: center;
    animation: rotate 2s linear infinite;
  }
  &__circle {
    fill: none;
    stroke-width: 3;
    stroke-dasharray: 1, 200;
    stroke-dashoffset: 0;
    stroke-linecap: round;
    animation: dash 1.5s ease-in-out infinite;
  }
}
@keyframes rotate {
  100% {
    transform: rotate(360deg);
  }
}
@keyframes dash {
  0% {
    stroke-dasharray: 1, 200;
    stroke-dashoffset: 0;
  }
  50% {
    stroke-dasharray: 90, 200;
    stroke-dashoffset: -35px;
  }
  100% {
    stroke-dashoffset: -125px;
  }
}
</style>
複製代碼

DEMObash

2、實現組件主要功能

之前咱們是監聽滾動條實現像下面這樣:異步

缺點:同步,且會重複觸發,每每須要 debounce 限制頻率,須要手動計算相對位置。svg

window.addEventListener('scroll', e => {
    // 一堆計算代碼...
})
複製代碼

而如今使用 Intersection Observer API :

優勢:異步,只有處於交叉點纔會觸發,無需計算元素之間的相對位置。

const observer = new IntersectionObserver(([{ isIntersecting }]) => {
    // 要作什麼事情...
}, {
    root: null, // 相對的視口元素,傳入 null 則爲頂級文檔視口
    rootMargin: '0px 0px 0px 0px', // 觸發交叉回調時被觀察元素相對於視口的偏移量
    threshold: 0 // 一個具體數值或數值數組, 觸發交叉回調時被觀察元素的可見比例
})
// 傳入被觀察元素
observer.observe(el)
複製代碼

你們可能注意到了,上面我在回調裏面接收了一個 isIntersecting 參數,這是幹什麼用的呢? 緣由是當被觀察元素通過和視口的交叉點時,會觸發兩次回調,一次進入交叉點,一次離開交叉點,進入爲 true,離開爲 false,因此咱們須要這個參數來保證加載回調只觸發一次。

下面是結合 Vue 的無限滾動實現。

JavaScript

這裏有個技巧,咱們 new IntersectionObserver() 會建立一個 observer 觀察器,由於當離開當前組件時須要註銷觀察器,因此這裏利用計算屬性建立這個觀察器的同時保持常駐,至關於簡化了在 mounted() 中 new IntersectionObserver() 並把實例存入 data 這一步操做。

<script>
import 'intersection-observer'
export default {
  name: 'ScrollLoader',
  props: {
    'loader-method': {
      type: Function,
      required: true
    },
    'loader-disable': {
      type: Boolean,
      default: false
    },
    'loader-distance': {
      type: Number,
      default: 0
    },
    'loader-color': {
      type: String,
      default: '#666666'
    },
    'loader-size': {
      type: Number,
      default: 50
    },
    'loader-viewport': {
      type: Object,
      default: null
    }
  },
  computed: {
    size () {
      return {
        width: `${this.loaderSize}px`
      }
    },
    color () {
      return {
        stroke: this.loaderColor
      }
    },
    options () {
      return {
        root: this.loaderViewport,
        rootMargin: `0px 0px ${this.loaderDistance}px 0px`
      }
    },
    observer () {
      return new IntersectionObserver(([{ isIntersecting }]) => {
        isIntersecting && !this.loaderDisable && this.loaderMethod()
      }, this.options)
    }
  },
  mounted () {
    this.observer.observe(this.$el)
  },
  activated () {
    this.observer.observe(this.$el)
  },
  deactivated () {
    this.observer.unobserve(this.$el)
  },
  beforeDestroy () {
    this.observer.unobserve(this.$el)
  }
}
</script>
複製代碼

Demo

在線預覽:DEMO

項目地址:vue-scroll-loader

3、結語

第一次在掘金髮布文章,算不上什麼乾貨,也不是很高深的技術,但仍是但願這篇文章能幫助到像我這樣的菜雞前端,文章若有遺漏或錯誤的地方,還但願大佬指點,謝謝你們!

相關文章
相關標籤/搜索