当前位置:网站首页>Scroll, do you understand?
Scroll, do you understand?
2022-06-29 15:22:00 【User 1097444】
1、 introduction
Recently, when implementing the scrolling interaction of the list , It's a complicated business scenario that makes people doubt their life . Today, I mainly talk about scroll Application :
- CSS Smooth scrolling
- JS Rolling method
- Distinguish between manual scrolling and script scrolling
2、CSS Smooth scrolling
2.1 One line style improves the experience
In some scenarios where scrolling interaction is frequent , We can improve the user experience by adding a line style to the scrollable container .
scroll-behavior: smooth; for instance , In the documentation website , We often use # To navigate to the corresponding browsing location .
Like this one above Example , We first pass # To anchor the corresponding content , Implemented a tab The effect of switching :
<div>
<a href="#A">A</a>
<a href="#B">B</a>
<a href="#C">C</a>
</div>
<div className="scroll-ctn">
<div id="A" className="scroll-panel">
A
</div>
<div id="B" className="scroll-panel">
B
</div>
<div id="C" className="scroll-panel">
C
</div>
</div>meanwhile , For smooth scrolling , We set the following... On the scroll container CSS:
.scroll-ctn {
display: block;
width: 100%;
height: 300px;
overflow-y: scroll;
scroll-behavior: smooth;
border: 1px solid grey;
} stay scroll-behavior: smooth Under the influence of , The default scrolling in the container presents the effect of smooth scrolling .
2.2 Compatibility
IE and Mobile ios Poor compatibility on , Rely on... When necessary polyfill.
2.3 Be careful
1、 Set... On the scrollable container scroll-behavior: smooth after , Its priority is higher than JS Methodical . in other words , stay JS It is specified in behavior: auto, Want to restore the effect of immediately scrolling to the target location , It won't work .
2、 Set... On the scrollable container scroll-behavior: smooth after , It can also affect browsers Ctrl+F The performance of the , Make it also present the effect of smooth scrolling .
3、JS Rolling method
3.1 The basic method
What we know as primordial scroll Method , About these :
- scrollTo: Scroll to the target location
- scrollBy: Scroll relative to the current position
- scrollIntoView: Let the elements scroll into the field of view
- scrollIntoViewIfNeeded: Let the elements scroll into the field of view ( If not in view )
We use more scrollTo For example , There are two ways to call it :
// The first form
const x = 0, y = 200;
element.scrollTo(x, y);
// The second form
const options = {
top: 200,
left: 0,
behavior: 'smooth'
};
element.scrollTo(options); And the act of scrolling , That is... In the method parameter behavior Divided into two :
- auto: Scroll now
- smooth: Smooth scrolling
In addition to the above 3 individual api, We can also use simple and crude scrollTop、 scrollLeft To set the scroll position :
// Set up container Up scrolling distance 200
container.scrollTop = 200;
// Set up container Left scrolling distance 200
container.scrollLeft = 200; It is worth mentioning that , scrollTop、 scrollLeft The compatibility is very good . And compared to other methods , Generally, there are no moths ( I'll talk about it later ).
3.2 application
I used to use scrolling in the past :
- Component initialization , Navigate to the target location
- Click an element at the bottom of the current page , Trigger scrolling
- ......
Take up a Example , Now I hope that after the list component is loaded , The list can automatically scroll to the third element .
According to the above mentioned, we can implement it in many ways , Suppose we have added... To the list container scroll-behavior: smooth The style of , And then in useEffect hook To call the scrolling method :
import React, { useEffect, useRef } from "react";
import "./styles.css";
export default function App() {
const listRef = useRef({ cnt: undefined, items: [] });
const listItems = ["A", "B", "C", "D"];
useEffect(() => {
// Navigate to the third
const { cnt, items } = listRef.current;
// The first one is
// cnt.scrollTop = items[2].offsetTop;
// The second kind
// cnt.scrollTo(0, items[2].offsetTop);
// The third kind of
// cnt.scrollTo({ top: items[2].offsetTop, left: 0, behavior: "smooth" });
// A fourth
items[2].scrollIntoView();
// items[2].scrollIntoViewIfNeeded();Ï
}, []);
return (
<div className="App">
<ul className="list-ctn" ref={(ref) => (listRef.current.cnt = ref)}>
{listItems.map((item, index) => {
return (
<li
className="list-item"
ref={(ref) => (listRef.current.items[index] = ref)}
key={item}
>
{item}
</li>
);
})}
</ul>
</div>
);
}In the above code , Mentioned four ways :
- Container of scrollTop assignment
- Container of scrollTo Method , Pass in the horizontal and vertical scroll position
- Container of scrollTo Method , Pass in scrolling configuration
- Elemental scrollIntoView / scrollIntoViewIfNeeded Method
Although the final effect is the same , But these methods are actually somewhat different .
3.3 scrollIntoView The strange phenomenon of
3.3.1 Overall page offset
Recently, there were some historical use cases , In this case :
The phenomenon is probably , When I pass the button , When scrolling to a message in the chat area , The whole page is offset ( Move up ). Take another look at the code , It was found that scrollIntoView:
Because it was the first time I met , So the Almighty stack overflow Went on a walk , See a similar problem :scrollIntoView Cause the whole page to move .
Under what circumstances does this problem often occur ?
1、 Page has iframe Under the circumstances , Like this one Example .
Performance is when iframe When the contents in the scroll , The main page also scrolls . This is obviously and MDN Inconsistent description on :
Element Interface scrollIntoView () Method scrolls the parent container of the element , Cause to be called scrollIntoView () The element is visible to the user .
2、 Use it directly scrollIntoView() Default parameters
Let's talk about it first. scrollIntoView() What parameters are supported :
element.scrollIntoView(alignToTop); // Boolean Type parameter
element.scrollIntoView(scrollIntoViewOptions); // Object Type parameter (1)alignToTop
- If
true, The top of the element will be aligned with the top of the visible area of the scrolling area it is in . CorrespondingscrollIntoViewOptions: {block: "start", inline: "nearest"}. This is the default value for this parameter . - If
false, The bottom of the element will be aligned with the bottom of the visible area of the scrolling area it is in . CorrespondingscrollIntoViewOptions: {block: "end", inline: "nearest"}.
(2)scrollIntoViewOptions
Contains the following properties :
behaviorOptional Define animation transitions ,"auto"or"smooth"One of . The default is"auto".blockOptional Define the vertical alignment ,"start","center","end", or"nearest"One of . The default is"start".inlineOptional Define the horizontal alignment ,"start","center","end", or"nearest"One of . The default is"nearest".
Back to our question , Why use default parameters , namely element.scrollIntoView(), Will cause the problem of page offset ?
The key lies in block: "start", From the above parameter description, we know that , By default, parameters are not transferred , What is taken is block: start, It said “ The top of the element is aligned with the top of the visible area of the scrolling area ”. But from the aspect of phenomenon , The impact is not just “ Scrolling area ” perhaps “ Parent container ”, The ancestors DOM Elements are also affected .
Because I can't find scrollIntoView Source code , For the time being, you can only locate yes start This default value is used for demon . Since there is a problem with the native method , We need to take some other measures to replace .
3.3.2 Solution
1、 Change parameters
Since it is block: start There is a problem , Then let's change the effect , Recommended here nearest.
element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });Maybe some curious friends want to ask , What exactly do these aligned options mean ? stay MDN There seems to be no special explanation in it . Here quote stackoverflow On the one Highly praise the answer , Can help you better understand .
- Use
{block: "start"}, The element is aligned at the top of its ancestor . - Use
{block: "center"}, Elements are aligned in the middle of their ancestors . - Use
{block: "end"}, Elements are aligned at the bottom of their ancestors . - Use
{block: "nearest"}:- If you are currently below their ancestors , The element is aligned at the top of its ancestor .
- If you are currently above their ancestors , Then the element is aligned at the bottom of its ancestor .
- If it is already in the view , Keep it as it is. .
2、scrollTop/scrollLeft
As mentioned above scrollTop/scrollLeft Assignment is the best scrolling method for compatibility , We can use it instead of the default scrollIntoView () The performance of the .
For example, to top an element , You can define a scrollable container scrollTop For this element offsetTop:
container.scrollTop = element.offsetTop;It is worth mentioning that , combination CSS Of scroll-behavior, This assignment method can also achieve smooth scrolling effect .
4、 How to distinguish between manual scrolling and script scrolling
4.1 background
Recently I met such a demand , Make a subtitle manuscript that highlights the current playing content in real time . The core interaction is :
1、 When the user does not scroll the document artificially , The function of automatic page turning will be maintained
2、 When the user scrolls the document artificially , Subsequent pages will not be automatically flipped , And appear “ Return to the current playback position ” The button
3、 If you click “ Return to the current playback position ” The button , Will return to the target location , And restore the function of automatic page turning .
Like in the above demonstration , The user triggered an artificial scroll , After clicking on “ Return to the current playback position ”, Triggered script scrolling .
4.2 Manual scrolling
How to define “ Manual scrolling ” Well ? What we know about man-made scrolling , contain :
- Mouse scrolling
- Keyboard direction keys scroll
- The indent key scrolls
- Page keys scroll
- ......
If say , We go through onWheel、onKeyDown Other events , To monitor human scrolling , It must not be perfect . So let's think about it a little bit differently , Can you go to the right place “ Script scrolling ” Work hard ?
4.3 Script scrolling
How to define “ Script scrolling ”? We're going to scroll through the code , Defined as “ Script scrolling ”.
We need a way to describe “ Script scrolling ”, Come and join us “ Manual scrolling ” Make a distinction . Because they are either or , So actually we just need to onScroll In this case , Through one flag Just distinguish .
The flow chart is as follows :
And the only thing that needs attention is , How do I need to know , The script scrolling is over ?
scrollTo And so on , Obviously we are not provided with a callback method , To tell us when the scroll ends . So we still need to rely on onScroll To monitor the current scrolling position , To know when the scroll reaches the target position .
So the above process needs another step :
Next, let's see how the code is organized .
4.4 Code implementation
First, let's take a look at what we want to achieve demo:
Next, we will implement the basic page structure .
1、 Define a long list , And pass useRef Record :
- Rolling container's
ref - Judgment variables for script scrolling
isScriptScroll - Current scroll position
scrollTop
2、 next , Bind a... To the scrolling container onScroll Method , Write the logic of manual scrolling and script scrolling respectively , And use throttling to avoid frequent triggering .
In the logic of manual scrolling and script scrolling , We passed the update wording This state , To distinguish between manual scrolling and script scrolling .
3、 Use one button To trigger script scrolling , call listScroll Method , Incoming container ref, Want to scroll to scrollTop And after scrolling callback Method .
as follows :
import throttle from "lodash.throttle";
import React, { useRef, useState } from "react";
import { listScroll } from "./utils";
import "./styles.css";
const scrollItems = new Array(1000).fill(0).map((item, index) => {
return index + 1;
});
export default function App() {
const [wording, setWording] = useState(" Waiting for the ");
const cacheRef = useRef({
isScriptScroll: false,
cnt: null,
scrollTop: 0
});
const onScroll = throttle(() => {
if (cacheRef.current.isScriptScroll) {
setWording(" Script scrolling ");
} else {
cacheRef.current.scrollTop = cacheRef.current.cnt.scrollTop;
setWording(" Manual scrolling ");
}
}, 200);
const scriptScroll = () => {
cacheRef.current.scrollTop += 600;
cacheRef.current.isScriptScroll = true;
listScroll(cacheRef.current.cnt, cacheRef.current.scrollTop, () => {
setWording(" Script scrolling ends ");
cacheRef.current.isScriptScroll = false;
});
};
return (
<div className="App">
<button
className="btn"
onClick={() => {
scriptScroll();
}}
>
Trigger a script scroll
</button>
<p className="wording"> current state :{wording}</p>
<ul
className="list-ctn"
onScroll={onScroll}
ref={(ref) => (cacheRef.current.cnt = ref)}
>
{scrollItems.map((item) => {
return (
<li className="list-item" key={item}>
{item}
</li>
);
})}
</ul>
</div>
);
} The next point is listScroll How to achieve . We need to bind another one scroll event , Keep listening to the container scrollTop Whether the target value has been reached , So it can be organized like this :
import debounce from "lodash.debounce";
/** Within the error range */
export const withErrorRange = (
val: number,
target: number,
errorRange: number
) => {
return val <= target + errorRange && val >= target - errorRange;
};
/** List scrolling encapsulates */
export const listScroll = (
element: HTMLElement,
targetPos: number,
callback?: () => void
) => {
// Have you successfully uninstalled
let unMountFlag = false;
const { scrollHeight: listHeight } = element;
// Avoid some boundary conditions
if (targetPos < 0 || targetPos > listHeight) {
return callback?.();
}
// Call the scroll method
element.scrollTo({
top: targetPos,
left: 0,
behavior: "smooth"
});
// Return directly without callback
if (!callback) return;
// If you have reached the target position , You can go back first
if (withErrorRange(targetPos, element.scrollTop, 10)) return callback();
// Anti shake processing
const cb = debounce(() => {
// We have reached the target position , Can return
if (withErrorRange(targetPos, element.scrollTop, 10)) {
element.removeEventListener("scroll", cb);
unMountFlag = true;
return callback();
}
}, 200);
element.addEventListener("scroll", cb, false);
// The bottom line : Unload scroll callback , Avoid affecting subsequent operations
setTimeout(() => {
if (!unMountFlag) {
element.removeEventListener("scroll", cb);
callback();
}
}, 1000);
};If you write according to the strict process , We need to rely on scroll Events to constantly judge scrollTop, Until they are equal within the error range .
But actually scrolling is a very fast process , Follow our timer logic , That is, the front and back feet , Is it possible to keep only the logic behind it ?
and , Considering the anomalies :
- An exception occurred in script scrolling
- Script scrolling is interrupted by human scrolling
We all have to make sure that a callback is executed , Ensure that the external state is released , The logic of the next scroll is normal .
So in a less strict scenario , The above code can be discarded eventListener Part of , Keep only the logic behind it , Further simplify :
/** List scrolling encapsulates */
export const listScroll = (
element: HTMLElement,
targetPos: number,
callback?: () => void
) => {
const { scrollHeight: listHeight } = element;
// Avoid some boundary conditions
if (targetPos < 0 || targetPos > listHeight) {
return callback?.();
}
// Call the scroll method
element.scrollTo({
top: targetPos,
left: 0,
behavior: "smooth"
});
// Return directly without callback
if (!callback) return;
// If you have reached the target position , You can go back first
if (withErrorRange(targetPos, element.scrollTop, 10)) return callback();
// The bottom line : Unload scroll callback , Avoid affecting subsequent operations
setTimeout(() => {
callback();
}, 1000);
};Of course , This implementation is just a reference , I believe you also have other better ideas .
5、 Summary
Review the whole article , A brief introduction to scroll Some of api Use , Native scrollIntoView And the implementation reference of distinguishing between manual scrolling and script scrolling .
rolling , This seems like a tiny interaction point , In fact, there may be a lot of workload hidden , In future evaluation or practice , Need to pay more attention and think , The complex logic hidden under the interactive experience .
Keep up with the technological frontier , Dig deep into professional fields
Scan the code and pay attention to us !
边栏推荐
- MCS: discrete random variable Poisson distribution
- Chapter IX app project test (4) test tools
- Informatics Olympiad all in one 2061: trapezoidal area
- 极化SAR地表分类
- 服务器的数据库连不上了【服务已起、防火墙已关、端口已开、netlent 端口不通】
- LeetCode笔记:Biweekly Contest 81
- Lumiprobe 脱氧核糖核酸丨磷酸盐 CPG 1000 固体载体
- Leetcode notes: biweekly contest 81
- 获取Text组件内容的宽度
- js获取上个月第一天以及本月的最后一天
猜你喜欢

