0

I'm trying to implement drag and drop on mobile browsers. For desktop browsers I successfully did it using onmouseup, onmousemove and onmousedown. Then, we noticed that it wasn't working on mobile browsers, so I changed the events respectively to onpointerup, onpointermove and onpointerdown, so that it can take into account any kind of interactions from different kind of pointers. It still works fine on desktop browsers, but on mobile pointermove event is fired just a couple of times, before the pointercancel event fires. I tried to handle such event, either preventing it from propagating or performing the default action with the specific methods, but it still stops the pointermove event. The result is that my elements are moving just for two or three steps.

After reading some blog posts online, I tried to use the event ontouchmove and I experienced a strange behavior: when I move the element, if the pointer starts the movement on the element itself, the touchmove event is fired just once after the touchstart and no touchend event is fired. On the other hand, when the movement starts on the background and the element is selected, I am able to get the desired result, because the element is moving and following the cursor as expected. In fact, all the touch events are properly fired in this case.

Here follows the code, the former related to pointermove solution, whereas the latter related to touchmove. Just some additional info: I'm using Angular 13 with Ionic 6, but I think it just depends on how browser handle these events. I took some time to analyze app.diagrams.net, that successfully managed to implement such solution also on mobile, so I guess it is possible and I want to find a way to do that as well.

First solution:

onPointerMove(event: PointerEvent) {
    console.log(event.type);
    if (this.planimetryEditable) { //check whether the map is in edit mode
      if (this.action === "moving") { //check if the right action has been selected
        this._moveElement({
          x: event.x,
          y: event.y
        });
      }
    }
  }

  onPointerUp() { //reset the action
    console.log('Pointer up');
    this.action = ""; 
  }

  onPointerDown(action = "") { //Set the action to 'moving' if the map is editable
    console.log('Pointer down');
    if (this.planimetryEditable) {
      this.action = action;
    }
  }

  mouseClick(event: MouseEvent) {
    console.log('Mouse click');
    const target = event.target as HTMLElement;
    if (target.id === "map" || target.id === 'mapOutline') { 
      //If the background is clicked, deselect the element
      this.selectElement.emit(undefined);
    }
  }
.map-container {
  background-color: white;
  height: 100%;
  width: 100%;
  overflow: auto;
  border-radius: 10px;
  position: relative;
}

#map {
  background: url("/assets/grid-elem.svg");
}
<div class="map-container">
  <svg id="map" [attr.width]="mapOutline.width*scaling" [attr.height]="mapOutline.height*scaling" (pointerup)="onPointerUp()" (click)="mouseClick($event)" (pointermove)="onPointerMove($event)" (pointercancel)="onPointerCancel($event)">
        <image id="mapOutline" *ngIf="mapOutline.url !== ''" [attr.href]="mapOutline.url" [attr.width]="mapOutline.width" [attr.height]="mapOutline.height" [attr.x]="0" [attr.y]="0" [attr.transform]="'scale('+scaling+')'">
        </image>
        <g *ngIf="selectedElement" [attr.transform]="selectedElement.getTransformRotation()">
                <rect [attr.height]="selectedElement.height" [attr.width]="selectedElement.width"
                    [attr.x]="selectedElement.x" [attr.y]="selectedElement.y" fill="transparent" stroke="skyblue" stroke-dasharray="3 3" (pointerdown)="onPointerDown('moving')">
                </rect>
        </g>
  </svg>
</div>

Second solution:

onMouseMove(event: MouseEvent) {
    if (this.planimetryEditable) { //check whether the map is in edit mode
      if (this.action === "moving") { //check if the right action has been selected
        this._moveElement({
    }
  }
  
  onMouseUp() { //reset the action
    this.action = "";
  }
  
  onMouseDown(action = "") { //Set the action to 'moving' if the map is editable
    if (this.planimetryEditable) {
      this.action = action;
    }
  }
  
  onTouchMove(event: TouchEvent) {
    if (this.planimetryEditable) {
      const target = event.target as HTMLElement;
      // Here I removed the check on 'mapOutline' to see what would happen and I found out that the element was moving correctly
      if (target.id !== 'map') {
        event.preventDefault(); // This is needed to prevent scroll behavior on the map
        this._moveElement({
          x: event.changedTouches[0].clientX,
          y: event.changedTouches[0].clientY
        })
      }
    }
  }

  mouseClick(event: MouseEvent) {
    console.log('Mouse click');
    const target = event.target as HTMLElement;
    if (target.id === "map" || target.id === 'mapOutline') { 
      //If the background is clicked, deselect the element
      this.selectElement.emit(undefined);
    }
  }
.map-container {
  background-color: white;
  height: 100%;
  width: 100%;
  overflow: auto;
  border-radius: 10px;
  position: relative;
}

#map {
  background: url("/assets/grid-elem.svg");
}
<div class="map-container">
    <svg id="map" [attr.width]="mapOutline.width*scaling" [attr.height]="mapOutline.height*scaling" (mouseup)="onMouseUp()"
        (mousemove)="onMouseMove($event)" (touchmove)="onTouchMove($event)" (click)="mouseClick($event)">
        <image id="mapOutline" *ngIf="mapOutline.url !== ''" [attr.href]="mapOutline.url" [attr.width]="mapOutline.width" [attr.height]="mapOutline.height" [attr.x]="0" [attr.y]="0" [attr.transform]="'scale('+scaling+')'"></image>
            <g *ngIf="selectedElement" [attr.transform]="selectedElement.getTransformRotation()">
                    <g *ngIf="selectedElement" [attr.transform]="selectedElement.getTransformRotation()">
                <rect [attr.height]="selectedElement.height" [attr.width]="selectedElement.width"
                    [attr.x]="selectedElement.x" [attr.y]="selectedElement.y" fill="transparent" stroke="skyblue" stroke-dasharray="3 3" (mousedown)="onMouseDown('moving')"></rect>
            </g>
      </svg>
    </div>

Thank you in advance to anybody who will be willing to help.

  • 1
    I don't know if can help you, but check this [link](https://w3c.github.io/touch-events/#mouse-events). The order events happens are: touchstart, Zero or more touchmove events, depending on movement of the finger, touchend, mousemove (for compatibility with legacy mouse-specific code), mousedown, mouseup and click. BTW Why not use angular/cdk/drag-drop? – Eliseo Oct 13 '22 at 13:11
  • Does this answer your question? [drag-and-drop on touchscreen](https://stackoverflow.com/questions/11817750/drag-and-drop-on-touchscreen) – Yogi Oct 13 '22 at 13:17
  • I wanted to do that by myself to fully understand how to work with SVG elements and what one can do. Anyway, using angular/cdk/drag-drop I am able to make this work also on mobile. – ivancolucci Oct 14 '22 at 15:48
  • Touch events stop working when the thumb is not over the html element. Therefor you can use `@HostListener('window:touchmove')` instead (note the `window:`). But I was going to say the same: use `@angular/cdk`. It supports everything like dragging between lists etc... – Pieterjan Oct 20 '22 at 18:35

0 Answers0