vue-infinite-scroll 库在 Modal 和 D

2022-01-12  本文已影响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')
}
上一篇 下一篇

猜你喜欢

热点阅读