Keeping image centred during drag - touch events

345 views Asked by At

A continuation from a previous unanswered post in which I wanted to centre an image to the touch that is dragging it. For example if the user touches the top right corner of the image, the image will automatically move so its centre is on that touch instead.

In the first code snippet, this is the behaviour I am trying to achieve:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta name="viewport" content=width=device-width,user-scalable=no>
        <meta charset="UTF-8">
    </head>
    <body>
        <script>
            let shiftX;
            let shiftY;
            let img = document.createElement('img');
            img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png";
            img.style.position = "absolute";
            img.style.height = "20%";
            img.addEventListener('touchstart', function(e) {
              start = e.target.parentNode.id;
              img.style.zIndex = 1;
            });
            img.addEventListener("touchmove", function(e) {
              e.preventDefault();
              img.style.left=e.touches[0].clientX-img.width/2+"px";
              img.style.top=e.touches[0].clientY-img.height/2+"px";

            });
            document.body.appendChild(img);
        </script> 
    </body>
</html>

But my best attempt of achieving this in my actual code is like so:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta name="viewport" content=width=device-width,user-scalable=no>
        <meta charset="UTF-8">
        <style>
          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%);
          }          
        </style>
    </head>
    <body>
        <script>
            let initialX;
            let initialY;
            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('img');
              img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png";
              img.addEventListener('touchstart', function(e) {
                start = e.target.parentNode.id;
                img.style.zIndex = 1;
                initialX = e.touches[0].clientX;
                initialY = e.touches[0].clientY;
              });
              img.addEventListener("touchmove", function(e) {
                e.preventDefault();
                img.style.left=(e.touches[0].clientX-initialX)+img.width/2+"px";
                img.style.top=(e.touches[0].clientY-initialY)+img.height/2+"px";
              });
              document.body.appendChild(d);
              d.appendChild(e);
              e.appendChild(f);
              f.appendChild(img);
            }
        </script> 
    </body>
</html>

Note:

Ignore the behaviour upon the second touch of the circles this is fixed in the main code

Here we have to use an InitialX and InitialY as in the previous example it was 0,0 (for style.left, style.top we need the change in x and y coordinates)

While this solution is nice, as it removes the offset it does not get this center functionality I want (maybe I'm being a bit to perfectionist here).

I'm pretty unsure what to do next, I have tried adding different offsets to no avail.

I have found this website quite useful though.

Please comment any improvements I can make to this question.

Edit due to answers: Please if you think you can answer this question could you make sure that your answer is in a code snippet editing the code of the latter snippet in this question. Secondly can you make sure it actually works for touch events, not drag events, touch events. Thank you.

5

There are 5 answers

0
ATP On BEST ANSWER

Try this:

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('img');
  img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png";
  
  img.addEventListener('touchstart', function(e) {
    start = e.target.parentNode.id;

  });
  
  img.addEventListener("touchmove", function(e) {
    e.preventDefault();
    img.style.left=(e.touches[0].clientX-f.getBoundingClientRect().left)+"px";
    img.style.top=(e.touches[0].clientY-f.getBoundingClientRect().top)+"px";
  });
  document.body.appendChild(d);
  d.appendChild(e);
  e.appendChild(f);
  f.appendChild(img);
}
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%);
}

1
TutsInsider On

One possible solution is to use the touchstart, touchmove, and touchend events to simulate the drag and drop functionality. You can use the getBoundingClientRect method to get the position and size of the elements, and compare them to the touch coordinates to detect overlaps.

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

let initialX, initialY, offsetX, offsetY;
let img = document.querySelector('img');
let divs = document.querySelectorAll('div');

img.addEventListener('touchstart', function(e) {
  e.preventDefault();
  let rect = img.getBoundingClientRect();
  initialX = e.touches[0].clientX - rect.left;
  initialY = e.touches[0].clientY - rect.top;
  offsetX = rect.left;
  offsetY = rect.top;
  img.style.zIndex = 1;
});

img.addEventListener('touchmove', function(e) {
  e.preventDefault();
  let x = e.touches[0].clientX - initialX;
  let y = e.touches[0].clientY - initialY;
  img.style.transform = `translate(${x}px, ${y}px)`;
});

img.addEventListener('touchend', function(e) {
  e.preventDefault();
  let rect = img.getBoundingClientRect();
  let target = null;
  for (let div of divs) {
    let divRect = div.getBoundingClientRect();
    if (rect.left < divRect.right && rect.right > divRect.left && rect.top < divRect.bottom && rect.bottom > divRect.top) {
      target = div;
      break;
    }
  }
  if (target) {
    let temp = target.innerHTML;
    target.innerHTML = img.outerHTML;
    img.outerHTML = temp;
    img = document.querySelector('img');
    img.addEventListener('touchstart', touchstart);
    img.addEventListener('touchmove', touchmove);
    img.addEventListener('touchend', touchend);
  }
  img.style.transform = 'none';
  img.style.zIndex = 0;
});

5
the Hutt On

