背景

最近项目中涉及到这么一个场景,需要用饿了么组件的el-table实现滚到到底部加载更多数据。

问题

element-ui的文档中,存在这么一个指令v-infinite-scroll,但是实际用下来发现,指令只能作用于当前绑定的元素上。 如下:

<div style="overflow:auto;height:300px" v-infinite-scroll="load">
    <ul>
        <li>今天老子</li>
        <li>不上班</li>
    </ul>
</div>
// div是滚动条出现的容器,指令生效
复制代码

而对于<el-table>不是真正的出现滚动条的标签,却无能为力

<el-table v-infinite-scroll="load">
    <el-table-column></<el-table-column>
    <el-table-column></<el-table-column>
    <el-table-column></<el-table-column>
</el-table>
// el-table 标签不是真正的容器,绑定了指令,无法生效。。。
复制代码

百度之后发现,真是什么乱七八糟的解决方法都有啊(CSDN你们看我干嘛?),然后决定自己动手来实现

实现

项目采用的是Vue,因此楼主采用Vue的解决方式来实现:Vue指令

首先我们需要知道如何判断滚到底部

const { scrollTop, scrollHeight, clientHeight } = targetEl

if (scrollHeight === scrollTop + clientHeight) {
    console.log('到底部了!!!')
}
复制代码

清楚了这个实现的关键点,接下来我们就实现一个简单版本

简洁版

export default {
    name: 'load-more',

    bind (el, binding, vnode) {
        binding.handler = function () {
            const { scrollTop, scrollHeight, clientHeight }  = el
            if (scrollHeight === scrollTop + clientHeight) {
                binding.value && binding.value()
            }
        }
        el.addEventListener('scroll', binding.handler)
    },

    unbind (el, binding) {
        el.removeEventListener('scroll', binding.handler)
        el = null
    }
}
复制代码

这时候,我们把写好的指令在组件内注册后。再进行简单的测试就发现,我们的指令已经生效了

<template>
    <div style="overflow:auto;height:300px" v-load-more="haha">
        <ul>
            <li>今天老子</li>
            <li>不上班</li>
        </ul>
    </div>
</template>
<script>
    name: 'list',
    
    methods: {
        haha () {
            console.log('生效了啊!!')
        }
    }
</script>
复制代码

能力的拓展

回到我们最初的问题,我们希望我们的指令能够支持

  1. 可指定作用的元素
  2. 是否禁止无限加载
  3. 支持设置滚动到底部的偏移量
  4. 支持防抖

完整代码

/**
 * @author: cunhang_wei
 * @description: 监听列表是否滚动到底部
 */
 
const debounce = function (func, delay) {
    let timer = null
    return function () {
        if (timer) clearTimeout(timer)
        timer = null
        let self = this
        let args = arguments
        timer = setTimeout(() => {
            func.apply(self, args)
        }, delay)
    }
}

export default {
    name: 'load-more',

    bind (el, binding, vnode) {
        const { expand } = binding.modifiers
        // 使用更丰富的功能,支持父组件的指令作用在指定的子组件上
        if (expand) {
            /**
             * target 目标DOM节点的类名
             * distance 减少触发加载的距离阈值,单位为px
             * func 触发的方法
             * delay 防抖时延,单位为ms
             * load-more-disabled 是否禁用无限加载
             */
            let { target, distance = 0, func, delay = 200 } = binding.value
            if (typeof target !== 'string') return
            let targetEl = el.querySelector(target)
            if (!targetEl) {
                console.log('找不到容器')
                return
            }
            binding.handler = debounce(function () {
                const { scrollTop, scrollHeight, clientHeight } = targetEl
                let disabled = el.getAttribute('load-more-disabled')
                disabled = vnode[disabled] || disabled

                if (scrollHeight <= scrollTop + clientHeight + distance) {
                    if (disabled) return
                    func && func()
                }
            }, delay)
            targetEl.addEventListener('scroll', binding.handler)
        } else {
            binding.handler = helper.debounce(function () {
                const { scrollTop, scrollHeight, clientHeight }  = el
                if (scrollHeight === scrollTop + clientHeight) {
                    binding.value && binding.value()
                }
            }, 200)
            el.addEventListener('scroll', binding.handler)
        }
    },

    unbind (el, binding) {
        let { arg } = binding
        // 使用更丰富的功能,支持父组件的指令作用在指定的子组件上
        if (arg === 'expand') {
            /**
             * target 目标DOM节点的类名
             * offset 触发加载的距离阈值,单位为px
             * method 触发的方法
             * delay 防抖时延,单位为ms
             */
            const { target } = binding.value
            if (typeof target !== 'string') return
            let targetEl = el.querySelector(target)
            targetEl && targetEl.removeEventListener('scroll', binding.handler)
            targetEl = null
        } else {
            el.removeEventListener('scroll', binding.handler)
            el = null
        }
    }
}
复制代码

测试

我们找到 <el-table> 标签真正的滚动容器

<template>
    <div>
        <el-table v-load-more.expand="{func: haha, target: '.el-table__body-wrapper', delay: 500}">
            <el-table-column></<el-table-column>
            <el-table-column></<el-table-column>
            <el-table-column></<el-table-column>
        </el-table>
    </div>
</template>
<script>
    name: 'list',
    
    methods: {
        haha () {
            console.log('生效了啊!!')
        }
        ...
    }
</script>
复制代码

演示效果

GIF20210602.gif

CodePen 演示代码地址

codepen.io/afine970/pe…

最后

好好学习不会差,我是爱你们的航少,看完觉得不错记得点个赞哦

点赞(3) 关注
立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部