最近看了一点rxjs的东西。现学现玩一下…..就来尝试下元素拖拽吧
如果使用非rxjs而是普通的js实现思路也不难。
一般实现拖拽的思路是:
1、监听 drag 元素 的 mousedown,回调中设置标识开始拖动,计算出初始点击到元素左上角距离
2、监听 document 的 mousemove,判断 1 中标识处于拖动,通过计算当前位置设置元素的样式
3、监听 document 的 mouseup,设置标识停止拖动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const drag = document.getElementById("drag");
let isDrag = false; let initialX = null, initialY = null; drag.addEventListener("mousedown", function(e) { isDrag = true; const { left, top } = drag.getBoundingClientRect(); initialX = e.clientX - left; initialY = e.clientY - top; });
document.addEventListener("mouseup", function(e) { isDrag = false; });
document.addEventListener("mousemove", function(e) { if (isDrag) { drag.style.left = `${e.clientX - initialX}px`; drag.style.top = `${e.clientY - initialY}px`; } });
|
接下来就是使用 rxjs 来实现了
rxjs 中一切皆为流,那么肯定有一个 Observable 源。在拖拽操作中,我们的源肯定就是鼠标的事件了,所以我们这边建立 3 个源,分别是鼠标移动、鼠标点击、鼠标 mouseup
1 2 3 4 5
| const target = document.getElementById("drag");
const mouseDown = fromEvent(target, "mousedown"); const mouseMove = fromEvent(document, "mousemove"); const mouseUp = fromEvent(document, "mouseup");
|
接下来,一次拖拽操作的开始肯定是鼠标点击元素准备拖拽了。所以,入口Observable
就是 mouseDown 这个 Observable 了。
通过普通的 js 写拖拽我们知道我们开始肯定是需要获取鼠标点击区域到元素左上角的偏移距离,用于后面拖拽后设置元素的正确位置。这里用到了map
操作符。和 js 的数组 map 有点像,输入一系列的值然后处理返回一系列新的值,这个过程都是 immutable 的哦。接下来我们就去计算这个偏移值
1 2 3 4 5 6 7 8 9 10 11 12
| mouseDown .pipe( map(e => { const { left, top } = e.target.getBoundingClientRect(); const clickOffsetX = e.clientX - left; const clickOffsetY = e.clientY - top; return { clickOffsetX, clickOffsetY }; })) ......
|
接下来,就是在mousemove
事件中去计算元素的位置并设置样式改变元素位置了。上面的pipe
运算符就是将前一个操作符的输出作为下一个操作符的输入。
我们知道拖拽的结束就是mouseup
触发的时候,这时候需要takeUntil
这个操作符。它的含义就是:解释源 Observable 会不停发射数据直到目标 Observable 发射数据。然后又是使用map
去根据原来计算出来的偏移值和当前鼠标移动的值去计算元素的位置了
1 2 3 4 5 6 7 8 9 10 11
| ... map(({ clickOffsetX, clickOffsetY }) => { return mouseMove.pipe( takeUntil(mouseUp), map(moveEvent => ({ x: moveEvent.clientX - clickOffsetX, y: moveEvent.clientY - clickOffsetY })) ); }) ...
|
上面我们map
又接了一个map
,类似于一个二维的Observable,如[[Observable]]。我们再借助concatAll
打平成一维即可。整个Observable的处理过程就完成了,最后订阅Observable
再设置元素的位置即可。完整代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| const { fromEvent, operators: { map, takeUntil, concatAll, withLatestFrom } } = rxjs;
const target = document.getElementById("drag");
const mouseDown = fromEvent(target, "mousedown"); const mouseMove = fromEvent(document, "mousemove"); const mouseUp = fromEvent(document, "mouseup");
mouseDown .pipe( map(e => { const { left, top } = e.target.getBoundingClientRect(); const clickOffsetX = e.clientX - left; const clickOffsetY = e.clientY - top; return { clickOffsetX, clickOffsetY }; }), map(({ clickOffsetX, clickOffsetY }) => { return mouseMove.pipe( takeUntil(mouseUp), map(moveEvent => ({ x: moveEvent.clientX - clickOffsetX, y: moveEvent.clientY - clickOffsetY })) ); }), concatAll() ) .subscribe(({ x, y }) => { target.style.left = `${x}px`; target.style.top = `${y}px`; });
|
demo
后话:使用rxjs对于处理复杂的异步操作还是很好的,也包含了很多函数式编程的思想。不过要学习rxjs那种思想感觉还是要有一定的适应过程….慢慢来吧