RxJS:高级拖放 - 防止操作员订阅
RxJS: Advanced Drag and Drop - Prevent subscription in operator
我尝试在 RxJS 中实现拖放。我有一个 ID 为 draggable
的 DOM 节点,可以四处拖动。通过使用标准程序,拖放可以按预期工作。
但我试图增强拖放功能,这就是事情变得复杂的地方。我尝试在拖动开始后更改元素的背景颜色,并在它被放下后将其更改回来。
在我的方法中,我使用 switchMap
将鼠标移动事件的结果映射到由鼠标按下事件触发的可观察对象中。但是由于我使用鼠标弹起事件来完成 switchMap
ed 可观察对象(在下面的示例中为 mm$
),所以我没有机会收到有关内部可观察对象的完成事件的通知,除非我正在订阅在 switchMap
运算符中添加它。
我知道在运算符内订阅远非好的做法,可能会导致内存泄漏。但是我还能做什么呢?如何才能做得更好?
Fiddle: https://jsfiddle.net/djwfyxs5/
const target = document.getElementById('draggable');
const mouseup$ = Observable.fromEvent(document, 'mouseup');
const mousedown$ = Observable.fromEvent(target, 'mousedown');
const mousemove$ = Observable.fromEvent(document, 'mousemove');
const move$ = mousedown$
.switchMap(md => {
md.target.style.backgroundColor = 'yellow';
const {offsetX: startX, offsetY: startY} = md;
const mm$ = mousemove$
.map(mm => {
mm.preventDefault();
return {
left: mm.clientX - startX,
top: mm.clientY - startY
};
})
.takeUntil(mouseup$);
// Can the next line be avoided?
mm$.subscribe(null, null, () => {
md.target.style.backgroundColor = 'purple';
});
return mm$;
});
move$.subscribe((pos) => {
target.style.top = pos.top + 'px';
target.style.left = pos.left + 'px';
});
我在这里回答了一个类似的问题:
根据您的目的调整答案应该相当简单,因为流仍然包含公开事件的事件。
为了找到解决方案,我一直在努力解决这个问题。在我的尝试中,我使用了一个结合了 mousedown
和 mouseup
事件的 helper observable。通过将它们与 combineLatest
运算符组合,可以访问 mousedown 事件的最新值,其中包含已单击的项目(目标)。
这让我可以在没有订阅的情况下正确设置颜色,如问题中所示。我的解决方案可以访问in this fiddle.
我不确定这是否可以完成 better/with 使用相同的想法减少代码。如果可能的话,我很乐意看到改进的实现。
完整代码:
const targets = document.getElementsByClassName('draggable');
const arrTargets = Array.prototype.slice.call(targets);
const mouseup$ = Rx.Observable.fromEvent(document, 'mouseup');
const mousedown$ = Rx.Observable.fromEvent(targets, 'mousedown');
const mousemove$ = Rx.Observable.fromEvent(document, 'mousemove');
// md -------x------------------------------------------
// mm xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// mu -----------------------------------------x--------
// move$ -------xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx---------
// s$ -------x---------------------------------x--------
const s$ = Rx.Observable.combineLatest(
mousedown$,
mouseup$.startWith(null), // start immediately
(md, mu) => {
const { target } = md; // Target is always the one of mousedown event.
let type = md.type;
// Set type to event type of the newer event.
if (mu && (mu.timeStamp > md.timeStamp)) {
type = mu.type;
}
return { target, type };
}
);
const move$ = mousedown$
.switchMap(md => {
const { offsetX: startX, offsetY: startY } = md;
const mm$ = mousemove$
.map(mm => {
mm.preventDefault();
return {
left: mm.clientX - startX,
top: mm.clientY - startY,
event: mm
};
})
.takeUntil(mouseup$);
return mm$;
});
Rx.Observable.combineLatest(
s$, move$.startWith(null),
(event, move) => {
let newMove = move || {};
// In case we have different targets for the `event` and
// the `move.event` variables, the user switched the
// items OR the user moved the mouse too fast so that the
// event target is the document.
// In case the user switched to another element we want
// to ensure, that the initial position of the currently
// selected element is used.
if (move && move.event.target !== event.target && arrTargets.indexOf(move.event.target) > -1) {
const rect = event.target.getBoundingClientRect();
newMove = {
top: rect.top, // + document.body.scrollTop,
left: rect.left // + document.body.scrollLeft
};
}
return { event, move: newMove }
}
)
.subscribe(action => {
const { event, move } = action;
if (event.type === 'mouseup') {
event.target.style.backgroundColor = 'purple';
event.target.classList.remove('drag');
} else {
event.target.style.backgroundColor = 'red';
event.target.classList.add('drag');
}
event.target.style.top = move.top + 'px';
event.target.style.left = move.left + 'px';
});
我尝试在 RxJS 中实现拖放。我有一个 ID 为 draggable
的 DOM 节点,可以四处拖动。通过使用标准程序,拖放可以按预期工作。
但我试图增强拖放功能,这就是事情变得复杂的地方。我尝试在拖动开始后更改元素的背景颜色,并在它被放下后将其更改回来。
在我的方法中,我使用 switchMap
将鼠标移动事件的结果映射到由鼠标按下事件触发的可观察对象中。但是由于我使用鼠标弹起事件来完成 switchMap
ed 可观察对象(在下面的示例中为 mm$
),所以我没有机会收到有关内部可观察对象的完成事件的通知,除非我正在订阅在 switchMap
运算符中添加它。
我知道在运算符内订阅远非好的做法,可能会导致内存泄漏。但是我还能做什么呢?如何才能做得更好?
Fiddle: https://jsfiddle.net/djwfyxs5/
const target = document.getElementById('draggable');
const mouseup$ = Observable.fromEvent(document, 'mouseup');
const mousedown$ = Observable.fromEvent(target, 'mousedown');
const mousemove$ = Observable.fromEvent(document, 'mousemove');
const move$ = mousedown$
.switchMap(md => {
md.target.style.backgroundColor = 'yellow';
const {offsetX: startX, offsetY: startY} = md;
const mm$ = mousemove$
.map(mm => {
mm.preventDefault();
return {
left: mm.clientX - startX,
top: mm.clientY - startY
};
})
.takeUntil(mouseup$);
// Can the next line be avoided?
mm$.subscribe(null, null, () => {
md.target.style.backgroundColor = 'purple';
});
return mm$;
});
move$.subscribe((pos) => {
target.style.top = pos.top + 'px';
target.style.left = pos.left + 'px';
});
我在这里回答了一个类似的问题:
根据您的目的调整答案应该相当简单,因为流仍然包含公开事件的事件。
为了找到解决方案,我一直在努力解决这个问题。在我的尝试中,我使用了一个结合了 mousedown
和 mouseup
事件的 helper observable。通过将它们与 combineLatest
运算符组合,可以访问 mousedown 事件的最新值,其中包含已单击的项目(目标)。
这让我可以在没有订阅的情况下正确设置颜色,如问题中所示。我的解决方案可以访问in this fiddle.
我不确定这是否可以完成 better/with 使用相同的想法减少代码。如果可能的话,我很乐意看到改进的实现。
完整代码:
const targets = document.getElementsByClassName('draggable');
const arrTargets = Array.prototype.slice.call(targets);
const mouseup$ = Rx.Observable.fromEvent(document, 'mouseup');
const mousedown$ = Rx.Observable.fromEvent(targets, 'mousedown');
const mousemove$ = Rx.Observable.fromEvent(document, 'mousemove');
// md -------x------------------------------------------
// mm xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// mu -----------------------------------------x--------
// move$ -------xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx---------
// s$ -------x---------------------------------x--------
const s$ = Rx.Observable.combineLatest(
mousedown$,
mouseup$.startWith(null), // start immediately
(md, mu) => {
const { target } = md; // Target is always the one of mousedown event.
let type = md.type;
// Set type to event type of the newer event.
if (mu && (mu.timeStamp > md.timeStamp)) {
type = mu.type;
}
return { target, type };
}
);
const move$ = mousedown$
.switchMap(md => {
const { offsetX: startX, offsetY: startY } = md;
const mm$ = mousemove$
.map(mm => {
mm.preventDefault();
return {
left: mm.clientX - startX,
top: mm.clientY - startY,
event: mm
};
})
.takeUntil(mouseup$);
return mm$;
});
Rx.Observable.combineLatest(
s$, move$.startWith(null),
(event, move) => {
let newMove = move || {};
// In case we have different targets for the `event` and
// the `move.event` variables, the user switched the
// items OR the user moved the mouse too fast so that the
// event target is the document.
// In case the user switched to another element we want
// to ensure, that the initial position of the currently
// selected element is used.
if (move && move.event.target !== event.target && arrTargets.indexOf(move.event.target) > -1) {
const rect = event.target.getBoundingClientRect();
newMove = {
top: rect.top, // + document.body.scrollTop,
left: rect.left // + document.body.scrollLeft
};
}
return { event, move: newMove }
}
)
.subscribe(action => {
const { event, move } = action;
if (event.type === 'mouseup') {
event.target.style.backgroundColor = 'purple';
event.target.classList.remove('drag');
} else {
event.target.style.backgroundColor = 'red';
event.target.classList.add('drag');
}
event.target.style.top = move.top + 'px';
event.target.style.left = move.left + 'px';
});