如何用RxJS实现仅触发更大滚动值的无状态滚动监听器?
解决RxJS滚动监听器仅发射增大的scrollY值问题
没问题!你完全可以不用维护外部的状态变量,只用RxJS的操作符就能实现这个需求。咱们来一步步改造你的代码,同时保证逻辑和你想要的场景完全匹配。
最终实现代码
首先是修改后的滚动监听方法和订阅逻辑,已经去掉了lastScrolledHeight这个状态属性:
public ngOnInit() { this.listenForScroll() .subscribe(scrollHeight => { // 这里只会收到比前一次滚动位置更大的值 this.showAddButton = false; // 根据你的原有逻辑,向下滚动时隐藏按钮,可按需调整 // 再也不用手动维护lastScrolledHeight啦! }); } private listenForScroll(): Observable<number> { return fromEvent(window, 'scroll').pipe( debounceTime(25), map(() => window.scrollY), // 直接用window.scrollY比e.path更可靠,兼容性更好 startWith(0), // 初始化一个初始滚动位置0,确保第一次滚动有对比基准 pairwise(), // 将当前值和前一次值组合成数组 [prevScrollY, currScrollY] filter(([prev, curr]) => curr > prev), // 只保留当前滚动位置大于前一次的情况(向下滚动) map(([prev, curr]) => curr) // 提取我们需要的当前滚动位置数值 ); }
操作符作用详解
咱们来拆解每个操作符的作用,明白为什么这么写:
startWith(0):给Observable添加一个初始值0,这样第一次滚动时,pairwise能拿到第一个对比的基准值,不会因为缺少初始值而不发射数据。pairwise():把连续的两次发射值打包成数组,比如第一次滚动到300时,数组是[0, 300];向上滚动到50时,数组是[300, 50];再向下到150时,数组是[50, 150]。filter(([prev, curr]) => curr > prev):只保留当前滚动位置大于前一次的情况,也就是过滤掉向上滚动的动作,只留下向下滚动的有效数据。map(...):把过滤后的数组转换成单独的滚动位置数值,方便订阅时直接使用。
场景验证
按照你的示例场景测试:
- 向下滚动到Y=300:
pairwise生成[0,300],满足curr>prev,发射300。 - 向上滚动到Y=50:
pairwise生成[300,50],不满足过滤条件,不发射任何值。 - 再次向下滚动到150:
pairwise生成[50,150],满足条件,发射150。
完全符合你的预期!
变体需求:仅发射滚动位置的新最大值
如果你之后有需求:只发射比之前所有滚动位置都大的新最大值(比如滚动到300后,再到150不发射,只有到301才发射),可以用scan操作符实现:
private listenForScroll(): Observable<number> { return fromEvent(window, 'scroll').pipe( debounceTime(25), map(() => window.scrollY), // 跟踪当前的最大滚动位置,只有当当前值大于最大值时才更新 scan((maxSoFar, current) => current > maxSoFar ? current : maxSoFar, 0), distinctUntilChanged() // 只有当最大值变化时才发射数据 ); }
这个版本会忽略中间的小幅度滚动,只发射滚动位置的峰值,供你参考。
内容的提问来源于stack exchange,提问作者MarcoLe
相关产品推荐
相关产品推荐

