Vue 组件封装之 ScrollView 上拉加载更多

一、ScrollView 上拉加载更多

组件说明:
实现上拉到底部加载更多功能。

效果展示:
当滑动到底部超过20px时加载下一条数据。
在这里插入图片描述
在这里插入图片描述

二、使用案例

<template>
  <div ref="scroll"
       class="scroll"
       v-scroll-view="getList"
       scroll-view-disabled="disabled"
       scroll-view-distance="10"
       scroll-view-delay="1000"
  >
    <div class="cm-pt-018 cm-tx-c item" v-for="item in list">
      {{item}}
    </div>
    <p class="cm-tx-c bottom">{{loadText}}</p>
  </div>
</template>
<script>
  export default{
    data(){
      return{
        count:16,
        loadText:"加载中",
        disabled:false,
        list:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
      }
    },
    methods:{
      getList(){
         if(this.count>=30){
           this.disabled = true;
           this.loadText = "加载完成";
         };
         this.getData();
      },
      getData(){
        this.list.push(this.count);
        this.count++;
      }
    },
    mounted(){
      setTimeout(()=>{
        //设置要滑动的div高度
        var scroll = this.$refs.scroll;
        var hei = window.innerHeight;
        scroll.style.height = hei+'px';
      },200);
    }
  }
</script>
<style>
  .scroll{
    overflow: auto;
  }
  .item{
    height: 50px;
    background: pink;
    border: 1px solid #ddd;
  }
  .bottom{
    height: 40px;
    line-height: 40px;
  }
</style>

三、API 使用指南

属性说明类型默认值
v-scroll-view每次滑动时需要调的方法Function(e: Object): void
scroll-view-disabled是否禁用滚动Booleanfalse
scroll-view-distance距离底部多少时开始滚动Number200
scroll-view-immediate是否立即启动滚动Booleanfalse
scroll-view-delay每一次滚动间隔多长时间Number1000

注意事项:在实现上拉加载更多时,需要设置滑动父元素的高度以及将父元素设置为overflow:auto,当头部有固定的区域时,高度=window.innerHeight-头部元素的高度。

四、源代码

main.js

import throttle from '../utils/throttle';
import {
  isHtmlElement,
  isFunction,
  isUndefined,
  isDefined
} from '../utils/types';
import {
  getScrollContainer
} from '../utils/dom';

const getStyleComputedProperty = (element, property) => {
  if (element === window) {
    element = document.documentElement;
  }

  if (element.nodeType !== 1) {
    return [];
  }
  // NOTE: 1 DOM access here
  const css = window.getComputedStyle(element, null);
  return property ? css[property] : css;
};

const entries = (obj) => {
  return Object.keys(obj || {})
    .map(key => ([key, obj[key]]));
};

const getPositionSize = (el, prop) => {
  return el === window || el === document
    ? document.documentElement[prop]
    : el[prop];
};

const getOffsetHeight = el => {
  return getPositionSize(el, 'offsetHeight');
};

const getClientHeight = el => {
  return getPositionSize(el, 'clientHeight');
};

const scope = 'ElScrollView';
const attributes = {
  //delay
  delay: {
    type: Number,
    default: 200
  },
  //distance
  distance: {
    type: Number,
    default: 0
  },
  //disabled
  disabled: {
    type: Boolean,
    default: false
  },
  //immediate
  immediate: {
    type: Boolean,
    default: true
  }
};

const getScrollOptions = (el, vm) => {
  if (!isHtmlElement(el)) return {};
  return entries(attributes).reduce((map, [key, option]) => {
    //console.log(entries(attributes));
    const { type, default: defaultValue } = option;
    let value = el.getAttribute(`scroll-view-${key}`);
    value = isUndefined(vm[value]) ? value : vm[value];
    switch (type) {
      case Number:
        value = Number(value);
        value = Number.isNaN(value) ? defaultValue : value;
        break;
      case Boolean:
        value = isDefined(value) ? value === 'false' ? false : Boolean(value) : defaultValue;
        break;
      default:
        value = type(value);
    }
    map[key] = value;
    return map;
  }, {});
};

const getElementTop = el => el.getBoundingClientRect().top;

const handleScroll = function(cb) {
  const { el, vm, container, observer } = this[scope];
  //console.log(this[scope]);
  const { distance, disabled } = getScrollOptions(el, vm);

  if (disabled) return;

  let shouldTrigger = false;

  if (container === el) {
    // be aware of difference between clientHeight & offsetHeight & window.getComputedStyle().height
    const scrollBottom = container.scrollTop + getClientHeight(container);
    shouldTrigger = container.scrollHeight - scrollBottom <= distance;
  } else {
    const heightBelowTop = getOffsetHeight(el) + getElementTop(el) - getElementTop(container);
    const offsetHeight = getOffsetHeight(container);
    const borderBottom = Number.parseFloat(getStyleComputedProperty(container, 'borderBottomWidth'));
    shouldTrigger = heightBelowTop - offsetHeight + borderBottom <= distance;
  }

  if (shouldTrigger && isFunction(cb)) {
    cb.call(vm);
  } else if (observer) {
    observer.disconnect();
    this[scope].observer = null;
  }

};

export default {
  name: 'ScrollView',
  inserted(el, binding, vnode) {
    const cb = binding.value;

    const vm = vnode.context;
    // only include vertical scroll
    const container = getScrollContainer(el, true);
    const { delay, immediate } = getScrollOptions(el, vm);
    const onScroll = throttle(delay, handleScroll.bind(el, cb));

    el[scope] = { el, vm, container, onScroll };

    if (container) {
      container.addEventListener('scroll', onScroll);

      if (immediate) {
        const observer = el[scope].observer = new MutationObserver(onScroll);
        observer.observe(container, { childList: true, subtree: true });
        onScroll();
      }
    }
  },
  unbind(el) {
    const { container, onScroll } = el[scope];
    if (container) {
      container.removeEventListener('scroll', onScroll);
    }
  }
};
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值