A guide to creating interactive elements that can be dragged using touchscreens and mouse controls

My drag and drop application code works perfectly on desktop, but when I try to use it on mobile, the drag events do not function as expected. I understand that touch events are necessary, but I am unsure how to set them up and implement the required functions.

<style>
    .objects {
        display:inline-block;
        background-color: #FFF3CC;
        border: #DFBC6A 1px solid;
        width: 50px;
        height: 50px;
        margin: 10px;
        padding: 8px;
        font-size: 18px;
        text-align: center;
        box-shadow: 2px 2px 2px #999;
        cursor: move;
    }
    #drop_zone {
        background-color: #EEE;
        border: #999 1px solid;
        width: 280px;
        height: 200px;
        padding: 8px;
        font-size: 18px;
    }
    </style>
  <h2 id="app_status">App status...</h2>
  <h1>Drop Zone</h1>
  <div id="drop_zone" ondragenter="drag_enter(event)" ondrop="drag_drop(event)" ondragover="return false" ondragleave="drag_leave(event)" ></div>
  <div id="object1" class="objects" draggable="true" ondragstart="drag_start(event)" ondragend="drag_end(event)">object 1</div>
  <div id="object2" class="objects" draggable="true" ondragstart="drag_start(event)" ondragend="drag_end(event)">object 2</div>
  <div id="object3" class="objects" draggable="true" ondragstart="drag_start(event)" ondragend="drag_end(event)">object 3</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script>
    function _(id){
       return document.getElementById(id);
    }
    var droppedIn = false;
    function drag_start(event) {
        _('app_status').innerHTML = "Dragging the "+event.target.getAttribute('id');
        event.dataTransfer.dropEffect = "move";
        event.dataTransfer.setData("text", event.target.getAttribute('id') );
    }
    function drag_enter(event) {
        _('app_status').innerHTML = "You are dragging over the "+event.target.getAttribute('id');
    }
    function drag_leave(event) {
        _('app_status').innerHTML = "You left the "+event.target.getAttribute('id');
    }
    function drag_drop(event) {
        event.preventDefault(); /* Prevent undesirable default behavior while dropping */
        var elem_id = event.dataTransfer.getData("text");
        _('app_status').innerHTML = "Dropped "+elem_id+" into the "+event.target.getAttribute('id');
        droppedIn = true;
          // Create our XMLHttpRequest object
          var hr = new XMLHttpRequest();
          // Create some variables we need to send to our PHP file
          var url = "jqueryserver.php";
          var vars = "value= "+elem_id;

          hr.open("POST", url, true);

          // Set content type header information for sending url encoded variables in the request
          hr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
          // Access the onreadystatechange event for the XMLHttpRequest object
          hr.onreadystatechange = function() {
            if(hr.readyState == 4 && hr.status == 200) {
              var return_data = hr.responseText;
            document.getElementById("app_status").innerHTML = return_data;
            }
          }
          // Send the data to PHP now... and wait for response to update the status div
          hr.send(vars); // Actually execute the request
          document.getElementById("app_status").innerHTML = event.target.getAttribute('id')+"processing...";
        }

    function drag_end(event) {
        if(droppedIn == false){
            _('app_status').innerHTML = "You let the "+event.target.getAttribute('id')+" go.";
        }
        droppedIn = false;
    }

</script>

Answer №1

