Agular 中的 RxJS 之 元素拖拽

863 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

前言

上一章中,我们补充了 rxjs 的节流防抖方法,这两个方法虽然实现的方式不少,但是本质上的减少一些高频数据流的数据流出。

这次我们就来总结一下之前的所有操作符和辅助方法,来实现一个 元素拖动 的简单案例。

js 版本

首先我们先用熟悉的 js 来实现一个简单的版本,其中就需要用到三个时间,分别是 onmousedown,onmousemove,onmouseup,他们分别代表着鼠标按下,移动和抬起,我们要在 鼠标按下 的时候去获取到当前盒子在屏幕中的位置,这个可以通过 event 里面的属性获取,然后在 鼠标移动 的时候,需要去实时改变盒子当前的定位,通过实时的一个计算以及修改盒子的定位就可以实现,最后在 鼠标抬起 的时候,我们需要去释放 onmousemove 事件。

<body>
<style>
  div {
    background-color: black;
    width: 100px;
    height: 100px;
    position: absolute;
    top: 0px;
    left: 0px;
  }
</style>
<button id="button">我是按钮</button>
<div id="drag"></div>
</body>
const drag = document.getElementById("drag");

drag.onmousedown = function (event) {
  let distanceX = event.clientX - event.target.offsetLeft;
  let distanceY = event.clientY - event.target.offsetTop;
  document.onmousemove = function (event) {
    let positionX = event.clientX - distanceX;
    let positionY = event.clientY - distanceY;
    console.log(positionX, positionY);
    drag.style.left = positionX + "px";
    drag.style.top = positionY + "px";
  };
  drag.onmouseup = function () {
    document.onmousemove = null;
  };
};

image.png

这就是最简单的一个元素拖拽的 js 版本的示例,那么接下来,我们要想如何用已经学过的 rxjs 操作符或者辅助方法配合来实现这个效果。

rxjs 版本

  1. 在 rxjs 中,我们可以通过 fromEvent 辅助方法来获取事件转为可观察对象,比如最开始的鼠标点击事件,我们可以将它转为可观察对象。

  2. 在点击事件中,我们需要获取到元素当前的一个坐标位置,并且将坐标位置提供给 鼠标移动方法使用,那么我们首先可以用 map 方法来获取到我们想要的数据,然后在通过 switchMap 操作符就可以切换可观察对象,我们是不是就可以从点击事件上拿到当前元素的位置,然后传递给 switchMap 作为参数提供给移动事件。

  3. 那么在 js 中,移动事件里面我们要去做什么呢,就是计算获取元素当前的位置并且动态的修改掉元素上的定位值,那么在 rxjs 当中,我们需要在操作符中通过计算获取到移动过后的位置,然后作为数据流将数据流出,那么在获得数据之后就可以进行对元素定位的修改。

  4. 最后就是要在鼠标抬起的时候,需要取消掉之前移动事件,在 rxjs 中就是停止数据的流出,那么就是需要用到 takeUntil 操作符。接收一个 可观察对象,就是鼠标抬起事件转为的可观察对象,然后就会停止上一个事件的数据流出。

那么综合上面所有的关键步骤,我们就可以用 rxjs 来实现一个元素拖拽的案例了

import { fromEvent } from "rxjs";
import { map, switchMap, takeUntil } from "rxjs/operators";

const drag = document.getElementById("drag");

fromEvent(drag, "mousedown")
  .pipe(
    map((event) => ({
      distanceX: event.clientX - event.target.offsetLeft,
      distanceY: event.clientY - event.target.offsetTop,
    })),
    switchMap(({ distanceX, distanceY }) =>
      fromEvent(document, "mousemove").pipe(
        map((event) => ({
          positionX: event.clientX - distanceX,
          positionY: event.clientY - distanceY,
        })),
        takeUntil(fromEvent(document, "mouseup"))
      )
    )
  )
  .subscribe(({ positionX, positionY }) => {
    console.log(positionX, positionY);
    drag.style.left = positionX + "px";
    drag.style.top = positionY + "px";
  });

image.png

节流

放到最后,从上面的控制台我们可以发现,在拖拽的过程当中,可观察对象会非常频繁的对外发出数据,并且在发出数据之后,是会动态去修改元素的定位的,这是属于在dom操作的范畴,那么我们知道,dom 操作是十分消耗浏览器性能的一个操作,是不能够频繁的去进行的,不然就会很容易造成一个浏览器的卡顿。

那么我们是不是就可以使用节流来对其进行一个限制,在流出数据的时候加上一个很短的时间,这样就可以减少浏览器的消耗,并且由于间隔的时间比较短,所以用户是感受不到添加了节流的,这就是对这个案例的一个性能上的优化。

image.png

总结

本文我们结合了多个操作符和辅助方法来实现了一个拖拽效果,在这之后,我们也会更多的去使用 rxjs 来实现一个日常开发中的事件,并且一起来体会 使用了 rxjs 和我们原生的操作,都会有着什么不同。