美文网首页
vue-infinite-scroll 库在 Modal 和 D

vue-infinite-scroll 库在 Modal 和 D

作者: 爱吃豆包 | 来源:发表于2022-01-12 15:20 被阅读0次

    因为需要在PC端使用上拉加载功能,发现 Antd 描述了一个 滚动加载库

    image.png

    安装依赖

    npm install vue-infinite-scroll --save
    

    选项解释

    v-infinite-scroll="loadMore" 表示回调函数是loadMore
    infinite-scroll-disabled="busy"表示由变量busy决定是否执行loadMorefalse则执行loadMoretrue则不执行,看清楚,busy表示繁忙,繁忙的时候是不执行的。
    infinite-scroll-distance="10"这里10决定了页面滚动到离页尾多少像素的时候触发回调函数,10是像素值。通常我们会在页尾做一个几十像素高的“正在加载中...”,这样的话,可以把这个div的高度设为infinite-scroll-distance的值即可。

    其他选项:

    infinite-scroll-immediate-check 默认值为true,该指令意思是,应该在绑定后立即检查busy的值和是否滚动到底。如果你的初始内容高度不够高、不足以填满可滚动的容器的话,你应设为true,这样会立即执行一次loadMore,会帮你填充一些初始内容。
    infinite-scroll-listen-for-event 当事件在Vue实例中发出时,无限滚动将再次检查。
    infinite-scroll-throttle-delay 检查busy的值的时间间隔,默认值是200,因为vue-infinite-scroll的基础原理就是,vue-infinite-scroll 会循环检查busy的值,以及是否滚动到底,只有当:busyfalse且滚动到底,回调函数才会执行。

    介绍完毕,看看遇到的问题

    遇到的问题

    参考了 https://blog.csdn.net/u012451520/article/details/117113286

    但是这个库只适合在这个页面使用,不适合在一些弹窗的组件中使用!
    https://github.com/ElemeFE/vue-infinite-scroll/issues/147

    image.png

    原因呢在上面的参考连接上已经有了(在这里我在贴出来一下)

    1.弹窗启动时,未经过mounted生命周期,所以未绑定成功滚动事件

    2.获得监听滚动函数的element时(getScrollEventTarget),也就是滚动的容器时,未能拿到overflowY的值,是根据这个值去返回当前滚动容器的元素。否则拿不到返回window的值。

    这个博主也给出了解决方案,就是修改滚动加载库源代码

    这个博主修改后的:

    请看到最后面,因为还有一个坑,没有完全解决,最后面的解决了遗留的问题

    const ctx = '@@InfiniteScroll';
     
    let throttle = function (fn, delay) {
      let now, lastExec, timer, context, args; //eslint-disable-line
     
      let execute = function () {
        fn.apply(context, args);
        lastExec = now;
      };
     
      return function () {
        context = this;
        args = arguments;
        now = Date.now();
     
        if (timer) {
          clearTimeout(timer);
          timer = null;
        }
     
        if (lastExec) {
          let diff = delay - (now - lastExec);
          if (diff < 0) {
            execute();
          } else {
            timer = setTimeout(() => {
              execute();
            }, diff);
          }
        } else {
          execute();
        }
      };
    };
     
    let getScrollTop = function (element) {
      if (element === window) {
        return Math.max(window.pageYOffset || 0, document.documentElement.scrollTop);
      }
     
      return element.scrollTop;
    };
     
    let getComputedStyle = document.defaultView.getComputedStyle;
     
    let getScrollEventTarget = function (element) {
      let currentNode = element;
      // bugfix, see http://w3help.org/zh-cn/causes/SD9013 and http://stackoverflow.com/questions/17016740/onscroll-function-is-not-working-for-chrome
      while (currentNode && currentNode.tagName !== 'HTML' && currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) {
        let overflowY = getComputedStyle(currentNode).overflowY;
        let overflowYStyle = currentNode.style.overflowY
        if (overflowY === 'scroll' || overflowY === 'auto' || overflowYStyle === 'auto') {
          return currentNode;
        }
        currentNode = currentNode.parentNode;
      }
      return currentNode;
    };
     
    let getVisibleHeight = function (element) {
      if (element === window) {
        return document.documentElement.clientHeight;
      }
     
      return element.clientHeight;
    };
     
    let getElementTop = function (element) {
      if (element === window) {
        return getScrollTop(window);
      }
      return element.getBoundingClientRect().top + getScrollTop(window);
    };
     
    let isAttached = function (element) {
      let currentNode = element.parentNode;
      while (currentNode) {
        if (currentNode.tagName === 'HTML') {
          return true;
        }
        if (currentNode.nodeType === 11) {
          return false;
        }
        currentNode = currentNode.parentNode;
      }
      return false;
    };
     
    let doBind = function () {
      if (this.binded) return; // eslint-disable-line
      this.binded = true;
     
      let directive = this;
      let element = directive.el;
     
      let throttleDelayExpr = element.getAttribute('infinite-scroll-throttle-delay');
      let throttleDelay = 200;
      if (throttleDelayExpr) {
        throttleDelay = Number(directive.vm[throttleDelayExpr] || throttleDelayExpr);
        if (isNaN(throttleDelay) || throttleDelay < 0) {
          throttleDelay = 200;
        }
      }
      directive.throttleDelay = throttleDelay;
     
      directive.scrollEventTarget = getScrollEventTarget(element);
      directive.scrollListener = throttle(doCheck.bind(directive), directive.throttleDelay);
      directive.scrollEventTarget.addEventListener('scroll', directive.scrollListener);
     
      this.vm.$on('hook:beforeDestroy', function () {
        directive.scrollEventTarget.removeEventListener('scroll', directive.scrollListener);
      });
     
      let disabledExpr = element.getAttribute('infinite-scroll-disabled');
      let disabled = false;
      console.log('disabledExpr', disabledExpr)
      if (disabledExpr) {
        this.vm.$watch(disabledExpr, function (value) {
          directive.disabled = value;
          if (!value && directive.immediateCheck) {
            doCheck.call(directive);
          }
        });
        disabled = Boolean(directive.vm[disabledExpr]);
      }
      directive.disabled = disabled;
     
      let distanceExpr = element.getAttribute('infinite-scroll-distance');
      let distance = 0;
      if (distanceExpr) {
        distance = Number(directive.vm[distanceExpr] || distanceExpr);
        if (isNaN(distance)) {
          distance = 0;
        }
      }
      directive.distance = distance;
     
      let immediateCheckExpr = element.getAttribute('infinite-scroll-immediate-check');
      let immediateCheck = true;
      if (immediateCheckExpr) {
        immediateCheck = Boolean(directive.vm[immediateCheckExpr]);
      }
      directive.immediateCheck = immediateCheck;
     
      if (immediateCheck) {
        doCheck.call(directive);
      }
     
      let eventName = element.getAttribute('infinite-scroll-listen-for-event');
      if (eventName) {
        directive.vm.$on(eventName, function () {
          doCheck.call(directive);
        });
      }
    };
     
    const doCheck = function (force) {
      let scrollEventTarget = this.scrollEventTarget;
      let element = this.el;
      let distance = this.distance;
     
      if (force !== true && this.disabled) return; //eslint-disable-line
      let viewportScrollTop = getScrollTop(scrollEventTarget);
      let viewportBottom = viewportScrollTop + getVisibleHeight(scrollEventTarget);
     
      let shouldTrigger = false;
     
      if (scrollEventTarget === element) {
        shouldTrigger = scrollEventTarget.scrollHeight - viewportBottom <= distance;
      } else {
        let elementBottom = getElementTop(element) - getElementTop(scrollEventTarget) + element.offsetHeight + viewportScrollTop;
     
        shouldTrigger = viewportBottom + distance >= elementBottom;
      }
     
      if (shouldTrigger && this.expression) {
        this.expression();
      }
    };
     
    export default {
      bind (el, binding, vnode) {
        el[ctx] = {
          el,
          vm: vnode.context,
          expression: binding.value
        };
        const args = arguments;
     
        doBind.call(el[ctx]);
        el[ctx].vm.$nextTick().then(function () {
          if (isAttached(el)) {
            doBind.call(el[ctx], args);
          }
     
          el[ctx].bindTryCount = 0;
     
          const tryBind = function () {
            if (el[ctx].bindTryCount > 10) return; //eslint-disable-line
            el[ctx].bindTryCount++;
            if (isAttached(el)) {
              doBind.call(el[ctx], args);
            } else {
              setTimeout(tryBind, 50);
            }
          };
          tryBind();
        });
      },
     
      unbind (el) {
        if (el && el[ctx] && el[ctx].scrollEventTarget) {
          el[ctx].scrollEventTarget.removeEventListener('scroll', el[ctx].scrollListener);
        }
      }
    };
    

    使用方式:

    import infiniteScroll from './directive';
    export default {
      directives: { infiniteScroll },
      components: {
        DetailsTitle
      },
    }//将上述代码的js放在文件夹下,直接引用,其实就是把vue-infinate-scroll包里的js抽出来,自己改了,按照原来的用法用就行了
    

    但是呢,问题来了,我在使用的时候依旧出现了问题。

    会报错 addEventListener is not a function

    并且其他人和我一样也遇到了


    image.png

    解决这个问题

    在这份代码的基础上,再次修改,修改后的源代码

    const ctx = '@@InfiniteScroll'
    
    const throttle = function(fn, delay) {
      let now, lastExec, timer, context, args; //eslint-disable-line
    
      const execute = function() {
        fn.apply(context, args)
        lastExec = now
      }
    
      return function() {
        context = this
        args = arguments
        now = Date.now()
    
        if (timer) {
          clearTimeout(timer)
          timer = null
        }
    
        if (lastExec) {
          const diff = delay - (now - lastExec)
          if (diff < 0) {
            execute()
          } else {
            timer = setTimeout(() => {
              execute()
            }, diff)
          }
        } else {
          execute()
        }
      }
    }
    
    const getScrollTop = function(element) {
      if (element === window) {
        return Math.max(window.pageYOffset || 0, document.documentElement.scrollTop)
      }
    
      return element.scrollTop
    }
    
    const getComputedStyle = document.defaultView.getComputedStyle
    
    const getScrollEventTarget = function(element) {
      let currentNode = element
      console.log('drawer:', element)
      // bugfix, see http://w3help.org/zh-cn/causes/SD9013 and http://stackoverflow.com/questions/17016740/onscroll-function-is-not-working-for-chrome
      while (currentNode && currentNode.tagName !== 'HTML' && currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) {
        const overflowY = getComputedStyle(currentNode).overflowY
        console.log('drawer:', overflowY)
        const overflowYStyle = currentNode.style.overflowY
        console.log('drawer:', overflowYStyle)
        if (overflowY === 'scroll' || overflowY === 'auto' || overflowYStyle === 'auto') {
          return currentNode
        }
        console.log('drawer:', currentNode.parentNode)
        currentNode = currentNode.parentNode
      }
      return currentNode
    }
    
    const getVisibleHeight = function(element) {
      if (element === window) {
        return document.documentElement.clientHeight
      }
    
      return element.clientHeight
    }
    
    const getElementTop = function(element) {
      if (element === window) {
        return getScrollTop(window)
      }
      return element.getBoundingClientRect().top + getScrollTop(window)
    }
    
    const isAttached = function(element) {
      let currentNode = element.parentNode
      while (currentNode) {
        if (currentNode.tagName === 'HTML') {
          return true
        }
        if (currentNode.nodeType === 11) {
          return false
        }
        currentNode = currentNode.parentNode
      }
      return false
    }
    
    const doBind = function() {
      if (this.binded) return; // eslint-disable-line
      this.binded = true
    
      const directive = this
      const element = directive.el
      console.log('directive:', directive, element.parentNode)
      const throttleDelayExpr = element.getAttribute('infinite-scroll-throttle-delay')
      let throttleDelay = 200
      if (throttleDelayExpr) {
        throttleDelay = Number(directive.vm[throttleDelayExpr] || throttleDelayExpr)
        if (isNaN(throttleDelay) || throttleDelay < 0) {
          throttleDelay = 200
        }
      }
      directive.throttleDelay = throttleDelay
    
      directive.scrollEventTarget = getScrollEventTarget(element)
      directive.scrollListener = throttle(doCheck.bind(directive), directive.throttleDelay)
      directive.scrollEventTarget.addEventListener('scroll', directive.scrollListener)
    
      this.vm.$on('hook:beforeDestroy', function() {
        directive.scrollEventTarget.removeEventListener('scroll', directive.scrollListener)
      })
    
      const disabledExpr = element.getAttribute('infinite-scroll-disabled')
      let disabled = false
      console.log('disabledExpr', disabledExpr)
      if (disabledExpr) {
        this.vm.$watch(disabledExpr, function(value) {
          directive.disabled = value
          if (!value && directive.immediateCheck) {
            doCheck.call(directive)
          }
        })
        disabled = Boolean(directive.vm[disabledExpr])
      }
      directive.disabled = disabled
    
      const distanceExpr = element.getAttribute('infinite-scroll-distance')
      let distance = 0
      if (distanceExpr) {
        distance = Number(directive.vm[distanceExpr] || distanceExpr)
        if (isNaN(distance)) {
          distance = 0
        }
      }
      directive.distance = distance
    
      const immediateCheckExpr = element.getAttribute('infinite-scroll-immediate-check')
      let immediateCheck = true
      if (immediateCheckExpr) {
        immediateCheck = Boolean(directive.vm[immediateCheckExpr])
      }
      directive.immediateCheck = immediateCheck
    
      if (immediateCheck) {
        doCheck.call(directive)
      }
    
      const eventName = element.getAttribute('infinite-scroll-listen-for-event')
      if (eventName) {
        directive.vm.$on(eventName, function() {
          doCheck.call(directive)
        })
      }
    }
    
    const doCheck = function(force) {
      const scrollEventTarget = this.scrollEventTarget
      const element = this.el
      const distance = this.distance
    
      if (force !== true && this.disabled) return; //eslint-disable-line
      const viewportScrollTop = getScrollTop(scrollEventTarget)
      const viewportBottom = viewportScrollTop + getVisibleHeight(scrollEventTarget)
    
      let shouldTrigger = false
    
      if (scrollEventTarget === element) {
        shouldTrigger = scrollEventTarget.scrollHeight - viewportBottom <= distance
      } else {
        const elementBottom = getElementTop(element) - getElementTop(scrollEventTarget) + element.offsetHeight + viewportScrollTop
    
        shouldTrigger = viewportBottom + distance >= elementBottom
      }
    
      if (shouldTrigger && this.expression) {
        this.expression()
      }
    }
    
    export default {
      // bind
      // bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
      // inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
      inserted(el, binding, vnode) {
        el[ctx] = {
          el,
          vm: vnode.context,
          expression: binding.value
        }
        const args = arguments
    
        doBind.call(el[ctx])
        el[ctx].vm.$nextTick().then(function() {
          if (isAttached(el)) {
            doBind.call(el[ctx], args)
          }
    
          el[ctx].bindTryCount = 0
    
          const tryBind = function() {
            if (el[ctx].bindTryCount > 10) return; //eslint-disable-line
            el[ctx].bindTryCount++
            if (isAttached(el)) {
              doBind.call(el[ctx], args)
            } else {
              setTimeout(tryBind, 50)
            }
          }
          tryBind()
        })
      },
    
      unbind(el) {
        if (el && el[ctx] && el[ctx].scrollEventTarget) {
          el[ctx].scrollEventTarget.removeEventListener('scroll', el[ctx].scrollListener)
        }
      }
    }
    

    解释为何出现会报错 addEventListener is not a function

    因为这个 滚动加载库 的实现就是一个 自定指令,在我调试的时候发现 el.parentNode (父节点)始终为null,因为获取不到节点,就无法知道 overflowYheight,所以就一直无效!

    通过vue官网发现 bindinserted 的区别。

    共同点dom插入都会调用,bindinserted之前
    不同点
    bind 时父节点为 null
    inserted 时父节点存在。
    bind是在dom树绘制前调用,inserteddom树绘制后调用

    bind: function (el) {
        console.log(el.parentNode)  // null
        console.log('bind')
    },
    inserted: function (el) {
        console.log(el.parentNode)  // <div class="directive-box">...</div>
        console.log('inserted')
    }
    

    相关文章

      网友评论

          本文标题:vue-infinite-scroll 库在 Modal 和 D

          本文链接:https://www.haomeiwen.com/subject/kdewcrtx.html