Elevator navigation, also known as anchor navigation, scrolls the corresponding marked element within the page to the viewport when the anchor element is clicked. And the corresponding anchor point is also highlighted when the element within the page scrolls. Elevator navigation generally puts the anchors on the left and right sides, similar to an elevator. Common elevator navigation effects are as follows, such as in some official documents:
It may have been used beforegetBoundingClientRect()
to determine if an element is in the viewport to achieve a similar effect, but now there's a more convenient way to do it, and that's theIntersectionObserver + scrollIntoView
, easy elevator navigation.
scrollIntoView() Introduction
scrollIntoView()
method scrolls the element's parent container so that the element appears in the visual area. The default is immediate scrolling with no animation.
If you want to add an animation effect, you can do so:
scrollIntoView({
behavior: 'smooth' // instant for immediate scrolling
})
It also has two optional parametersblock
cap (a poem)inline
。
block
Indicates the vertical alignment of the element to its parent container when it appears in the viewport.inline
Indicates the horizontal alignment of the element to its parent container when it appears in the viewport.
They also have four values to choose fromstart
、center
、end
、nearest
The The default isstart
;
scrollIntoView({
behavior: 'smooth',
block:'center',
inline:'center',
})
For block
-
start
Aligns the top of the element with the top of the scroll container. -
center
Aligns the center of the element vertically with the center of the scroll container. -
end
Aligns the bottom of the element with the bottom of the scroll container.
For inline
-
start
Aligns the left side of the element with the left side of the scroll container. -
center
Aligns the center of the element horizontally with the center of the scroll container. -
end
Aligns the right side of the element with the right side of the container.
(indicates contrast)nearest
Whether vertically or horizontally, as long as it appears in the viewport the task is complete. This can be interpreted as making the element appear in the viewport with a minimal amount of movement, (lazy movement). If the element is already fully present in the viewport, no change occurs.
To get a feel for the change, there are four rows and five columns in the scrolling container below, containing everything from the letterA
until (a time)T
. Clickappear in the viewport
button takes the values of the three drop-down boxes as parameters to call thescrollIntoView()
Methods.
Let's take another look at the settings fornearest
Scrolling after
When the lettersG
When inside the viewport, calling the method scrolls the container without changing it. When theG
not completely in the viewport, it will scroll until it appears completely in the viewport.
Check out this full example herescrollIntoView Optional Parameter Practice (codepen)
And scrollIntoView compatibility is great!
IntersectionObserver Introduction
The Intersection Observer API provides a way ofsynchronousA method for detecting changes in the viewport intersection of a target element with an ancestor element or a top-level document. That is, the ability to determine whether an element is in the viewport or not, and the ability to listen to the proportion of the visible portion of the element that appears in the viewport, so that we can execute our customized logic.
Since it's asynchronous, it doesn't block the main thread, and performance is naturally better than the previous frequent execution of thegetBoundingClientRect()
It's better to determine if an element is in the viewport.
Create an IntersectionObserver
let options = {
root: (selector),
rootMargin: "0",
threshold: 1.0,
};
let observer = new IntersectionObserver(callback, options);
let target = (selector);
(target); //Listen to the target element
This is accomplished by calling theIntersectionObserver
The constructor creates a crossviewer. The constructor takes two arguments, a callback function and an optional. In the above example, the callback function is called when the element is fully present (100%) in the viewport.
selectable
-
root
The element used as the viewport must be an ancestor of the target. Defaults to the browser viewport. -
rootMargin
The margins around the root, i.e. the size of the viewport that can limit the detection of the root element. The value of the directional size of the peace is commonly usedmargin
Same as, for example"10px 20px 30px 40px"
(top, right, bottom, left). Only positive numbers increase the root element detection range and negative numbers decrease the detection range.
For example, let's set a scrollable div container to be the root element, with a width and height of 1000px. Setting therootMargin:0
Indicates that the viewport size of the root element is the size of the current viewable area of the root element, i.e., 1000px * 1000px. setrootMargin:25% 0 25% 0
means the top and bottom margins are 25%, then the detection viewport size is 1000px * 500px.
-
threshold
A number, or an array of numbers, indicating what percentage of the viewport the target appears in should the observer's callback be executed. If you only want to detect when visibility exceeds 50%, you can use a value of 0.5. If you want the callback to be executed every time the visibility exceeds 25%, you need to specify the array [0, 0.25, 0.5, 0.75, 1]. The default value is 0 (which means that the callback will run as long as one pixel is visible). A value of 1.0 means that the threshold will not be considered passed until every pixel is visible.
callback function
When the target element matches the configuration in the optional, it triggers our defined callback function
let options = {
root: (selector),
rootMargin: "0",
threshold: 1.0,
};
let observer = new IntersectionObserver(function (entries) {
(entry => {
})
}, options);
entries represents an array of listened-to target elements, each entry in the array has some of the following values
-
Returns the boundary information of the target element, the value and
getBoundingClientRect()
The form is the same. -
The proportion of target and root elements that intersect, i.e., appear in the detection region.
-
Returns information about the boundaries of the intersection area of the root and target elements, with the values and
getBoundingClientRect()
The form is the same. -
Returns true or fasle, indicating whether or not it appears within the root element detection area
-
Returns information about the boundaries of the root element, the value and
getBoundingClientRect()
The form is the same. -
Returns the target element that appears within the detection area of the root element
-
Returns the timestamp from when the cross-watcher was created to when the target element appeared in the detection area
For example, to detect that 75% of the target elements appear in the detection area do this.
(entry => {
if( && >0.75){
}
})
Listen to the target element
After creating an observer, one or more target elements are observed.
let target = (selector);
(target);
('div').forEach(el => {
(el)
})
IntersectionObserver
The compatibility is also good:
masteredIntersectionObserver + scrollIntoView
usage, implementing elevator navigation is simple.
Simply write an elevator navigationhtml
cap (a poem)css
<div class="a" style="background:aqua;">Chapter 1</div>
<div class="b" style="background: blueviolet;">Chapter 2</div>
<div class="c" style="background: chartreuse;">Chapter 3</div>
<div class="d" style="background: darkgoldenrod;">Chapter 4</div>
<div class="e" style="background: firebrick;">Chapter 5</div>
<div class="f" style="background: gold;">Chapter 6</div>
<div class="g" style="background: hotpink;">Chapter 7</div>
<ul class="rightBox">
<li class="aLi"> Chapter 1</li>
<li class="bLi"> <li class="bLi"> Chapter 2</li>
<li class="cLi">Chapter 3</li>.
<li class="dLi">Chapter 4</li>.
<li class="eLi">Chapter 5</li>.
<li class="fLi">Chapter 6</li>.
<li class="gLi">Chapter 7</li>.
</ul>
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
}
ul,li{list-style: none;}
body {
padding: 20px 0;
}
div{
width: 60%;
height: 70%;
border-radius: 10px;
margin-left: auto;
margin-right: auto;
opacity: 0.4;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
font-weight: bold;
color: #000;
}
div+div {
margin-top: 20px;
}
.rightBox {
position: fixed;
right: 20px;
top: 50%;
color: teal;
transform: translatey(-50%);
}
li {
cursor: pointer;
box-sizing: border-box;
border: 1px solid #fff;
border-radius: 4px;
padding: 8px 12px;
}
li:hover {
background: #f5d2c4;
}
.active {
background: #f5d2c4;
}
Preview below:
Step 1: Click on the navigation menu on the right and utilize thescrollIntoView
method makes the element corresponding to the content area appear in the visual area.
let rightBox = ('.rightBox')
('click', function (e) {
let target = || ;
if (target && !('rightBox')) {
('.' + ('Li', '')).scrollIntoView({
behavior: 'smooth',
block: 'center'
})
}
}, false)
Step 2: When the page container scrolls, when the target element appears in the detection area, then linkage changes the style of the corresponding navigation.
here arethreshold
be set to1
, which means that the callback is executed when the target element is fully displayed in the visual area, changing the style of the navigation menu.
let observer = new IntersectionObserver(function (entries) {
(entry => {
let target = ('.' + + 'Li')
if () { // Appears in the detection area
('li').forEach(el => {
if(('active')){
('active')
}
})
if (!('active')) {
('active')
}
}
})
}, {
threshold: 1
})
('div').forEach(el => {
(el)
})
The effect is as follows:
The basic requirements are met, but there are some issues with the scrolling process. For example, when switching back and forth between two consecutive elements, the second element is displayed in a higher percentage of the detection area than the first element, although it doesn't reach 100%, and the navigation menu is still displayed for the first element. See the picture below:
So here you can control it more finely, highlighting the navigation menu of whoever displays a higher percentage between the two elements.
let observer = new IntersectionObserver(function (entries) {
(entry => {
if ( && > 0.65) {
}
})
}, {
threshold: [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
})
This is set herethreshold: [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
, when the proportion of target elements appearing in the detection area reaches20%,30%,40%,50%,60%,70%,80%
The callback function is executed when the target element is visible and displayed in the detection area at a rate of65%
when highlighting the navigation menu. This works better:
Check out this full example hereIntersectionObserver + scrollIntoView for elevator navigation.
Of course, it depends on the actual element block size and business requirements.
If it helps, help out with the kudos, thanks~