先来看具体效果:
实现原理:通过鼠标移动,计算鼠标在页面中的位置,从而改变DOM元素绝对定位的left
、top
值。
-
首先css设置 DOM
元素为绝对定位; -
捕获鼠标点击之后的鼠标移动事件,实时计算鼠标位置,直到 mouseup
; -
边界判断并实时修改位置。
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))), )
MouseEvent
对象,就能拿到我们想要的 clientX
、 clientY
。
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'; });
clientX
、 offsetX
的关系:完整代码:
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); } }
OK!
本文转载自:岩弈,版权归原作者所有,本博客仅以学习目的的传播渠道,不作版权和内容观点阐述,转载时根据场景需要有所改动。
最新评论