因为需要在PC端使用上拉加载功能,发现 Antd
描述了一个 滚动加载库
安装依赖
npm install vue-infinite-scroll --save
选项解释
v-infinite-scroll="loadMore"
表示回调函数是loadMore
infinite-scroll-disabled="busy"
表示由变量busy决定是否执行loadMore
,false
则执行loadMore
,true
则不执行,看清楚,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
的值,以及是否滚动到底,只有当:busy
为false
且滚动到底,回调函数才会执行。
介绍完毕,看看遇到的问题
遇到的问题
参考了 https://blog.csdn.net/u012451520/article/details/117113286
但是这个库只适合在这个页面使用,不适合在一些弹窗的组件中使用!
https://github.com/ElemeFE/vue-infinite-scroll/issues/147
原因呢在上面的参考连接上已经有了(在这里我在贴出来一下)
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
,因为获取不到节点,就无法知道 overflowY
和 height
,所以就一直无效!
通过vue官网发现 bind
和 inserted
的区别。
共同点: dom
插入都会调用,bind
在inserted
之前
不同点:
bind
时父节点为 null
inserted
时父节点存在。
bind
是在dom
树绘制前调用,inserted
在dom
树绘制后调用
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')
}
网友评论