Angular 5拖拽时垂直滚动,元素与光标出现偏移问题
解决Angular 5拖拽时滚动条导致元素与光标错位问题
这个问题我之前也碰到过!核心原因是拖拽时的坐标计算没考虑容器(或页面)的滚动偏移量——clientX/clientY是相对于当前视口的位置,但当容器滚动后,元素的定位基准(容器左上角)已经偏离了视口左上角,两者的差值就是scrollTop/scrollLeft,没把这个值算进去就会导致光标和拖拽元素错位。
具体解决方案
你的拖拽核心逻辑应该在appTagDraggable指令里,我们需要修改指令中的坐标计算逻辑,把容器的滚动偏移量加进去:
1. 修正指令中的拖拽逻辑
假设你的指令代码大致如下,我帮你调整关键部分:
import { Directive, ElementRef, HostListener, Output, EventEmitter, OnInit } from '@angular/core'; @Directive({ selector: '[appTagDraggable]' }) export class TagDraggableDirective implements OnInit { private isDragging = false; private startX: number; private startY: number; private initialLeft: number; private initialTop: number; private parentContainer: HTMLElement; // 拖拽的父容器(.map元素) private containerWidth: number; private containerHeight: number; @Output() dragStart = new EventEmitter(); @Output() dragMove = new EventEmitter<any>(); @Output() dragEnd = new EventEmitter(); constructor(private element: ElementRef) {} ngOnInit() { // 获取父容器(根据你的DOM结构,定位到.map类元素) this.parentContainer = document.querySelector('.map') as HTMLElement; this.updateContainerSize(); } // 监听窗口resize,更新容器尺寸 @HostListener('window:resize') onResize() { this.updateContainerSize(); } private updateContainerSize() { this.containerWidth = this.parentContainer.clientWidth; this.containerHeight = this.parentContainer.clientHeight; } @HostListener('mousedown', ['$event']) onMouseDown(event: MouseEvent) { event.preventDefault(); this.dragStart.emit(); this.isDragging = true; // 关键:计算鼠标相对于父容器的初始位置,加上容器的滚动偏移 this.startX = event.clientX + this.parentContainer.scrollLeft; this.startY = event.clientY + this.parentContainer.scrollTop; // 获取元素初始的百分比定位值 this.initialLeft = parseFloat(this.element.nativeElement.style.left) || 0; this.initialTop = parseFloat(this.element.nativeElement.style.top) || 0; } @HostListener('document:mousemove', ['$event']) onMouseMove(event: MouseEvent) { if (!this.isDragging) return; event.preventDefault(); // 关键:计算当前鼠标相对于父容器的位置,同样加上滚动偏移 const currentX = event.clientX + this.parentContainer.scrollLeft; const currentY = event.clientY + this.parentContainer.scrollTop; // 计算偏移量,转换成百分比(和你的模板定位方式匹配) const deltaX = currentX - this.startX; const deltaY = currentY - this.startY; const newLeft = this.initialLeft + (deltaX / this.containerWidth * 100); const newTop = this.initialTop + (deltaY / this.containerHeight * 100); // 可选:限制元素在容器内,防止拖出边界 const maxLeft = 100 - parseFloat(this.element.nativeElement.style.width) || 0; const maxTop = 100 - parseFloat(this.element.nativeElement.style.height) || 0; const clampedLeft = Math.max(0, Math.min(maxLeft, newLeft)); const clampedTop = Math.max(0, Math.min(maxTop, newTop)); // 更新元素位置 this.element.nativeElement.style.left = `${clampedLeft}%`; this.element.nativeElement.style.top = `${clampedTop}%`; this.dragMove.emit({left: clampedLeft, top: clampedTop}); } @HostListener('document:mouseup') onMouseUp() { if (this.isDragging) { this.isDragging = false; this.dragEnd.emit(); } } }
2. 模板的小优化
你把(window:resize)绑定在每个子div上有点冗余,上面的指令已经包含了resize监听,所以模板里可以去掉这个绑定:
<div class="row"> <div class="map"> <img style="width:100%;height: 100%;cursor: pointer;" id="imgMap" class="img-thumbnail" src="https://imgd.aeplcdn.com/1056x594/n/3cpbpra_1422080.jpg?q=80" alt=""/> <div *ngFor="let xandYcoordinate of xandYcoordinates"> <div appTagDraggable [style.left.%]="xandYcoordinate.left" [style.top.%]="xandYcoordinate.top" class="box" [style.width.%]="xandYcoordinate.width" [style.height.%]="xandYcoordinate.height" (dragStart)="onDragStart()" (dragMove)="onDragMove($event)" (dragEnd)="onDragEnd()"> </div> </div> </div> </div>
为什么这样能解决问题?
当父容器出现滚动条时,scrollTop是容器向上滚动的距离,scrollLeft是向左滚动的距离。clientX/clientY是鼠标相对于浏览器视口的位置,加上滚动偏移量后,就能得到鼠标相对于父容器左上角的真实位置,这样计算出来的元素偏移量就不会因为滚动而错位了。
内容的提问来源于stack exchange,提问作者Harish
相关产品推荐
相关产品推荐

