使用RxJS进行反应式编程 - 可以简化此滚动功能吗?

时间:2015-07-28 00:46:32

标签: javascript functional-programming reactive-programming rxjs

我对反应式编程(和RxJS)很陌生,所有这些运算符都很难理解。

无论如何,我已经成功编写了这个函数,可以在拖动文件的同时处理文档的滚动。我现在想知道这是否可以简化。

基本上,onMouseDown我需要每隔10ms检查一次鼠标的位置,当鼠标移动时我需要更新clientY,这就是为什么我设置了{mouse}观察者组合的Rx.Oberservable.interval(10)。这将滚动页面,无论您是否移动鼠标(按预期)。

以下是代码:

handleWindowScrollOnDrag() {
    var dragTarget = this.getDOMNode()
    var scrollTarget = document.body
    var wHeight = window.innerHeight
    var maxScroll = document.documentElement.scrollHeight - wHeight

    // Get the three major events
    var mouseup   = Rx.Observable.fromEvent(document, 'mouseup');
    var mousemove = Rx.Observable.fromEvent(document, 'mousemove');
    var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown');

    var mousedrag = mousedown.flatMap(function (md) {
        var y = scrollTarget.scrollTop
        var multiplier = 1

        // Scroll every 10ms until mouseup when we can
        var intervalSource = Rx.Observable.interval(10).takeUntil(mouseup);

        // Get actual clientY until mouseup
        var movement = mousemove.map(function (mm) {
            return {
                y: mm.clientY
            };
        }).takeUntil(mouseup);

        return Rx.Observable
            .combineLatest(movement, intervalSource, function (s1) {
                multiplier = 1

                if (s1.y < 100 && y >= 0) {
                    if (s1.y < 75) multiplier = 3;
                    if (s1.y < 50) multiplier = 5;
                    if (s1.y < 25) multiplier = 10;
                    if (s1.y < 15) multiplier = 20;
                    y -= (1 * multiplier)
                }

                if (s1.y > wHeight - 100 && y <= (maxScroll)) {
                    if (s1.y > wHeight - 75) multiplier = 3;
                    if (s1.y > wHeight - 50) multiplier = 5;
                    if (s1.y > wHeight - 25) multiplier = 10;
                    if (s1.y > wHeight - 15) multiplier = 20;
                    y += (1 * multiplier)
                }

                return {
                    y: y
                };
        });
    });

    // Update position
    this.subscription = mousedrag.subscribe(function (pos) {
        document.body.scrollTop = pos.y
    });

},

1 个答案:

答案 0 :(得分:3)

您可以删除takeUntil流上只需要一个movement的额外combineLatest,因为.scan运算符将在完成时清理并处理所有基础订阅。

接下来,您可以使用y来管理累积状态而不是闭包变量,从而删除一些额外状态。

您还可以通过简单地传递鼠标移动map值来简化事件正文,而不是在combineLatest.withLatestFrom中产生对象分配的开销运算符。

最后,我会更改为使用combineLatest而不是combineLatest。由于您基本上已经以100 fps进行轮询,间隔为jQuery.animate,如果鼠标同时移动,则会发出更快的速度。

作为旁注,虽然我对DOM滚动的工作方式并不十分熟悉(而且我实际上并不知道您的代码在野外的效果如何),但垃圾邮件的页面滚动值比页面的实际渲染率似乎有点过分。降低间隔率可能会更好,并使用类似handleWindowScrollOnDrag() { var dragTarget = this.getDOMNode() var scrollTarget = document.body; var wHeight = window.innerHeight; var maxScroll = document.documentElement.scrollHeight - wHeight; // Get the three major events var mouseup = Rx.Observable.fromEvent(document, 'mouseup'); var mousemove = Rx.Observable.fromEvent(document, 'mousemove'); var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown'); var mousedrag = mousedown.flatMap(function (md) { // Scroll every 10ms until mouseup when we can var intervalSource = Rx.Observable.interval(10, Rx.Scheduler.requestAnimationFrame); return intervalSource .takeUntil(mouseup) .withLatestFrom(mousemove, function (s1, s2) { return s2.clientY; }) .scan(scrollTarget.scrollTop, function(y, delta) { var multiplier = 1; if (delta < 100 && y >= 0) { if (delta < 75) multiplier = 3; if (delta < 50) multiplier = 5; if (delta < 25) multiplier = 10; if (delta < 15) multiplier = 20; y -= (1 * multiplier); } if (delta > wHeight - 100 && y <= (maxScroll)) { if (delta > wHeight - 75) multiplier = 3; if (delta > wHeight - 50) multiplier = 5; if (delta > wHeight - 25) multiplier = 10; if (delta > wHeight - 15) multiplier = 20; y += (1 * multiplier); } return y; }); }); // Update position this.subscription = mousedrag.subscribe(function (pos) { document.body.scrollTop = pos; }); }, 的内容来平滑其间的滚动。

; tldr

movement

修改

原版中的错误实际上允许人们进一步简化代码。您可以通过将map传递给mousemove并使用该结果选择器获取{{1},将所有withLatestFrom流一起移除(并通过额外调用clientY) }}

此外,如果您尝试将interval与页面的渲染循环同步,我会添加您可能想要添加调度程序的位置,但我仍然会说您可能希望使用15ms(60fps)而不是10ms,它再次归结为滚动渲染的完成方式。

如果设置滚动位置只会在每个渲染循环结束时更新,那么它将正常工作。但是,如果它在每set之后贪婪地重新计算,那么最好让像React这样的中间件首先写入Virtual DOM而不是直接将所有更改刷新到DOM。