You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 06:36:15