Keeping image centred during drag – touch events – Touch

by
Ali Hasan
cssgrid html javascript touch touch-event

The Problem:

Develop a user interface that allows a user to move an image around a container using touch events while keeping the image centered relative to the touch point during the drag. Implement an adjustment for the offset to ensure the image’s center remains aligned with the touch point.

The Solutions:

Solution 1: Centering Image During Drag

To center the image during the drag, we need to calculate the offset between the touch event and the center of the image. We can achieve this by using the getBoundingClientRect() method on the f element (the square that contains the image).

Here’s the updated code snippet with the necessary changes:

<!– begin snippet: js hide: false console: true babel: false –>

<!– language: lang-js –>

let d = document.createElement(&#39;div&#39;);
d.setAttribute(&#39;class&#39;,&#39;chessboard&#39;)

for (let i = 0; i &lt;8; i++){
  let e = document.createElement(&#39;div&#39;);
  e.setAttribute(&#39;class&#39;,&#39;row&#39;);
  let f = document.createElement(&#39;div&#39;);
  f.setAttribute(&#39;class&#39;,&#39;square white&#39;);
  let img = document.createElement(&#39;img&#39;);
  img.src = &quot;https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png&quot;;
  
  img.addEventListener(&#39;touchstart&#39;, function(e) {
    start = e.target.parentNode.id;

  });
  
  img.addEventListener(&quot;touchmove&quot;, function(e) {
    e.preventDefault();
    img.style.left=(e.touches[0].clientX-f.getBoundingClientRect().left)-img.width/2+&quot;px&quot;;
    img.style.top=(e.touches[0].clientY-f.getBoundingClientRect().top)-img.height/2+&quot;px&quot;;
  });
  document.body.appendChild(d);
  d.appendChild(e);
  e.appendChild(f);
  f.appendChild(img);
}

<!– language: lang-css –>

html, body {
  height: 100%;
  margin: 0;
  padding: 0;  
}
@media screen and (orientation: landscape) {
.chessboard {
  height: 90%;
  aspect-ratio : 1 / 1;     
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
}
@media screen and (orientation: portrait) {
  .chessboard {
    width: 90%;
    aspect-ratio : 1 / 1;
    margin: 0 auto;
    display: flex;
    flex-wrap: wrap;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
  }
.row{
  height: 12.5%;
  width: 100%;
  display: flex;
}
.square {
  height: 100%;
  width: 12.5%;
  position: relative;
  box-sizing: border-box;
}
.white {
  background-color: #f1d8b0;
}
img{
  position: absolute;
  width: 90%;
  height: 90%;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

<!– end snippet –>

In the touchmove event handler, we calculate the offset by subtracting the f.getBoundingClientRect().left and f.getBoundingClientRect().top values from the touch event’s clientX and clientY properties, respectively. We then subtract half the image’s width and height to center the image within the square.

With these changes, the image should now be centered as you drag it around the screen.

Solution 2: Use drag and drop API

The drag and drop API provides a `setDragImage()` method. This method allows you to specify the image that will be displayed when an element is being dragged. By passing the offset arguments to this method, you can center the image during the drag operation.

Let’s go through the steps to implement this solution:

  1. First, include the following CSS styles in your HTML:
    html,
    body {
      height: 100%;
      margin: 0;
      padding: 0;
    }

    @media screen and (orientation: landscape) {
      .chessboard {
        height: 90%;
        aspect-ratio: 1 / 1;
        margin: 0 auto;
        display: flex;
        flex-wrap: wrap;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }
    }

    @media screen and (orientation: portrait) {
      .chessboard {
        width: 90%;
        aspect-ratio: 1 / 1;
        margin: 0 auto;
        display: flex;
        flex-wrap: wrap;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }
    }

    .row {
      height: 24%;
      width: 100%;
      display: flex;
    }

    .square {
      height: 100%;
      width: 24%;
      position: relative;
      box-sizing: border-box;
    }

    .white {
      background-color: #f1d8b0;
    }

    div:has(.piece) {
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .piece {
      background-image: url("https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png");
      position: absolute;
      awidth: 90%;
      aheight: 90%;
      background-size: cover;
      background-position: center;
    }

    .drag {
      background-image: url("https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png");
      position: absolute;
      awidth: 90%;
      aheight: 90%;
      background-size: cover;
      background-position: center;
    }

    .dragged {
      background-image: url("https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png");
      height: 50px;
      width: 50px;
      background-size: contain;
    }
  1. Next, add the following JavaScript code to your HTML:
    <script>
      const imgPath =
        "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png";

      function start_handler(e) {
        e.dataTransfer.setData("text", e.target.id);
        const rect = e.target.getBoundingClientRect();
        // the offests centers the drag image
        e.dataTransfer.setDragImage(e.target, rect.width / 2, rect.height / 2);
      }

      let d = document.createElement("div");
      d.setAttribute("class", "chessboard");

      for (let i = 0; i < 8; i++) {
        let e = document.createElement("div");
        e.setAttribute("class", "row");

        let f = document.createElement("div");
        f.setAttribute("class", "square white");

        let img = document.createElement("div");
        img.setAttribute("class", "piece");
        img.setAttribute("draggable", "true");
        //img.src = imgPath;
        img.setAttribute("ondragstart", "start_handler(event)");

        f.appendChild(img);
        e.appendChild(f);
        d.appendChild(e);
      }

      document.body.appendChild(d);
    </script>
  1. Finally, create a div element with the class chessboard. This div will contain all the squares of the chessboard.

  2. Inside the chessboard div, create 8 rows using the div element with the class row.

  3. Inside each row, create 8 squares using the div element with the class square.

  4. Inside each square, add a div element with the class piece. This div will represent the piece that can be dragged.

  5. Set the draggable attribute of the piece div to true. This will allow the piece to be dragged.

  6. Add the ondragstart event listener to the piece div. When the user starts dragging the piece, this event listener will be triggered.

  7. Inside the start_handler() function, set the data to be transferred during the drag operation using e.dataTransfer.setData().

  8. Then, use e.dataTransfer.setDragImage() to specify the image that will be displayed during the drag operation. The offset arguments center the drag image.

  9. Finally, append the chessboard div to the document body.

This solution uses the drag and drop API to achieve the desired behavior. When the user starts dragging a piece, the setDragImage() method is used to center the drag image. This results in a smooth and intuitive drag experience.

Solution 3: Use offsetX and offsetY to keep the image centred during movement

<p> In this solution, we introduce two new properties called "offsetX" and "offsetY" to the image element. These properties help us to keep track of the image’s center point relative to the parent container. When the image is dragged, we calculate the new position of its center point by subtracting the offset values from the touch coordinates. This allows us to reposition the image while maintaining its centered position.</p>

<pre>
let img = document.createElement("img");
img.src = "image.png";

// Add event listeners to the image

img.addEventListener("touchstart", function (e) {
// Get the bounding rectangle of the parent container
let parentRect = img.parentNode.getBoundingClientRect();

// Get the bounding rectangle of the image
let rect = img.getBoundingClientRect();

// Calculate the center point of the image relative to the parent container
let imgCenterX = rect.width / 2;
let imgCenterY = rect.height / 2;

// Calculate the offset values
img.offsetX = e.touches[0].clientX – rect.left + parentRect.left – imgCenterX;
img.offsetY = e.touches[0].clientY – rect.top + parentRect.top – imgCenterY;

// Set the z-index of the image to bring it to the front
img.style.zIndex = 1;
});

img.addEventListener("touchmove", function (e) {
// Prevent the default behavior of the touch event
e.preventDefault();

// Get the new touch coordinates
let touchX = e.touches[0].clientX;
let touchY = e.touches[0].clientY;

// Calculate the new position of the image’s center point
let newX = touchX – img.offsetX;
let newY = touchY – img.offsetY;

// Update the position of the image
img.style.left = newX + "px";
img.style.top = newY + "px";
});
</pre>

Solution 4: Event Handling and Transformations

To achieve the desired behavior, you can transform the image element using CSS transform property based on the touch event coordinates. Here’s an improved version of the solution:

&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
    &lt;head&gt;
        &lt;meta name=&quot;viewport&quot; content=width=device-width,user-scalable=no&gt;
        &lt;meta charset=&quot;UTF-8&quot;&gt;
        &lt;style&gt;
            html, body {
                height: 100%;
                margin: 0;
                padding: 0;
            }
            @media screen and (orientation: landscape) {
                .chessboard {
                    height: 90%;
                    aspect-ratio : 1 / 1;     
                    margin: 0 auto;
                    display: flex;
                    flex-wrap: wrap;
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                }
            }
            @media screen and (orientation: portrait) {
                .chessboard {
                    width: 90%;
                    aspect-ratio : 1 / 1;
                    margin: 0 auto;
                    display: flex;
                    flex-wrap: wrap;
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                }
            }
            .row{
                height: 12.5%;
                width: 100%;
                display: flex;
            }
            .square {
                height: 100%;
                width: 12.5%;
                position: relative;
                box-sizing: border-box;
            }
            .white {
                background-color: #f1d8b0;
            }
            img{
                position: absolute;
                width: 90%;
                height: 90%;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
            }
            .dragging {
                z-index: 1;
            }
        &lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;script&gt;
            let img = document.querySelector(&#39;img&#39;);
            let divs = document.querySelectorAll(&#39;div&#39;);

            img.addEventListener(&#39;touchstart&#39;, function(e) {
                e.preventDefault();
                img.classList.add(&#39;dragging&#39;);
                let rect = img.getBoundingClientRect();
                offsetX = e.touches[0].clientX - rect.left;
                offsetY = e.touches[0].clientY - rect.top;
            });

            img.addEventListener(&#39;touchmove&#39;, function(e) {
                e.preventDefault();
                let x = e.touches[0].clientX - offsetX;
                let y = e.touches[0].clientY - offsetY;
                img.style.transform = `translate(${x}px, ${y}px)`;
            });

            img.addEventListener(&#39;touchend&#39;, function(e) {
                e.preventDefault();
                img.classList.remove(&#39;dragging&#39;);
                img.style.transform = &#39;none&#39;;

                let rect = img.getBoundingClientRect();
                let target = null;
                for (let div of divs) {
                    let divRect = div.getBoundingClientRect();
                    if (rect.left &lt; divRect.right &amp;&amp; rect.right &gt; divRect.left &amp;&amp; rect.top &lt; divRect.bottom &amp;&amp; rect.bottom &gt; divRect.top) {
                        target = div;
                        break;
                    }
                }
                if (target) {
                    let temp = target.innerHTML;
                    target.innerHTML = img.outerHTML;
                    img.outerHTML = temp;
                    img = document.querySelector(&#39;img&#39;);
                    img.addEventListener(&#39;touchstart&#39;, touchstart);
                    img.addEventListener(&#39;touchmove&#39;, touchmove);
                    img.addEventListener(&#39;touchend&#39;, touchend);
                }
            });
        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;

In this code:

  1. We use the offsetX and offsetY variables to store the difference between the touch position and the image’s position, which helps in calculating the translation.
  2. The touchstart event listener adds a dragging class to the image, enabling the CSS z-index property to ensure the image stays on top during dragging.
  3. The touchmove event listener calculates the translation based on the touch position and the offset, and applies the translation to the image using the transform property.
  4. The touchend event listener removes the dragging class and resets the image’s transform to none.
  5. It also checks for any overlapping elements using a simple collision detection algorithm and swaps the image’s position with the overlapping element if there’s a match.

This solution provides better control over the image dragging behavior and allows it to move smoothly to the desired location.

Q&A

How to keep image center while drag event by using touch events.?

Add touchstart, touchmove, touchend events to simulate drag and drop functionality.

How to do centering of the image on drag.

By using getBoundingClientRect() to get the position and size of the elements and compare them to the touch coordinates to detect overlaps.

How can we get the touch position accurately to move the object accordingly?

Use translate property to move the image relative to its original position, and adjust it to the touch point.

Video Explanation:

The following video, titled "Canon R50 Tutorial Training Video Overview Users Guide Set Up ...", provides additional insights and in-depth exploration related to the topics discussed in this post.

Play video

... in the comment section: http://www.michaelthemaven.com/?postID ... Touch and Drag Auto Focus 01:44:56 - Video Focusing DRIVE MODES ...