使用Intersection Observer API建立无限加载组件.md

开发过程当中,常常会遇到须要处理大量数据的状况,好比列表、历史记录等,一般选择无限加载和分页导航。html

传统后端渲染,通常会选择分页导航,它能够轻松跳转,甚至一次跳转几个页面,如今SPA盛行,无限滚动加载是更好的方案,能够给用户更好的体验,尤为是在移动端。vue

在Awesome Vue中,有以下无限滚动组件ios

Intersection Observer API的出现,让开发无限滚动组件变得更加简单方便。git

Intersection Observer API

Intersection Observer API提供了一个可订阅的模型,能够观察该模型,以便在元素进入视口时获得通知。github

建立一个观察者实例很简单,咱们只须要建立一个IntersectionObserver的新实例并调用observe方法,传递一个DOM元素:chrome

const observer = new IntersectionObserver();

const coolElement = document.querySelector("#coolElement");
observer.observe(coolElement);

接下来可使用回调方式将参数传给InersectionObserver:npm

const observer = new IntersectionObserver(entries => {
  const firstEntry = entries[0];
  if (firstEntry.isIntersecting) {
    // Handle intersection here...
  }
});

const coolDiv = document.querySelector("#coolDiv");
observer.observe(coolDiv);

回调接收entries做为其参数。 这是一个数组,由于当你使用阈值时你能够有几个条目,但事实并不是如此,因此只获得第一个元素。
而后可使用firstEntry.isIntersection属性检查它是否相交。 这是进行异步请求并检索下一个页面的数据。json

IntersectionObserver构造函数使用如下表示法接收选项组件做为其第二个参数:后端

const options = {
  root: document.querySelector("#scrollArea"),
  rootMargin: "0px",
  threshold: 1.0
};

const observer = new IntersectionObserver(callback, options);

关于options里的参数解释,截自ruanyifeng intersectionobserver_apiapi

==root==:性指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点

==rootMargin==:
定义根元素的margin,用来扩展或缩小rootBounds这个矩形的大小,从而影响intersectionRect交叉区域的大小。它使用CSS的定义方法,好比10px 20px 30px 40px,表示 top、right、bottom 和 left 四个方向的值。

这样设置之后,无论是窗口滚动或者容器内滚动,只要目标元素可见性变化,都会触发观察器

==threshold==:决定了何时触发回调函数。它是一个数组,每一个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。
好比,[0, 0.25, 0.5, 0.75, 1]就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。

因为须要使用dom元素做为观察者,在Vue中,使用mounted,React中使用componentDidMount

// Observer.vue
export default {
  data: () => ({
    observer: null
  }),
  mounted() {
    this.observer = new IntersectionObserver(([entry]) => {
      if (entry && entry.isIntersecting) {
        // ...
      }
    });

    this.observer.observe(this.$el);
  }
};
注意:咱们在 [entry] 参数上使用数组解构,使用this.$el做为root以便观察

为了使其可重用,咱们须要让父组件(使用Observer组件的组件)处理相交的事件。 为此,能够在它相交时发出一个自定义事件:

export default {
  mounted() {
    this.observer = new IntersectionObserver(([entry]) => {
      if (entry && entry.isIntersecting) {
        this.$emit("intersect");
      }
    });

    this.observer.observe(this.$el);
  }
  // ...
};

<template>
  <div class="observer"/>
</template>

组件销毁的时候,记得关闭observer

export default {
  destroyed() {
    this.observer.disconnect();
  }
  // ...
};

与==unobserve==不一样的是,unobserve关闭当前被观察的元素,而disconnect关闭全部被观察的元素。

<!-- Observer.vue -->
<template>
  <div class="observer"/>
</template>

<script>
export default {
  props: ['options'],
  data: () => ({
    observer: null,
  }),
  mounted() {
    const options = this.options || {};
    this.observer = new IntersectionObserver(([entry]) => {
      if (entry && entry.isIntersecting) {
        this.$emit("intersect");
      }
    }, options);

    this.observer.observe(this.$el);
  },
  destroyed() {
    this.observer.disconnect();
  },
};
</script>

建立无限滚动组件Vue

假若有以下相似需求

<template>
  <div>
    <ul>
      <li class="list-item" v-for="item in items" :key="item.id">
        {{item.name}}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data: () => ({ page: 1, items: [] }),
  async mounted() {
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/comments?_page=${
        this.page
      }&_limit=50`
    );

    this.items = await res.json();
  }
};
</script>

引入Observer组件

<template>
  <div>
    <ul>
      <li class="list-item" v-for="item in items" :key="item.id">
        {{item.name}}
      </li>
    </ul>
    <Observer @intersect="intersected"/>
  </div>
</template>

<script>
import Observer from "./Observer";
export default {
  data: () => ({ page: 1, items: [] }),
  async mounted() {
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/comments?_page=${
        this.page
      }&_limit=50`
    );

    this.items = await res.json();
  },
  components: {
    Observer
  }
};
</script>

将==mounted==钩子里的异步请求移到==methods==里,并加上自增page以及合并items数据

export default {
  data: () => ({ page: 1, items: [] }),
  methods: {
    async intersected() {
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/comments?_page=${
          this.page
        }&_limit=50`
      );

      this.page++;
      const items = await res.json();
      this.items = [...this.items, ...items];
    }
  }
};
this.items = [...this.items, ...items] 等价于 this.items.concat(items)

到此InfiniteScroll.vue已经完成

<!-- InfiniteScroll.vue -->
<template>
  <div>
    <ul>
      <li class="list-item" v-for="item in items" :key="item.id">{{item.name}}</li>
    </ul>
    <Observer @intersect="intersected"/>
  </div>
</template>

<script>
import Observer from "./Observer";

export default {
  data: () => ({ page: 1, items: [] }),
  methods: {
    async intersected() {
      const res = await fetch(`https://jsonplaceholder.typicode.com/comments?_page=${
        this.page
      }&_limit=50`);

      this.page++;
      const items = await res.json();
      this.items = [...this.items, ...items];
    },
  },
  components: {
    Observer,
  },
};
</script>

值得注意的是,intersection Observer api兼容性并非太好,经本人测试,chrome上无压力,其他全不兼容,不过可使用W3C’s Intersection Observernpm install intersection-observer,而后在Observer.vue中加入require('intersection-observer');便可。

Demo在此:https://codesandbox.io/s/kxm8...

相关文章
相关标签/搜索