【问题标题】:Emit from an observable only if another observable hasn't emitted in the last two seconds仅当在最后两秒内没有发出另一个 observable 时才从 observable 发出
【发布时间】:2020-03-24 09:56:40
【问题描述】:

注意:类似的问题是 here,但似乎没有以我可以申请的明显方式回答。

问题:我有一个“转到顶部”按钮,其功能是将用户滚动到页面顶部。当用户实际位于顶部时,它不应该是可见的。

它的可见性目前是通过动画不透明度属性处理的,但由于 display: none 未被应用,它可以与之交互。所以我必须找到一种方法来显示:不透明度为0时不适用。

第一个实现,在 Angular 中,是这样的:

    @HostListener('window:scroll', ['$event'])
    scrolled(_event: Event) {
        if (window.pageYOffset === 0) {
            setTimeout(() => (this.noClick = true), 1000);
        } else {
            this.noClick = false;
        }

        this.show = window.pageYOffset > 0;
    }

其中noClick 和show 是布尔值,noClick 应用于设置 display:none 的类,show 应用于动画不透明度。

但是有一个竞争条件,用户可以在滚动到 0 后向下滚动,延迟仍然可以将 noClick 设置为 true。

我想考虑一个 Observable 来解决这个问题。

如果我有两个科目:

displayNone = new Subject<boolean>();   // only contains true values
displaySome = new Subject<boolean>();   // only contains false values

还有一些事件代码:

    @HostListener('window:scroll', ['$event'])
    scrolled(_event: Event) {
        if (window.pageYOffset === 0) {
            this.displayNone.next(true);
        } else {
            this.displaySome.next(false);
        }

        this.show = window.pageYOffset > 0;
    }

我需要为 displayNone 功能构造一个 observable,如果 displayNone 延迟 1000 毫秒(动画时间)并且在那段时间 displaySome 发出,它必须忽略 displayNone。

我目前能来的最接近的是这样的:

race(this.displaySome, this.displayNone.pipe(delay(1000)))

但我的理解是,它只需要评估一次并决定通过哪一个。我需要不断对其进行评估。

【问题讨论】:

  • 1000ms 延迟有什么作用?
  • 另外,你能创建一个简单的堆栈闪电战来演示这个问题吗
  • 我很不清楚为什么你有两个主题。看起来您可能只有一个发射 true/false 取决于您是否要显示按钮。或者也许有什么我没看到...
  • @KurtHamilton 1000 毫秒的延迟是 opacity 属性上动画的持续时间,因为 display: none 不会动画。
  • @martin 这两个主题是因为其中一个必须延迟才能让动画有时间工作,而另一个应该覆盖它。

标签: javascript angular animation rxjs


【解决方案1】:

您不需要两个 Observable 来显示/隐藏带有动画的按钮。您的问题的最佳解决方案与您的问题的答案不同。我正在尝试回答这两个问题。

仅在最后 X 毫秒内没有发出另一个 Observable 时才发出

const X = 1000;
const obs$ = source$.pipe(
  // emit values on a new inner Observable when the otherObservable$ emits
  window(otherObservable$),
  // skip all values on this inner Observable until a given time has passed
  // (but not for the first inner Observable created before otherObservable$ emits)
  mergeMap((w, i) => i == 0 ? w : w.pipe(skipUntil(timer(X))))
);

根据滚动事件显示/隐藏按钮

  1. 从滚动事件创建一个 Observable 并映射到一个布尔值,指示是显示还是隐藏按钮。
hasScrolledDown$: Observable<boolean>;

ngOnInit() {
  this.hasScrolledDown$ = fromEvent(window, 'scroll').pipe(
    throttleTime(20),
    map(() => window.pageYOffset > 50)
  )
}
  1. 使用 ngIf 根据 Observable 中的值显示/隐藏按钮。
<button *ngIf="hasScrolledDown$ | async">
  1. (可选)为enter 和leave 状态变化添加动画。
<button *ngIf="hasScrolledDown$ | async" [@fadeAnimation]>
export const fadeOutAnimation = animation([
    style({ opacity: 1 }),
    animate('200ms', style({ opacity: 0 }))
])

export const fadeInAnimation = animation([
    style({ opacity: 0 }),
    animate('200ms', style({ opacity: 1 }))
])

@Component({
  selector: 'button-overview-example',
  templateUrl: 'button-overview-example.html',
  styleUrls: ['button-overview-example.css'],
  animations: [
    trigger('fadeAnimation', [
      transition(':enter', [useAnimation(fadeInAnimation)]),
      transition(':leave', [useAnimation(fadeOutAnimation)])
    ])
  ],
})

Demo

【讨论】:

  • 此解决方案完美运行,感谢您回答真正的问题。尽管我仍然对 observables 问题感到好奇。
  • @JeffreyDrake 我从你的标题中添加了对问题的答案。
猜你喜欢
  • 1970-01-01
  • 2017-12-17
  • 1970-01-01
  • 1970-01-01
  • 2018-01-18
  • 2023-02-25
  • 1970-01-01
  • 1970-01-01
  • 2020-09-27
相关资源
最近更新 更多