rxjs实现页面拖拽

为了练习rxjs操作符,本节将实现一个基础的拖拽功能。

先来看具体效果:

在这里插入图片描述

实现原理:通过鼠标移动,计算鼠标在页面中的位置,从而改变DOM元素绝对定位的lefttop值。

实现思路:

  1. 首先css设置DOM元素为绝对定位;

  2. 捕获鼠标点击之后的鼠标移动事件,实时计算鼠标位置,直到mouseup;

  3. 边界判断并实时修改位置。

先来一个边界判断计算值的方法:

getValue(value, max, min): number {
  return Math.min(Math.max(value, min), max);
}

静态页面代码:

<p class="drag" #drag>drag me</p>

获取DOM元素:

@ViewChild('drag', {static: true}) dragEl: ElementRef;

绑定事件:

const mouseDown = fromEvent(this.dragEl.nativeElement, 'mousedown');
const mouseUp = fromEvent(document.body, 'mouseup');
const mouseMove = fromEvent(document.body, 'mousemove');

首先我们来看看 mouseDown所发射的流:

mouseDown.subscribe(res => console.log(res));

在这里插入图片描述

返回一个 MouseEvent对象,包含了我们想要的各种信息。

同理,mouseMove所发射的流也是 MouseEvent对象。

鼠标点击之后,我们需要的是 mouseMove发射的流:

mouseDown.pipe(
  map(() => mouseMove),
)

直到鼠标抬起停止发射:

mouseDown.pipe(
  map(() => mouseMove.pipe(takeUntil(mouseUp))),
)

不过此时,打印日志却是这样的:

在这里插入图片描述

所以,我们需要将这个Observable通过 concatAll展开:

mouseDown.pipe(
  map(() => mouseMove.pipe(takeUntil(mouseUp))),
  concatAll()
)

这样,我们就能获取到一个个的 MouseEvent对象,就能拿到我们想要的 clientXclientY

在这里插入图片描述

mouseDown.pipe(
  map(() => mouseMove.pipe(takeUntil(mouseUp))),
  concatAll(),
  map((e: MouseEvent) => {
    return {
      x: e.clientX,
      y: e.clientY
    };
  })
).subscribe(res => {
  this.dragEl.nativeElement.style.left = res.x  + 'px';
  this.dragEl.nativeElement.style.top = res.y + 'px';
});

但是会发现,点击之后,鼠标一直在元素的左上方:

在这里插入图片描述

这样是不合理的,位置应该减去点击时鼠标距元素左上角的距离。

要获取最初点击时鼠标位置,我们就应该拿到点击时的流:

使用 withLatestFrom就能拿到两个流:

mouseDown.pipe(
  map(() => mouseMove.pipe(takeUntil(mouseUp))),
  concatAll(),
  withLatestFrom(mouseDown, (move: MouseEvent, down: MouseEvent) => ({move, down}))
);

在这里插入图片描述

同时,处理一下边界问题:

    mouseDown.pipe(
      map(() => mouseMove.pipe(takeUntil(mouseUp))),
      concatAll(),
      withLatestFrom(mouseDown, (move: MouseEvent, down: MouseEvent) => {
        // 获取点击元素的宽高
        const {width, height} = (down.target as HTMLElement).getBoundingClientRect();
        return {
          x: this.getValue(move.clientX - down.offsetX, window.innerWidth - width, 0),
          y: this.getValue(move.clientY - down.offsetY, window.innerHeight - height, 0)
        };
      })
    ).subscribe(res => {
      this.dragEl.nativeElement.style.left = res.x  + 'px';
      this.dragEl.nativeElement.style.top = res.y + 'px';
    });

引用一张网图来说明 clientXoffsetX的关系:

在这里插入图片描述

至此,功能全部实现。

完整代码:

import {Component, OnInit, ChangeDetectionStrategy, AfterViewInit, ViewChild, ElementRef} from '@angular/core';
import {fromEvent} from 'rxjs';
import {concatAll, map, takeUntil, withLatestFrom} from 'rxjs/operators';

@Component({
  selector: 'app-drag',
  template: `
      <p class="drag" #drag>
        drag me
      </p>
  `,
  styles: [`
    .drag{
      width: 100px;
      height: 100px;
      border: 1px solid #afafaf;
      border-radius: 50%;
      text-align: center;
      line-height: 100px;
      cursor: pointer;
      position: absolute;
      -moz-user-select:none;
      -webkit-user-select:none;
      -ms-user-select:none;
      -khtml-user-select:none;
      user-select:none;
    }`
  ],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class DragComponent implements OnInit, AfterViewInit {
  @ViewChild('drag', {static: true}) dragEl: ElementRef;
  constructor() { }
  ngOnInit(): void {}

  ngAfterViewInit(): void {
    const mouseDown = fromEvent(this.dragEl.nativeElement, 'mousedown');
    const mouseUp = fromEvent(document.body, 'mouseup');
    const mouseMove = fromEvent(document.body, 'mousemove');
    mouseDown.pipe(
      map(() => mouseMove.pipe(takeUntil(mouseUp))),
      concatAll(),
      withLatestFrom(mouseDown, (move: MouseEvent, down: MouseEvent) => {
        const {width, height} = (down.target as HTMLElement).getBoundingClientRect();
        return {
          x: this.getValue(move.clientX - down.offsetX, window.innerWidth - width, 0),
          y: this.getValue(move.clientY - down.offsetY, window.innerHeight - height, 0)
        };
      })
    ).subscribe(res => {
      this.dragEl.nativeElement.style.left = res.x  + 'px';
      this.dragEl.nativeElement.style.top = res.y + 'px';
    });
  }
  getValue(value, max, min): number {
    return Math.min(Math.max(value, min), max);
  }
}

欢迎关注我的公众号,公众号将第一时间更新angular教程:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yanyi24

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值