This query may be outdated, but I have provided an example below on how to drag elements using both touch and mouse without relying on any external library.

 /******************************
        Required JavaScript */

        function makeDraggable(elmnt) {
            let pos1 = 0,
                pos2 = 0,
                pos3 = 0,
                pos4 = 0;

            let dragHandle = elmnt.getElementsByClassName("drag-handle")[0];


            if (dragHandle !== undefined) {
                // If the handle is present, it will be used to move the DIV:
                dragHandle.onmousedown = dragMouseDown;
                dragHandle.ontouchstart = dragMouseDown; // Added touch event

            } else {
                // Otherwise, the DIV can be moved from anywhere inside it:
                elmnt.onmousedown = dragMouseDown;
                elmnt.ontouchstart = dragMouseDown; // Added touch event
            }

            function dragMouseDown(e) {
                e = e || window.event;
                e.preventDefault();
               

                // Get the touch or click position
                if (e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend' || e.type == 'touchcancel') {
                    let evt = (typeof e.originalEvent === 'undefined') ? e : e.originalEvent;
                    let touch = evt.touches[0] || evt.changedTouches[0];
                    x = touch.pageX;
                    y = touch.pageY;
                } else if (e.type == 'mousedown' || e.type == 'mouseup' || e.type == 'mousemove' || e.type == 'mouseover' || e.type == 'mouseout' || e.type == 'mouseenter' || e.type == 'mouseleave') {
                    x = e.clientX;
                    y = e.clientY;
                }

                console.log("Starting position x: "+x+" y:"+y);

                pos3 = x;
                pos4 = y;
                document.onmouseup = closeDragElement;
                document.ontouchend = closeDragElement;
                document.onmousemove = elementDrag;
                document.ontouchmove = elementDrag;
            }

            function elementDrag(e) {
                e = e || window.event;
                e.preventDefault();

                if (e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend' || e.type == 'touchcancel') {
                    let evt = (typeof e.originalEvent === 'undefined') ? e : e.originalEvent;
                    let touch = evt.touches[0] || evt.changedTouches[0];
                    x = touch.pageX;
                    y = touch.pageY;
                } else if (e.type == 'mousedown' || e.type == 'mouseup' || e.type == 'mousemove' || e.type == 'mouseover' || e.type == 'mouseout' || e.type == 'mouseenter' || e.type == 'mouseleave') {
                    x = e.clientX;
                    y = e.clientY;
                }

                pos1 = pos3 - x;
                pos2 = pos4 - y;
                pos3 = x;
                pos4 = y;

                elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
                elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
            }

            function closeDragElement() {
                console.log("End position x: "+pos3+" y:"+pos4);

                document.onmouseup = null;
                document.ontouchcancel = null;
                document.ontouchend = null;
                document.onmousemove = null;
                document.ontouchmove = null;
            }
        }




        /*******************************
        Test JavaScript */

        makeDraggable(document.getElementById("test-draggable"));
        makeDraggable(document.getElementById("test-draggable2"));
        makeDraggable(document.getElementById("test-full-draggable"));
 /******************************
        Required CSS */

 .draggable {
     position: absolute;
     z-index: 9;
 }

 .drag-handle {
     cursor: move;
     z-index: 10;
 }

 .full-draggable {
     cursor: move;
 }

 /*******************************
        Test CSS */

 .draggable {
     background-color: #f1f1f1;
     border: 1px solid #d3d3d3;
     text-align: center;
 }

 .draggable .drag-handle {
     padding: 10px;
     background-color: #2196F3;
     color: #fff;
 }

 #test-draggable2 {
     left: 200px
 }

 #test-full-draggable {
     left: 500px
 }
 <div id="test-draggable" class="draggable">
        <div class="drag-handle">Drag using me</div>
        I'm the <br>content, <br>you can't<br> drag touching<br> there
    </div>

    <div id="test-draggable2" class="draggable">
        <div class="drag-handle">Drag using me</div>
        It even works with many draggables<br>
        I'm the <br>content, <br>you can't<br> drag touching<br> there
    </div>

    <div id="test-full-draggable" class="draggable full-draggable">


        I don't have <br> a handle so <br> I'm completely <br> draggable
    </div>

Answer №2

After discovering a flaw in Marco's answer and finding a solution, I am sharing the answer and providing the working code on codesandbox as promised.

It seems that the Surface Book 2 (and potentially other devices) had trouble with how Marco had set up the listeners for touch events, possibly due to a conflict in the javascript engine or the interaction with the operating system.

I made a simple change to the event listener definitions in three places in his code (full code available in the sandbox link above):

 // dragHandle.ontouchstart = dragMouseDown; //added touch event
 dragHandle.addEventListener("touchstart", dragMouseDown, false);

and:

    // document.ontouchend = closeDragElement;
    elmnt.addEventListener("touchend", closeDragElement, false);
    // call a function whenever the cursor moves:
    document.onmousemove = elementDrag;
    // document.ontouchmove = elementDrag;
    document.addEventListener("touchmove", elementDrag, false);

Lastly, in the function closeDragElement:

// document.ontouchend = null; //added touch event
document.removeEventListener("touchend", closeDragElement, false);
document.removeEventListener("touchmove", elementDrag, false);
// document.ontouchmove = null; //added touch event

It may seem like magic, but these small adjustments did the trick!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for storing mustache templates for rendering in Node.js

My data is stored in the following format: let data = {"list" :[ { "email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="98f9fafb8afef0f9f5e8f4fdb6fbf7f5">[email protected] ...

unable to retrieve the value of the table row with a particular class designation

I'm currently working with a table code and I need to retrieve the value of the table row with the class "highlight". However, when trying to do so with the code below, I'm getting a null result. Can someone please assist me? Table name: itemtab ...

The Jsoup function for sending data does not yield any results

Can someone assist me with sending data to a form in this format? <form id="money" action="" method="post"> <input id="user" type="text" placeholder="Username" maxlenght="10" name="user"></input> <div class="select"> <select id= ...

Accessing a webpage solely by logging in prevents unauthorized access

My login page currently redirects to a page named gallery.html upon successful login. However, I have noticed that entering /gallery.html in the URL also directly accesses the secure page without logging in. Can anyone suggest an effective way to impleme ...

You cannot use a relative path when inserting an image tag in React

I am having an issue with my img tag not loading the desired image when using a relative src path in my React project. When I try: //import { ReactComponent } from '*.svg'; import React from 'react'; import firebase from "./firebas ...

Issue with Checkbox Functionality Between Parent and Child Components in React.js

In the main component, I have four checkboxes. My goal is to display a value from a function in the child component based on whether each checkbox is checked or not. export default class App extends Component { constructor(props) { super(props); ...

Animating SVG fills based on height using JavaScript

I am looking to create an animated gradient effect using JavaScript instead of CSS. I want the gradient animation to change based on the height in both SVG elements. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="200" height="20 ...

How to Use Vue.js to Find the Nearest Div Element with a Specific

Below is the HTML code I am working with: <div id="app"> <div class="image"> <div class="overlay"> <p>Some overlay text</p> </div> <img src="https://placeimg.com/640/480/any" class="img-fluid"> ...

Partially Assessed Ajax Response

I am struggling with my ajax response as it seems to evaluate only some of the html, but not all of it. For instance, I have this code that replaces a div with the response from the request: eval(document.getElementById("test").innerHTML-xmlhttp.response ...

Touch target for scrollbars

While developing the modal in HTML, I encountered an issue where keyboard-only users cannot access all content within the modal. I came across a note stating that the scrollbar touch target should be 44px by 44px. I tried reading this documentation https ...

Warning: MaxListenersExceededNotification may occur during the installation or creation of Node.js projects on macOS

Encountering a warning message while attempting to set up or generate Node.js projects on macOS (darwin): (node:80101) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 close listeners added to [TLSSocket]. Use emitter.setMaxList ...

The method item.appendChild does not exist as a function

Despite being a common error, I've researched extensively and still can't figure out why it's happening. It seems like it should be an easy fix, but I'm struggling to find the solution on my own. var item = document.createElement("div" ...

What is the process for creating a hover linear wipe transition using CSS/JS?

It's worth noting that I can't simply stack one image on top of the other because I'll be dealing with transparent images as well. preview of linear wipe ...

User form not triggering post requests

I have a unique react blog application embedded with a form for submitting intriguing blog posts. The setup includes a server, routes, model, and controllers for fetch requests. Surprisingly, everything functions impeccably when tested on Postman. However, ...

Use JQuery to load a particular page by specifying its ID with the hashtag symbol

I am facing an issue where I need to create multiple private chatrooms per user. Although I have managed to make it work, I encountered a problem where if there are more than one private chats happening simultaneously, the content of these chats gets broad ...

The slash character is escaped by the RegExp constructor, but the dot character is

Consider the following code: console.log(new RegExp('.git')); console.log(new RegExp('scripts/npm')); which produces the following output: /.git/ /scripts\/npm/ The puzzling question here is - why does it escape the slash in &a ...

How to Send an Array to AJAX and Retrieve the Data in Codeigniter Controller

I am attempting to retrieve table data, store it in an array, and pass it to the controller so I can write it in PHP Excel. Previously, I had success with other data but now my excel file is turning up empty. Below is the JavaScript code snippet: var Ta ...

How can I locate a Forum or Node using a JWT Token as a reference point?

Could someone please guide me on locating my forum using my JWT token? exports.getByOwnerID = function (req, res, next) { Forum.find({createdBy: req.body.createdBy}) .then(doc => { if(!doc) { return res.status(400).end();} return res.sta ...

Delete the item if the link uses Javascript: void(0)

<a class="slicknav_item" href="home">Home<span></span></a> <a class="slicknav_item" href="about">About<span></span></a> <a class="slicknav_item" href="javascript: void(0)">Services<span></span& ...

Node.js: Calculating the number of requests processed per second in the http.get()

In my node.js project, I am creating a simple application for sending HTTP requests. var http = require('http'); var options = { host: 'www.example.com', port: 80, path: '/index.html' }; for(var i = 0; i < 500; i++ ...