Components: cdkDrag: with cdkDropList -> wrong Cursor when dragging

Created on 24 Jul 2020  路  9Comments  路  Source: angular/components

Hi all,
issue_1
issue_2

Here is a little sample.
https://stackblitz.com/angular/yvmnrxexppp?file=app%2Fcdk-drag-drop-connected-sorting-example.html

Since version 7.2.x (I noticed) there is the problem that the cursor (set in CSS) adapts to the underlying object during the dragging process.

If the cursor is changed to "move" and I use e.g. move an "input" (drag) then the cursor changes from move to input.

This phenomenon only occurs with cdkDropList, not when the cdkDrag property is only set for a single object.

The CSS:

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);

  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
}

The HTML:

   <div class="col-md-4">

      <div class="drag-container">
        <div class="section-heading">Still Doing</div>
        <div cdkDropList #pendingList="cdkDropList" [cdkDropListData]="todo"
          [cdkDropListConnectedTo]="[doneList,reviewList]" class="item-list" (cdkDropListDropped)="drop($event)">
          <div class="item-box" *ngFor="let item of todo" cdkDrag>{{item}}</div>
        </div>
      </div>
    </div>
P3 cddrag-drop

Most helpful comment

The way we detect whether the user's pointer is over a drop list is through document.elementFromPoint, but the problem is that the dragged element will always be under the user's pointer. We've worked around it by setting pointer-events: none on the element while it's dragging which is why your cursor goes away.

It could be worked around by using document.elementsFromPoint and skipping the dragged item's preview element while going through the array of results, but we'd have to account for some browser differences and do some testing to ensure that performance doesn't degrade since the latter method is doing a bit more work.

All 9 comments

Is this reporting an issue with the example or it's CSS? or is this reporting an issue with the cdkDrag directive?

I think its a issue with the cdkDrag directive. Before version 7.2.x it works fine

Please submit Angular Material and CDK questions here and issues here. This repo is for docs infrastructure only.

I have transferred this issue to the correct repository for you.

The way we detect whether the user's pointer is over a drop list is through document.elementFromPoint, but the problem is that the dragged element will always be under the user's pointer. We've worked around it by setting pointer-events: none on the element while it's dragging which is why your cursor goes away.

It could be worked around by using document.elementsFromPoint and skipping the dragged item's preview element while going through the array of results, but we'd have to account for some browser differences and do some testing to ensure that performance doesn't degrade since the latter method is doing a bit more work.

While implementing custom drag and drop directive (before Angular CDK) i was able to resolve this by hiding/showing dragImage i in a single atomic cycle while mouse moves

  dragging(ev: MouseEvent) {
    // Get element behind drag image at following mouse coordinates
    dragImage.style.visibility = "hidden";
    const curHoveredElement = document.elementFromPoint(ev.clientX, ev.clientY);
    dragImage.style.visibility = "visible";
  }

Did not experience flickering/drawing problems so far (although it was tested on not big drag images/preview objects)

@marijanlekic can you provide a complete example with html? EG what drag event are you listening to? What is the point of accessing the curHoveredElement?

Also @crisbeto you mention in a couple places, https://github.com/angular/components/issues/15090#issuecomment-462449477, that this can be worked around with current APIs. An example (stackblitz would be awesome) of working around this with current APIs would go a long way.

@newmanw what I was referring to in that comment is that you can add a class to the body when dragging starts using the cdkDragStarted class and then remove it when it stops on cdkDragEnded. Then you can apply the cursor style to that global class. E.g.

body.is-dragging {
  cursor: move;
}

@newmanw

I wrote my own drag and drop functionality so i dont use CDK drag events.
In the case that i wrote in my prev comment dragging is being called on event that is similar to dragMove (between drag start and drag end).
The point of curHoveredElement in my case is to trigger dragEnter, dragLeave, dragOver (or such custom events) on the element that is below dragging preview element.

By using "pointer-events:none" we are not not able to control cursor in the way that we want to (wrong cursor/getting cursor from the element that is below dragging preview element), and also, when we use pointerEvents: none, the element that is below dragging preview element, will get events such as "mouseenter", "mouseleave", "mousemove", etc.

So basically i resolved this by not setting "pointer-events:none". Instead, i set cursor to anything that i want to and i get element that is below dragging image by hiding preview element and showing it.

    previewImage.style.visibility = "hidden";
    // Get element below preview element
    const elementBelow = document.elementFromPoint(ev.clientX, ev.clientY);
    previewImage.style.visibility = "visible";
    // Trigger events on elementBelow (dragEnter, dragOver, dragLeave...)

For my needs, i did not face any rendering/other problems...

I have fixed it with this custom directive. Just use it for the same element where the cdkDrag directive is hosted:

@Directive({
  selector: '[appDragCursor]',
})
export class DragCursorDirective implements OnInit, OnDestroy {
  private unsubscribe$: Subject<void> = new Subject();

  constructor(@Inject(WINDOW) private window: Window, private cdkDrag: CdkDrag) {}

  public ngOnInit(): void {
    this.cdkDrag.started.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
      this.window.document.body.style.cursor = 'move';
    });

    this.cdkDrag.ended.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
      this.window.document.body.style.cursor = 'auto';
    });
  }

  public ngOnDestroy(): void {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

kara picture kara  路  3Comments

RoxKilly picture RoxKilly  路  3Comments

constantinlucian picture constantinlucian  路  3Comments

alanpurple picture alanpurple  路  3Comments

vitaly-t picture vitaly-t  路  3Comments