数字图像处理复习

微信公众号—菜单

What is the relationship between synchronized and multithreading

我 35 岁,可以转行当程序员吗?

Solution to the problem that the assembly drawing cannot be recognized after the storage position of SolidWorks part drawing is changed
![中序和后序遍历构建二叉树[递归划分区间与回溯拼接子树+中后序和中前序的相似与不同]](/img/c6/a2780620fef8bc5f7b564ef173f589.png)
中序和后序遍历构建二叉树[递归划分区间与回溯拼接子树+中后序和中前序的相似与不同]

真正的软件测试人员 =“半个产品+半个开发”?

揭秘百度智能测试在测试自动执行领域实践

Lumiprobe reactive dye miscellaneous dye: BDP FL ceramide

Northwestern Polytechnic University attacked by overseas e-mail
随机推荐
LeetCode-1188. Designing finite blocking queues
Lumiprobe 脱氧核糖核酸丨磷酸盐 CPG 1000 固体载体
Is it safe to open a stock account at present? Can I open an account online directly
复数卷积神经网络:CV-CNN
Basic use of text preprocessing library Spacy (quick start)
材质 动态自发光
Konva series Tutorial 4: drawing attributes
BFD原理与配置
MySQL的json 数组操作 json_array_append、json_array_insert
使用自定义注解实现Redis分布式锁
Unity C basic review 29 - Generic delegation (p451)
Biovendor free light chain( κ and λ) Chemical properties of ELISA Kit
.NET程序配置文件操作(ini,cfg,config)
EMC surge protection and decoupling design
阿里云体验有奖:使用PolarDB-X与Flink搭建实时数据大屏
MCS: multivariate random variable polynomial distribution
Unity C# 基础复习27——委托示例(P448)
MCS:离散随机变量——Poisson分布
PyTorch 二维多通道卷积运算方式
BioVendor遊離輕鏈(κ和λ)Elisa 試劑盒的化學性質