jerosoler / Drawflow

Simple flow library 🖥️🖱️
https://jerosoler.github.io/Drawflow/
MIT License
4.78k stars 741 forks source link

Canvas jumps / moves randomly when panning on mobile device #470

Closed gpack closed 2 years ago

gpack commented 2 years ago

I've attached a quick video of the issue using the demo page (see below link): https://wiskdigital.com.au/temp/Grid_jumps_on_mobile.mov

You can't really see my touches in the video, but I simply tap once at the top left corner and then I try to drag up from the bottom right corner and as soon as I start dragging you can see the canvas/grid jump/move away off the screen into a strange (x,y) coordinate.

You can try replicate this bug on your phone by simply touching somewhere on the canvas and then trying to pan from another point on the canvas, then the whole grid jumps to a random coordinate.

I think this has to do with possibly the "touch" events for the mobile maybe? Or is it the "pinch zoom" function for mobile devices that is causing this issue possibly?

Do you have any idea on how to fix this? Would it best to disable pinch zoom function, would that help resolve this bug? if yes, then how can I disable it?

thanks for your help.

jerosoler commented 2 years ago

The zoom does not affect. Since it does not store any coordinates. It just calls the Zoom in and Zoom Out functions.

It seems that he takes the last position of the previous "touch". And it does not respect the last position of the touch.

gpack commented 2 years ago

I don't think it is relating to zoom functions.

Yes, it seems like it's losing the last "touch" position (coordinates). This problem only happens IF the user just taps anywhere on the canvas (without moving the grid). So what is happening when user taps on canvas, is this recording some coordinate or doing something?

Because if you drag the canvas and then drag again it works just fine. So it's only causing an issue when a user "taps" first and then tries to drag (the canvas position gets mixed up).

Do you know what the problem could be or how to fix it?

jerosoler commented 2 years ago

I think I have found the problem.

Try copying this code before the editor.start();

I have only added the variables mouse_x and mouse_y at the end of the function.

I've been testing and it seems correct.

