current position:Home>Analysis and expansion of Vue infinite scroll source code
Analysis and expansion of Vue infinite scroll source code
2021-08-27 09:42:49 【Answer 321】
Preface
Recently, the project is doing a web Version customer service chat tool , There is a chat window inside, which needs to scroll and load chat records , Just remember when you were hungry? The team had a vue-infinite-scroll plug-in unit , After looking at the source code, I found that it only supports scrolling down , But chat logs are scrolled up , Therefore, the function of upward scrolling is expanded on its basis . The following is mainly about vue-infinite-scroll Plug in source code analysis and how to expand the function of upward scrolling loading .
plug-in unit Usage
usage 1: Set the instruction host element itself to overflow:auto, Internal elements are used to support rolling , When scrolling to the bottom , Increasing the height of the internal elements simulates infinite scrolling .
<div class="app" v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10">
<div class="content"></div>
<div class="loading" v-show="busy">loading.....</div>
</div>
Copy code
.app {
height: 1000px;
border: 1px solid red;
width: 600px;
margin: 0 auto;
overflow: auto;
}
.content {
height: 1300px;
background-color: #ccc;
width: 80%;
margin: 0 auto;
}
.loading {
font-weight: bold;
font-size: 20px;
color: red;
text-align: center;
}
Copy code
var app = document.querySelector('.app');
new Vue({
el: app,
directives: {
InfiniteScroll,
},
data: function() {
return { busy: false };
},
methods: {
loadMore: function() {
var self = this;
self.busy = true;
console.log('loading... ' + new Date());
setTimeout(function() {
var target = document.querySelector('.content');
var height = target.clientHeight;
target.style.height = height + 300 + 'px';
console.log('end... ' + new Date());
self.busy = false;
}, 1000);
},
},
});
Copy code
The effect is as follows :
usage 2: Set the parent element to scroll , When you scroll to the bottom of the parent element , Increase your height , Simulate the operation of pulling the next page of data .:
<div class="app">
<div class="content" v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10"></div>
<div class="loading" v-show="busy">loading.....</div>
</div>
Copy code
The effect is exactly the same as the above .
The source code parsing
entrance
Let's start at the entrance , Because this plug-in is a vue Instructions , So the entrance is quite understandable
export default {
bind (el, binding, vnode) {
// Official warning : except el outside , All other parameters should be read-only , Do not modify . If you need to share data between hooks , It is recommended that the dataset To carry out .
el[ctx] = {
el,
vm: vnode.context, // vue example
expression: binding.value // Callback function required to scroll to the bottom or top , Usually used to load the next page of data
}
const args = arguments
el[ctx].vm.$once('hook:mounted', function () {
el[ctx].vm.$nextTick(function () {
// Determine whether the element is already on the page
if (isAttached(el)) {
doBind.call(el[ctx], args)
}
el[ctx].bindTryCount = 0
// interval 50ms polling 10 Time , Determine whether the element is already on the page
var 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) {
// Scroll event unbound
if (el && el[ctx] && el[ctx].scrollEventTarget) { el[ctx].scrollEventTarget.removeEventListener('scroll', el[ctx].scrollListener) }
}
}
Copy code
The core is after the host element is rendered , perform doBind Method , We guess it will be in doBind Bind the parent element of the scroll scroll event .
isAttached Method is used to determine whether an element has been rendered on the page , The judgment method is to check whether the tag name of the ancestor element is HTML:
var isAttached = function (element) {
var currentNode = element.parentNode
while (currentNode) {
if (currentNode.tagName === 'HTML') {
return true
}
// nodeType === 11 Indicates that the node is DocumentFragment
if (currentNode.nodeType === 11) {
return false
}
currentNode = currentNode.parentNode
}
return false
}
Copy code
Little knowledge :
nodeType==11 Node is DocumentFragment,DocumentFragment node Does not belong to the document tree , If the parent node of an element is DocumentFragment, Then it means that this element has not been inserted into Document tree in , There is no parent node .
DocumentFragment The interface represents part of the document ( Or a paragraph ). To be more exact , It represents one or more adjacent Document Nodes and all their descendants .
DocumentFragment The node does not belong to the document tree , inherited parentNode Property always null.
But it has a special behavior , This behavior makes it very useful , That is, when a request is sent to DocumentFragment When the node is inserted into the document tree , It's not DocumentFragment Oneself , But all its descendants . This makes DocumentFragment Become a useful placeholder , Temporarily store the nodes inserted into the document at one time . It also facilitates document clipping 、 Copy and paste operations , Especially with Range This is especially true when interfaces are used together .
It can be used Document.createDocumentFragment() Method to create a new empty DocumentFragment node .
Copy code
binding
Here, the user configuration item is obtained by obtaining the element attribute , And find the nearest scrollable parent element of the host, and then bind the scrolling event , Scroll through events to check when an event should be triggered
var doBind = function () {
// Bind only once , After binding, it returns
if (this.binded) return
this.binded = true
var directive = this
var element = directive.el
// Closure interval
var throttleDelayExpr = element.getAttribute('infinite-scroll-throttle-delay')
// Default 200 millisecond
var throttleDelay = 200
if (throttleDelayExpr) {
// Give priority to... On the instance throttleDelayExpr Corresponding properties
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.$once('hook:beforeDestroy', function () {
directive.scrollEventTarget.removeEventListener('scroll', directive.scrollListener)
})
// Whether to disable infinite scrolling
var disabledExpr = element.getAttribute('infinite-scroll-disabled')
// Not disabled by default
var disabled = false
// If this item is configured , Then listen for disabledExpr Corresponding properties
if (disabledExpr) {
this.vm.$watch(disabledExpr, function (value) {
directive.disabled = value
// When disable by false when , restart check
if (!value && directive.immediateCheck) {
doCheck.call(directive)
}
})
disabled = Boolean(directive.vm[disabledExpr])
}
directive.disabled = disabled
// The distance between the scroll bar and the top or bottom , Execute when less than this value doCheck
var distanceExpr = element.getAttribute('infinite-scroll-distance')
// The default is 0
var distance = 0
if (distanceExpr) {
distance = Number(directive.vm[distanceExpr] || distanceExpr)
if (isNaN(distance)) {
distance = 0
}
}
directive.distance = distance
// Whether to execute immediately doCheck
var immediateCheckExpr = element.getAttribute('infinite-scroll-immediate')
// The default is true
var immediateCheck = true
if (immediateCheckExpr) {
immediateCheck = Boolean(directive.vm[immediateCheckExpr])
}
directive.immediateCheck = immediateCheck
if (immediateCheck) {
doCheck.call(directive, false)
}
// When this event set on the component is triggered , Perform an inspection , It is generally used for manual trigger inspection
var eventName = element.getAttribute('infinite-scroll-listen-for-event')
if (eventName) {
directive.vm.$on(eventName, function () {
doCheck.call(directive)
})
}
}
Copy code
doBind In fact, it is to get the user configuration , Including trigger interval 、 Whether to trigger immediately 、 Whether to disable 、 The distance triggers 、 Trigger events manually , Control through these configuration items doCheck Execution opportunity .
When looking for a scrolling parent element, it starts from itself , So we can use it like 1 That way, set the instruction on the scroll element itself
// Start with yourself , Find the parent element with scrolling set . overflow-y by scroll or auto
var getScrollEventTarget = function(element) {
var currentNode = element;
// Solve listening body and html Compatibility problem with scrolling events on
// nodeType 1 Represents the element node
while (currentNode && currentNode.tagName !== 'HTML' && currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) {
var overflowY = getComputedStyle(currentNode).overflowY;
if (overflowY === 'scroll' || overflowY === 'auto') {
return currentNode;
}
currentNode = currentNode.parentNode;
}
return window;
};
Copy code
Core logic doCheck
This function is used to check whether it has scrolled to the bottom . Here, the elements to be scrolled can be themselves and a parent element .
var doCheck = function(force) {
var scrollEventTarget = this.scrollEventTarget;
var element = this.el;
var distance = this.distance;
if (force !== true && this.disabled) return;
// The distance between the top of the scrolling element and the top of the document coordinates
var viewportScrollTop = getScrollTop(scrollEventTarget);
// viewportBottom: The distance between the bottom of the scrolling element and the top of the document coordinates ; visibleHeight: The height of a scrolling element without a border
var viewportBottom = viewportScrollTop + getVisibleHeight(scrollEventTarget);
// Whether to trigger
var shouldTrigger = false;
// The scrolling element is the host element itself
if (scrollEventTarget === element) {
shouldTrigger = scrollEventTarget.scrollHeight - viewportBottom <= distance;
} else {
// The scroll element is the parent element of the host element
// The difference between itself and the top of the parent element
var elementBottom = getElementTop(element) - getElementTop(scrollEventTarget) + element.offsetHeight + viewportScrollTop;
shouldTrigger = viewportBottom + distance >= elementBottom;
}
if (shouldTrigger && this.expression) {
this.expression(); // Trigger bound infinite scroll function
}
};
Copy code
The scroll element is the host element
The scroll element is the parent element of the host element
expand
We can see from the source code that , The plug-in only judges the scroll down , In the preface, we mentioned that the business needs to scroll up . Analyzed the source code , We know that the internal of the plug-in is through ele.getAttribute() To get the name of the configuration item , And then in doCheck Make a judgment and trigger the wireless scrolling function bound by the user . So we have the following ideas :
1、 Add configuration items of trigger type
// The trigger type is scroll up or scroll down
var triggerTypeExpr = element.getAttribute('infinite-scroll-trigger-type')
// The default trigger type is scroll down
var triggerType = 'scrollDown'
if (triggerTypeExpr) {
triggerType = directive.vm[triggerTypeExpr] || triggerTypeExpr
// Optional value is 'scrollDown', 'scrollUp'
if (!['scrollDown', 'scrollUp'].includes(triggerType)) {
triggerType = 'scrollDown'
}
}
directive.triggerType = triggerType
Copy code
2、doCheck It is the trigger type when 'scrollUp' Make trigger condition judgment
Here, we actually need to judge whether the distance between the top of the host element and the top of the scroll element is less than or equal to the distance threshold, that is distance
// The scrolling element is the host element itself
if (scrollEventTarget === element) {
if (triggerType === 'scrollDown') {
shouldTrigger = scrollEventTarget.scrollHeight - viewportBottom <= distance
} else {
shouldTrigger = viewportScrollTop <= distance
}
} else {
// The scroll element is the parent element of the host element
// The difference between itself and the top of the parent element
var topGap = getElementTop(element) - getElementTop(scrollEventTarget)
// elementBottom: The distance between the bottom of the host element and the top of the document coordinates
var elementBottom = topGap + element.offsetHeight + viewportScrollTop
if (triggerType === 'scrollDown') {
shouldTrigger = viewportBottom + distance >= elementBottom
} else {
shouldTrigger = topGap <= distance
}
}
Copy code
The expanded configuration items are as follows , The usage is basically the same as the above , Just more configuration items
Option | Description |
---|---|
infinite-scroll-disabled | infinite scroll will be disabled if the value of this attribute is true. |
infinite-scroll-distance | Number(default = 0) - the minimum distance between the bottom of the element and the bottom of the viewport before the v-infinite-scroll method is executed. |
infinite-scroll-immediate-check | Boolean(default = true) - indicates that the directive should check immediately after bind. Useful if it's possible that the content is not tall enough to fill up the scrollable container. |
infinite-scroll-listen-for-event | infinite scroll will check again when the event is emitted in Vue instance. |
infinite-scroll-throttle-delay | Number(default = 200) - interval(ms) between next time checking and this time. |
infinite-scroll-trigger-type | String(default = 'scrollDown') - choose between 'scrollDown' and 'scrollUp'. |
copyright notice
author[Answer 321],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210827094243113r.html
The sidebar is recommended
- Crazy blessing! Tencent boss's "million JVM learning notes", real topic of Huawei Java interview 2020-2021
- JS JavaScript how to get the subscript of a value in the array
- How to implement injection in vuex source code?
- One line of code teaches you how to advertise on Tanabata Valentine's Day - Animation 3D photo album (music + text) HTML + CSS + JavaScript
- An article disassembles the pyramid architecture behind the gamefi outbreak
- BEM - a front-end CSS naming methodology
- Another ruthless character fell by 40000, which was "more beautiful" than Passat and maiteng, and didn't lose BMW
- August 23, 2021 Daily: less than three years after its establishment, Google dissolved the health department
- The female doctor of Southeast University is no less beautiful than the female star. She has been married four times, and her personal experience has been controversial
- There are many potential safety hazards in Chinese restaurant. The top of the program recording shed collapses, and the artist will fall down if he is careless
guess what you like
-
Introduction to flex flexible layout in CSS -- learning notes
-
CSS learning notes - Flex layout (Ruan Yifeng tutorial summary)
-
Today, let's talk about the arrow function of ES6
-
Zheng Shuang's 30th birthday is deserted. Chen Jia has been sending blessings for ten years. Is it really just forgetting to make friends?
-
Asynchronous solution async await
-
"The story of huiyeji" -- people are always greedy, and fairies should be spotless!
-
Installing Vue devtool for chrome and Firefox
-
Basic usage of JS object
-
1. JavaScript variable promotion mechanism
-
Two easy-to-use animation JS that make the page move
Random recommended
- Front end Engineering - scaffold
- Java SQL Server intelligent fixed asset management, back end + front end + mobile end
- Mediator pattern of JavaScript Design Pattern
- Array de duplication problem solution - Nan recognition problem
- New choice for app development: building mobile applications using Vue native
- Vieira officially terminated his contract and left the team. The national security club sent blessings to him
- Less than 200000 to buy a Ford RV? 2.0T gasoline / diesel power, horizontal bed / longitudinal bed layout can be selected
- We are fearless in epidemic prevention and control -- pay tribute to the front-line workers of epidemic prevention!
- Front end, netty framework tutorial
- The wireless charging of SAIC Roewe rx5 plus is so easy to use!
- Upload and preview pictures with JavaScript, and summarize the most complete mybatis core configuration file
- Ajax foundation - HTTP foundation of interview essential knowledge
- [JS] 10. Closure application (loop processing)
- Left hand IRR, right hand NPV, master the password of getting rich