You can use drag and drop API and use setDragImage() method which takes offset arguments. The offset arguments can be used to center the image:

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;
  width: 90%;
  height: 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;
}
<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
Gokul Krishnan Nair On

In order to achieve the desired functionality of centering, you have the option to modify the touchmove event handler in the second code snippet provided. Instead of directly setting the left and top styles, you can calculate the new position by taking into account the center of the image and the touch coordinates. Below, you will find an updated version of your code snippet:

In this modification, I have devised a method to determine the current center position of the image, taking into account both the centerX and centerY coordinates. With this information, I am able to make precise adjustments to the left and top styles. The purpose of these adjustments is to ensure that the image remains perfectly centered on the touch point. As an additional enhancement, I have incorporated touch-action: none; into the image styles. This serves the purpose of disabling any default touch gestures that may be inherent in the browser, thereby granting us better control and manipulation over the image.

let initialX;
let initialY;
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('img');
    img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png";
    img.addEventListener('touchstart', function (e) {
        img.style.zIndex = 1;
        initialX = e.touches[0].clientX;
        initialY = e.touches[0].clientY;
    });
    img.addEventListener("touchmove", function (e) {
        e.preventDefault();
        let centerX = img.offsetLeft + img.width / 2;
        let centerY = img.offsetTop + img.height / 2;
        let deltaX = e.touches[0].clientX - initialX;
        let deltaY = e.touches[0].clientY - initialY;
        img.style.left = centerX + deltaX - img.width / 2 + "px";
        img.style.top = centerY + deltaY - img.height / 2 + "px";
    });
    document.body.appendChild(d);
    d.appendChild(e);
    e.appendChild(f);
    f.appendChild(img);
}
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%);
  touch-action: none;
  /* Disable browser touch gestures */
}

2
Utmost Creator On

You can see the answer for previous question here - Problem trying to centre image with touch events, and read some explanation if you need.

I have combined part of that code into this solution and here I needed to consider and taking into account the new classes and layout (in particular .row as its position different for each image. (.row class is calculated for each image to start the reference point from this position)

Please keep in mind that iframe problem is still here. Locally or on some sandboxes it works good as you can see from https://5t39n2.csb.app/ link.

<!-- https://stackoverflow.com/questions/77524882/keeping-image-centred-during-drag-touch-events# -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width" ="device-width,user-scalable" ="no" />
    <meta charset="UTF-8" />
    <style>
      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%);
      }
    </style>
  </head>

  <body>
    <script>
      let d = document.createElement("div");
      d.setAttribute("class", "chessboard");
      document.body.append(d);

      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("img");
        img.classList.add("img");
        img.src =
          "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png";

        img.addEventListener("touchstart", function (e) {
          e.preventDefault();
          let rowRect = this.parentNode.parentNode.getBoundingClientRect(); // Get the parent .row element's rect
          let rect = img.getBoundingClientRect();
          let imgCenterX = rect.width / 2;
          let imgCenterY = rect.height / 2;
          img.offsetX =
            e.touches[0].clientX - rect.left + rowRect.left - imgCenterX;
          img.offsetY =
            e.touches[0].clientY - rect.top + rowRect.top - imgCenterY;
          img.style.zIndex = 1;
        });

        img.addEventListener("touchmove", function (e) {
          e.preventDefault();
          let touchX = e.touches[0].clientX;
          let touchY = e.touches[0].clientY;
          img.style.left = touchX - img.offsetX + "px";
          img.style.top = touchY - img.offsetY + "px";
        });

        d.appendChild(e);
        e.appendChild(f);
        f.appendChild(img);
      }
    </script>
  </body>
</html>

<!DOCTYPE html>
<html lang="en">

<head>
  <meta name="viewport" content="width"="device-width,user-scalable"="no" />
  <meta charset="UTF-8" />
  <style>
    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%);
    }
  </style>
</head>

<body>
  <script>
    let d = document.createElement("div");
    d.setAttribute("class", "chessboard");
    document.body.append(d);

    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("img");
      img.classList.add("img");
      img.src =
        "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Location_dot_blue.svg/1200px-Location_dot_blue.svg.png";

      img.addEventListener("touchstart", function(e) {
        e.preventDefault();
        let rowRect = this.parentNode.parentNode.getBoundingClientRect(); // Get the parent .row element's rect
        let rect = img.getBoundingClientRect();
        let imgCenterX = rect.width / 2;
        let imgCenterY = rect.height / 2;
        img.offsetX =
          e.touches[0].clientX - rect.left + rowRect.left - imgCenterX;
        img.offsetY =
          e.touches[0].clientY - rect.top + rowRect.top - imgCenterY;
        img.style.zIndex = 1;
      });

      img.addEventListener("touchmove", function(e) {
        e.preventDefault();
        let touchX = e.touches[0].clientX;
        let touchY = e.touches[0].clientY;
        img.style.left = touchX - img.offsetX + "px";
        img.style.top = touchY - img.offsetY + "px";
      });

      d.appendChild(e);
      e.appendChild(f);
      f.appendChild(img);
    }
  </script>
</body>

</html>