You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
options.disableHysteresis (Boolean [optional]): Defaults to false. Disable the hysteresis. Ignore the scroll direction when determining the trigger value.
Hysteresis characterizes a system whose behavior (output) does not only depend on its input at time t, but also on its past behavior, on the path it has followed.
给了一个很简单的需求, 某个页面需要做一个
BackTop
组件, 在页面滑动向下滑动到一定程度的时候(比如直接滑到底部)显示这个组件, 点击可以直接回到顶部, 该组件同时也消失基础实现例子
本来想手写的, 但是公司用的 material-ui 这个组件库里直接搜到了例子, 这里简化一下代码:
思路
梳理一下思路:
useScrollTrigger()
的hook
返回一个trigger
状态, 这个状态用来判断是否显示BackTop
组件, 如果自己写的话可以用这个状态来控制 css 里的display: none
属性, 当然这里直接用了 material ui 里内置的<Zoom>
组件了handleClick()
, 具体细节为:anchor
)用于定位滚动到的地方, 例子里为<div id="back-to-top-anchor" />
scrollIntoView()
原生 api 滚动到锚点的位置children
, 可以自定义, 用于显示BackTop
组件的 UI, 这里简单使用<KeyboardArrowUpIcon />
思路可以说非常清晰了
useScrollTrigger()
这个
hooks
令我有些困惑, 他提供的参数options
有三个属性:disableHysteresis
,target
,threshold
, 主要聊一聊前两个参数:disableHysteresis
hysteresis
这个词我根本不认识...于是直接谷歌, 发现了这么一个问题: What does hysteresis mean and how does it apply to computer science or programming?该问题里的第一个回答重点如下:
也就是说他的值, 可能是根据上一次值来决定的, 这里联系一下源码来看
先提一点, 其中使用
useRef()
做数据存储, 存储当前(之前)所滑动的长度. 为啥不用useState
是因为useState
在你state
发生改变的时候会重新渲染组件, 而ref
被重新设置是不会触发重新渲染的. 他就是一个存取器用来存一些值, 组件重新渲染啥的和他无关所以如果涉及到 UI 的需要依据某些变化的值来重新渲染的, 考虑用
useState
, 否则用useRef
回到源码, 关于 disableHysteresis 的逻辑就在于那段
if
:解读一下:
false
(说明 Hysteresis 是被允许的) 并且,previous
(之前的滑动长度也是有的)false
也就是说, 只要在某个阶段, 滚动条向上移动, 那么当前的滑动条长度(
pageYOffset
或者是scrollTop
) 一定是小于之前的, 同时disableHysteresis
为false
, 那么trigger
直接就是false
了, 根本不会再走store.current > threshold
这个逻辑来判断...这就造成了两种 UI 效果
disableHysteresis
为false
的时候, 即使BackTop
组件在最底部, 用户只要往上滚动一点点,BackTop
组件立马消失, 因为他根本不会再比较你当前的滑动条长度和给定的threshold
了(即使你是超过的...)disableHysteresis
为true
的时候, 老老实实比较当前滑动条长度和指定的threshold
. 所以往上滑的时候, 没超过threshold
是不会显示BackTop
组件的最后来看下效果:
首先是
disableHysteresis = true
, 也就是不存在Hysteresis
现象, 可以看到自始至终只有一个Hysteresis not triggered
这条 log:接着来看
disableHysteresis = false
, 也就是存在Hysteresis
现象, 可以发现会有两条 log, 同时只要往上滑, 一定会是出现Hysteresis not triggered
这条 log:另: 我翻了一下 Ant Design 的
BackTop
组件, 貌似是没有提供这个功能, 当然用起来也是更加简单, 有兴趣可以去看看源码, 这里不做深究target
如果
target
是 window, 那没啥好纠结的, 但情况往往需要传入的是一个特定的元素, 比如可能是左侧侧边栏回顾一下之前的源码:
对于给定的
target
做事件监听, 也就是说, 你必须非常精准的传入触发scroll
事件的元素...我为什么说是精准, 因为在当时项目里我为了传这个 dom 节点花了很大的力气, 如果没有正确传入这个
eventHandler
是根本不会触发的...所以我当时想, 能不能传入一个不必太精准的 dom 节点, 也就是说做一下事件委托? 于是我尝试改了一下源码:
然后尝试传入一个略微父级的元素, 发现根本不触发
handleScroll
...遇到了一个之前一直很忽略的知识点:
scroll
事件的目标元素是一个元素的话, 比如说是一个div
, 那么此时事件只有从document
到div
的捕获阶段以及div
的冒泡阶段.也就是说, 尝试在父级监视
scroll
的冒泡阶段监视这一事件是无效的..., 而addEventListener
里第三个参数默认是false
指定在了冒泡阶段. 在这里做事件委托是失败的.给一张
scroll
事件的事件流吧(红色以上不执行...):当时因为这个问题卡了很久, 直到搜到了这个相关问题才得以解决: Listening to all scroll events on a page
就像回答里说的: 如果要做事件委托, 请将
addEventListener
的第三个参数改为true
, 以方便在捕获阶段捕获事件关于事件流, 事件委托和 scroll 事件的, 我推荐看这一篇文章: 你所不知道的scroll事件:为什么scroll事件会失效?
最后的最后, 我还是没改源码, 老老实实找到了对应的 dom 节点传过去, 最后项目里的代码大概可能长这样:
参考
The text was updated successfully, but these errors were encountered: