Preview of modified version
This article was written three days ago, and some bigwigs gave me some suggestions for revision, which I think is really on the mark. So there is a modified version of this upgrade. The code has been synchronized to GitHub.
The modifications are as follows:
- Added graphic instructions, intuitive instructions
getBoundingClientRect()
Collection means - How to avoid frequent reflow risk (optimize scroll listening)
- Performance issues associated with listening for scrolling (use
IntersectionObserver
, new scheme)
The changes are in points 4 and 5. If you’ve read this article, you can go straight to the changes and updates. Or you could watch it again.
preface
The first demand I received when I joined the second company was to repair the rolling ceiling effect that I outsourced before. I was wondering why a scroll top was buggy, but when I looked at the code, I realized that offsetTop was used directly and that there was no compatibility.
offsetTop
Used to get the distance (offset) from the current element to the top of the positioning parent (element.offsetParent).
The parent offsetParent is defined as the position closest to the current element. = static parent element.
Maybe whoever wrote this code didn’t pay attention to the “locate the parent” condition.
Later in the project, the effect of rolling top suction always needs to be realized. Now I will introduce the four ways of implementing rolling top suction I know in detail.
directory
- use
position:sticky
implementation - Use the JQuery
offset().top
implementation - Use native
offsetTop
implementation - use
obj.getBoundingClientRect().top
implementation - Performance Optimization
Do you know all four of these ways? The relevant code has been uploaded to GitHub, interested can clone the code to run locally. Please give a star support.
Four implementations
Let’s take a look at the renderings:
Implement sticky with position:sticky
1. What is sticky positioning?
Sticky positioning is equivalent to the combination of relative positioning and fixed positioning; When the distance between an element and its parent reaches the sticky sticky positioning requirement in the scrolling process of a page element; The relative positioning effect of elements has been changed to fixed positioning effect.
MDN portal
2. How to use it?
Conditions of use:
- The parent element cannot
overflow:hidden
oroverflow:auto
attribute - You must specify
Top, bottom, left, right
One of four values, otherwise only in relative position - The height of the parent element cannot be lower
sticky
Height of elements sticky
The element is valid only within its parent element
To achieve this effect, apply the following styles to the elements that need to scroll to the top:
.sticky {
position: -webkit-sticky;
position: sticky;
top: 0;
}
Copy the code
3. Is this property useful?
Let’s first look at the compatibility of this property in Can I use:
As you can see, the compatibility of this property is not very good because the API is still experimental. However, the API is fairly compatible on IOS.
So when we use this API in a production environment, we usually use it in a couple of ways.
Offset ().top with JQuery
We know that JQuery encapsulates the API for manipulating DOM and reading DOM properties. Based on the combination of offset(). Top API and scrollTop(), we can also achieve the scrolling top effect.
. window.addEventListener('scroll', self.handleScrollOne); . handleScrollOne:function() {
let self = this;
let scrollTop = $('html').scrollTop();
let offsetTop = $('.title_box').offset().top; self.titleFixed = scrollTop > offsetTop; }...Copy the code
This is fine, but as JQuery is slowly dying out, we try not to use the JQuery API in our code. We can handle the native offsetTop ourselves based on the source of offset().top. So there’s a third way.
ScrolloTop () has compatibility issues. $(‘ HTML ‘).scrolltop () has a value of 0 in wechat browser, Internet Explorer, and some Firefox versions.
Use the native offsetTop implementation
We know that offsetTop is an offset relative to the location parent. If the element that needs to scroll to the top appears to position the parent, then offsetTop does not get the distance from the top of the page.
We can do the following for offsetTop ourselves:
getOffset: function(obj,direction){
let offsetL = 0;
let offsetT = 0;
while( obj! == window.document.body && obj ! == null ){ offsetL += obj.offsetLeft; offsetT += obj.offsetTop; obj = obj.offsetParent; }if(direction === 'left') {return offsetL;
}else {
returnoffsetT; }}Copy the code
Use:
. window.addEventListener('scroll', self.handleScrollTwo); . handleScrollTwo:function() {
let self = this;
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
let offsetTop = self.getOffset(self.$refs.pride_tab_fixed); self.titleFixed = scrollTop > offsetTop; }...Copy the code
Do you see any problems with both approaches?
Do we really need to use the value of scrolltop-offsetTop to achieve the effect of rolling the top? The answer is no.
Let’s look at the fourth option.
Four, using obj. GetBoundingClientRect (). The top
Definition:
This API tells you how far an element on a page is relative to the browser window.
Use: the TAB can be used to suction a top obj. GetBoundingClientRect (). The top instead of scrollTop – offsetTop, code is as follows:
// html
<div class="pride_tab_fixed" ref="pride_tab_fixed">
<div class="pride_tab" :class="titleFixed == true ? 'isFixed' :''">
// some code
</div>
</div>
// vue
export default {
data() {return{
titleFixed: false}},activated(){
this.titleFixed = false;
window.addEventListener('scroll', this.handleScroll); }, methods: {// handleScroll:function () {
let offsetTop = this.$refs.pride_tab_fixed.getBoundingClientRect().top;
this.titleFixed = offsetTop < 0;
// some code
}
}
}
Copy the code
OffsetTop and getBoundingClientRect()
1. getBoundingClientRect():
Used to get the left, top, right, and bottom positions of an element on a page relative to the browser window. Does not contain rolled up portions of the document.
This function returns an object with eight attributes: top, right, butTom, left, width, height, x, y
2. offsetTop:
Used to get the distance (offset) from the current element to the top of the positioning parent (element.offsetParent).
The parent offsetParent is defined as the position closest to the current element. = static parent element.
The offsetTop and offsetParent methods are combined to get the distance from the top of the body. The code is as follows:
getOffset: function(obj,direction){
let offsetL = 0;
let offsetT = 0;
while( obj! == window.document.body && obj ! == null ){ offsetL += obj.offsetLeft; offsetT += obj.offsetTop; obj = obj.offsetParent; }if(direction === 'left') {return offsetL;
}else {
returnoffsetT; }}Copy the code
Extend knowledge
OffsetWidth:
Border-left + padding-left + width + padding-right + border-right
OffsetHeight:
Border-top + padding-top + height + padding-bottom + border-bottom
Note: If there is a vertical scroll bar, offsetWidth also includes the width of the vertical scroll bar; If there is a horizontal scroll bar, offsetHeight also includes the height of the horizontal scroll bar;
OffsetTop:
The pixel distance between the top outer border of the element and the top inner border of the offsetParent element;
The offsetLeft:
The pixel distance between the left outer border of the element and the left inner border of the offsetParent element;
Matters needing attention
- All offset attributes are read-only;
- If the element is set to
display:none
, its offset attributes are all 0; - The offset property needs to be recalculated each time it is accessed (save the variable);
- When the DOM is not initialized, the DOM is read and 0 is returned. For this problem we need to wait until the DOM element is initialized.
Two problems encountered
First, the moment of top suction accompanied by shaking
The reason for jitter is that when the top element position changes to fixed, that element is removed from the document stream and the next element is filled. It is this fill operation that causes jitter.
The solution
Add a parent element of equal height to the top element, and we listen for getBoundingClientRect(). Top to achieve the top effect:
<div class="title_box" ref="pride_tab_fixed">
<div class="title" :class="titleFixed == true ? 'isFixed' :''"> use ` obj. GetBoundingClientRect (). Top ` implementation < / div > < / div >Copy the code
This solution can solve the jitter Bug.
Two, the top suction effect can not respond in time
This problem is a headache for me, and I have never cared about it before. I didn’t notice it until one day when I used Meituan to order food.
Description:
- When the page scrolls down, the top element must wait for the page to stop before the top effect appears
- When the page scrolls up, the top element does not recover until the top element restores the document flow position, but only after the page has stopped scrolling
Cause: On ios, scroll listening events cannot be monitored in real time. Related events are triggered only when scrolling stops.
Solution:
Remember position:sticky in the first scenario? This property has good compatibility with IOS6 and beyond, so we can differentiate between IOS and Android devices to do two things.
IOS uses position:sticky, Android uses scroll to listen for getBoundingClientRect().top.
What if the IOS version is too low? Provide an idea here: window. RequestAnimationFrame ().
Performance Optimization (New)
This is the end of the way to roll the top in 4, but is that really the end of it? There is room for improvement.
We optimize performance in two directions (actually one direction) :
- Avoid over-reflow
- Optimize scroll listening events
Problem Locating Process
We know that excessive reflow can degrade the performance of a page. Therefore, we need to reduce the number of reflows as much as possible to give users a more fluid feeling.
Some friends may say that I know, but what is the relationship between this and rolling suction top?
Don’t worry, do you remember that the scroll top used offsetTop or getBoundingClientRect().top to get the offset of the response?
Now that there is an attribute for the read element it naturally causes the page to be reflow.
Therefore, the direction of our optimization is to reduce the number of times to read the element attribute. When we look at the code, we find that the relevant method will be called to read the offset of the element as soon as the scroll event is triggered.
Optimization scheme
There are two solutions to this problem:
- Sacrificing smoothness to meet performance, use throttling to control the invocation of related methods
- By using IntersectionObserver in conjunction with throttling, smoothness is also sacrificed.
The first option
This scheme is very common, but the side effect is also obvious, that is, there will be some delay in the top effect, if the product is acceptable then it is a good method.
This allows you to control reading only for a certain amount of time
In this case, the throttling functions are directly throttle methods encapsulated in Lodash.js.
The code is as follows:
window.addEventListener('scroll', _.throttle(self.handleScrollThree, 50));
Copy the code
Second option
The second option is relatively easy to accept, that is, IntersectionObserver will be adopted if it is supported, and throttle will be adopted if it is not.
Let’s talk about IntersectionObserver first
IntersectionObserver can be used to monitor whether elements enter the visible area of the device without frequent calculation.
With this property we can achieve performance optimization without reading the relative positions of elements when they are not visible; Throttle is used when the browser does not support this attribute.
Let’s see how compatible this property is:
It’s probably more than 60% supported and still usable in projects (you need to do compatibility).
For details on how to use IntersectionObserver, please refer to MDN or Ruan Yifeng’s tutorial.
The code for optimization using IntersectionObserver and Throttle is as follows:
IntersectionObserverFun: function() {
let self = this;
let ele = self.$refs.pride_tab_fixed;
if( !IntersectionObserver ){
let observer = new IntersectionObserver(function() {let offsetTop = ele.getBoundingClientRect().top;
self.titleFixed = offsetTop < 0;
}, {
threshold: [1]
});
observer.observe(document.getElementsByClassName('title_box') [0]); }else {
window.addEventListener('scroll', _.throttle(function() {letoffsetTop = ele.getBoundingClientRect().top; self.titleFixed = offsetTop < 0; }, 50)); }},Copy the code
Pay attention to
The IntersectionObserver API is asynchronous and does not trigger with the rolling synchronization of the target element.
The specification states that IntersectionObserver should be implemented using requestIdleCallback(). It will not immediately implement the callback, it calls the window. The requestIdleCallback () to asynchronous execution we specify the callback function, but also specifies the maximum delay time is 100 milliseconds.
Conclusion:
Such a scheme combining IntersectionObserver and throttle is an alternative scheme, which has the advantage of effectively reducing the risk of page reflow. However, it also has the disadvantage of sacrificing the smoothness of the page. How to make a choice depends on the needs of the business.
Front-end dictionary series
The Front End Dictionary series will continue to be updated, and in each issue I will cover a topic that comes up frequently. I would appreciate it if you could correct any inpreciseness or mistakes in the reading process. I will be delighted if I learn something from this series.
If you think my article is good, you can follow my wechat public account, which will reveal the plot in advance.
If you want to join a group, you can also add a smart robot that automatically pulls you into the group:
Portal of popular articles
- Solution to the rolling penetration problem
- Inheritance (1) – Prototype chain Do you really understand?
- Advanced Network Basics (PART 1)
- Advanced Network Basics (part 2)
- Implementation of Canvas snow background performance considerations
- [Front-end dictionary] What is involved in caching from entering URL to rendering (very detailed)