You can check?

 editor.click = function (e) {
    this.dispatch('click', e);
    if(this.editor_mode === 'fixed') {
      //return false;
       e.preventDefault();
       if(e.target.classList[0] === 'parent-drawflow' || e.target.classList[0] === 'drawflow') {
         this.ele_selected = e.target.closest(".parent-drawflow");
       } else {
         return false;
       }
    } else if(this.editor_mode === 'view') {
      if(e.target.closest(".drawflow") != null || e.target.matches('.parent-drawflow')) {
        this.ele_selected = e.target.closest(".parent-drawflow");
        e.preventDefault();
      }
    } else {
      this.first_click = e.target;
      this.ele_selected = e.target;
      if(e.button === 0) {
        this.contextmenuDel();
      }

      if(e.target.closest(".drawflow_content_node") != null) {
        this.ele_selected = e.target.closest(".drawflow_content_node").parentElement;
      }
    }
    switch (this.ele_selected.classList[0]) {
      case 'drawflow-node':
        if(this.node_selected != null) {
          this.node_selected.classList.remove("selected");
          if(this.node_selected != this.ele_selected) {
            this.dispatch('nodeUnselected', true);
          }
        }
        if(this.connection_selected != null) {
          this.connection_selected.classList.remove("selected");
          this.removeReouteConnectionSelected();
          this.connection_selected = null;
        }
        if(this.node_selected != this.ele_selected) {
          this.dispatch('nodeSelected', this.ele_selected.id.slice(5));
        }
        this.node_selected = this.ele_selected;
        this.node_selected.classList.add("selected");
        if(!this.draggable_inputs) {
          if(e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT' && e.target.hasAttribute('contenteditable') !== true) {
            this.drag = true;
          }
        } else {
          if(e.target.tagName !== 'SELECT') {
            this.drag = true;
          }
        }
        break;
      case 'output':
        this.connection = true;
        if(this.node_selected != null) {
          this.node_selected.classList.remove("selected");
          this.node_selected = null;
          this.dispatch('nodeUnselected', true);
        }
        if(this.connection_selected != null) {
          this.connection_selected.classList.remove("selected");
          this.removeReouteConnectionSelected();
          this.connection_selected = null;
        }
        this.drawConnection(e.target);
        break;
      case 'parent-drawflow':
        if(this.node_selected != null) {
          this.node_selected.classList.remove("selected");
          this.node_selected = null;
          this.dispatch('nodeUnselected', true);
        }
        if(this.connection_selected != null) {
          this.connection_selected.classList.remove("selected");
          this.removeReouteConnectionSelected();
          this.connection_selected = null;
        }
        this.editor_selected = true;
        break;
      case 'drawflow':
        if(this.node_selected != null) {
          this.node_selected.classList.remove("selected");
          this.node_selected = null;
          this.dispatch('nodeUnselected', true);
        }
        if(this.connection_selected != null) {
          this.connection_selected.classList.remove("selected");
          this.removeReouteConnectionSelected();
          this.connection_selected = null;
        }
        this.editor_selected = true;
        break;
      case 'main-path':
        if(this.node_selected != null) {
          this.node_selected.classList.remove("selected");
          this.node_selected = null;
          this.dispatch('nodeUnselected', true);
        }
        if(this.connection_selected != null) {
          this.connection_selected.classList.remove("selected");
          this.removeReouteConnectionSelected();
          this.connection_selected = null;
        }
        this.connection_selected = this.ele_selected;
        this.connection_selected.classList.add("selected");
        const listclassConnection = this.connection_selected.parentElement.classList;
        if(listclassConnection.length > 1){
          this.dispatch('connectionSelected', { output_id: listclassConnection[2].slice(14), input_id: listclassConnection[1].slice(13), output_class: listclassConnection[3], input_class: listclassConnection[4] });
          if(this.reroute_fix_curvature) {
            this.connection_selected.parentElement.querySelectorAll(".main-path").forEach((item, i) => {
              item.classList.add("selected");
            });
          }
        }
      break;
      case 'point':
        this.drag_point = true;
        this.ele_selected.classList.add("selected");
      break;
      case 'drawflow-delete':
        if(this.node_selected ) {
          this.removeNodeId(this.node_selected.id);
        }

        if(this.connection_selected) {
          this.removeConnection();
        }

        if(this.node_selected != null) {
          this.node_selected.classList.remove("selected");
          this.node_selected = null;
          this.dispatch('nodeUnselected', true);
        }
        if(this.connection_selected != null) {
          this.connection_selected.classList.remove("selected");
          this.removeReouteConnectionSelected();
          this.connection_selected = null;
        }

      break;
      default:
    }
    if (e.type === "touchstart") {
      this.pos_x = e.touches[0].clientX;
      this.pos_x_start = e.touches[0].clientX;
      this.pos_y = e.touches[0].clientY;
      this.pos_y_start = e.touches[0].clientY;
      this.mouse_x = e.touches[0].clientX;
      this.mouse_y = e.touches[0].clientY;
    } else {
      this.pos_x = e.clientX;
      this.pos_x_start = e.clientX;
      this.pos_y = e.clientY;
      this.pos_y_start = e.clientY;
    }
    if (['input','output','main-path'].includes(this.ele_selected.classList[0])) {
      e.preventDefault();
    }
    this.dispatch('clickEnd', e);
  }
gpack commented 2 years ago

Yep, it worked! I pasted the code above and it fixed the jumping issue on mobile now. Great work!

In my project I'm using "drawflow.min.js", so should I just use the full code you sent me OR do I need to update some lines of code in "drawflow.min.js"?

I'm just wondering what is the most elegant/best way?

jerosoler commented 2 years ago

I'll put it in the library, I'll let you know when it's implemented.

gpack commented 2 years ago

That'll be great. thanks Jero.

jerosoler commented 2 years ago

Update in version 0.0.59