当前位置:   article > 正文

【前端技巧】JS自定义指令 —— 无限滚动(改写el-table-infinite-scroll)

el-table-infinite-scroll

@Author:Outman
@Date:2023-03-08

JS自定义指令 —— 无限滚动(改写el-table-infinite-scroll)

自定义指令实现

// directives/index.js
/**
 * 此处引入element官方的infinite-scroll,'./infinite-scroll/index'此处的index并非为当前的index.js文件
 * 整体目录结构为:
 * · directives
 *   · index.js (当前文件)
 *   · infinite-scroll (element官方的infinite-scroll,本文文末已提供)
 *     · index.js
 *     · src
 *       · main.js
 */
import InfiniteScroll from "./infinite-scroll/index";
export default {
  // 全局挂载
  install(Vue) {
        /**
     * @description: 无限滚动
     * @param scrollClass String 需要添加无限滚动的节点class
     * scrollClass默认为".el-table__body-wrapper",可自行修改为所需绑定无限滚动节点的class
     * 例如:element-table中若要实现无限滚动,则需要绑定class为el-table__body-wrapper
     * 无限滚动相关配置项(elementUI相关配置):
     * infinite-scroll-disabled	   是否禁用	                                         boolean	-	false
     * infinite-scroll-delay	     节流时延,单位为ms	                                number	 - 200
     * infinite-scroll-distance	   触发加载的距离阈值,单位为px	                       number   -	0
     * infinite-scroll-immediate   是否立即执行加载方法,以防初始状态下内容无法撑满容器	 boolean  -	true
     */
    const elScope = 'InfiniteScroll'; // scope name
    Vue.directive('outman-infinite-scroll', {
      inserted(el, binding, vnode, oldVnode){
        const value = binding.value || {};
        const { scrollClass = ".el-table__body-wrapper" } = value;
        // 获取 hui table 中的滚动层  
        const scrollElement = el.querySelector(scrollClass);
        if(!scrollElement)throw '找不到指定的容器!'
        // 设置自动滚动
        scrollElement.style.overflowY = 'auto';
        // dom 渲染后
        setTimeout(()=>{
          // 高度默认值赋值
          if (!el.style.height) {
            scrollElement.style.height = '300px';
          }
          // 获取配置参数
          asyncElOptions(vnode, el, scrollElement);
          // 绑定 infinite-scroll
          InfiniteScroll.inserted(scrollElement, binding, vnode);
          // 将子集的引用放入 el 上,用于 unbind 中销毁事件
          el[elScope] = scrollElement[elScope];
        }, 0)
      },
      componentUpdated(el, binding, vnode){
        const value = binding.value || {};
        const { scrollClass = ".h-table-body" } = value;
        // 更新配置参数
        asyncElOptions(vnode, el, el.querySelector(scrollClass));
      },
      unbind(el){
        InfiniteScroll.unbind(el)
      }
    })
    /**
     * 同步 el-infinite-scroll 的配置项
     * @param sourceVNode
     * @param sourceElem
     * @param targetElem
     */
    function asyncElOptions(sourceVNode, sourceElem, targetElem) {
      let value;
      ['disabled', 'delay', 'immediate'].forEach((name) => {
        name = 'infinite-scroll-' + name;
        value = sourceElem.getAttribute(name);
        if (value !== null) {
          targetElem.setAttribute(name, value);
        } else {
          targetElem.removeAttribute(name);
        }
      });
      // fix: windows/chrome 的 scrollTop + clientHeight 与 scrollHeight 不一致的 BUG
      const name = 'infinite-scroll-distance';
      value = sourceElem.getAttribute(name);
      targetElem.setAttribute(name, value < 1 ? 1 : value);
    }
  }
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

自定义指令引入

// 引入app.js
import directives from '../src/directives/index';
Vue.use(directives);
  • 1
  • 2
  • 3

自定义指令应用

<!-- 应用 -->
<template>
  <el-table 
    height="200" 
    :columns="columns" 
    :data="data" 
    v-outman-infinite-scroll="handleInfinite" 
    :infinite-scroll-delay="200"
    :infinite-scroll-disabled="busy" 
    :infinite-scroll-distance="10"
  >
  </el-table>
</template>
<script>
export default {
  data() {
    return {
      busy: false,
      columns: [
        {
          title: '姓名',
          key: 'name'
        },
        {
          title: '年龄',
          key: 'age'
        },
        {
          title: '地址',
          key: 'address'
        }
      ],
      data: [
        {
          name: '王小明',
          age: 18,
          address: '北京市朝阳区芍药居'
        },
        {
          name: '张小刚',
          age: 25,
          address: '北京市海淀区西二旗'
        },
        {
          name: '李小红',
          age: 30,
          address: '上海市浦东新区世纪大道'
        },
        {
          name: '周小伟',
          age: 26,
          address: '深圳市南山区深南大道'
        },
        {
          name: '王小明',
          age: 18,
          address: '北京市朝阳区芍药居'
        },
        {
          name: '张小刚',
          age: 25,
          address: '北京市海淀区西二旗'
        }
      ]
    };
  },
  methods: {
    handleInfinite() {
      console.log('我在无限滚动了!');
    }
  }
};
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

el-infinite-scroll 源码(上述infinite-scroll目录内的文件)

// infinite/index.js
import InfiniteScroll from './src/main.js';

/* istanbul ignore next */
InfiniteScroll.install = function(Vue) {
  Vue.directive(InfiniteScroll.name, InfiniteScroll);
};

export default InfiniteScroll;

// infinite/src/main.js
import throttle from 'throttle-debounce/debounce';
import {
  isHtmlElement,
  isFunction,
  isUndefined,
  isDefined
} from 'element-ui/src/utils/types';
import {
  getScrollContainer
} from 'element-ui/src/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 = 'ElInfiniteScroll';
const attributes = {
  delay: {
    type: Number,
    default: 200
  },
  distance: {
    type: Number,
    default: 0
  },
  disabled: {
    type: Boolean,
    default: false
  },
  immediate: {
    type: Boolean,
    default: true
  }
};

const getScrollOptions = (el, vm) => {
  if (!isHtmlElement(el)) return {};

  return entries(attributes).reduce((map, [key, option]) => {
    const { type, default: defaultValue } = option;
    let value = el.getAttribute(`infinite-scroll-${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];
  const { distance, disabled } = getScrollOptions(el, vm);

  if (disabled) return;

  const containerInfo = container.getBoundingClientRect();
  if (!containerInfo.width && !containerInfo.height) 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: 'InfiniteScroll',
  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);
    }
  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读