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.