Search code examples
javascriptreactjstouch-event

javascript. how to get the exact touch position on an image


I have an image. for this i would like to get the touch events start, move and end. I need the exact image-pixel of the touch.

The Problem is that the actual image is smaller than the HTMLImageElement. (css style object-fit: scale-down) important image explanation

I don't need the touch events for the HTMLImageElement but the actual image itself.

How do I archive this?

btw. I use React (if this helps) but I can (hopefully) adapt the answer myself.

Current Attempt:

<img ...
onTouchStart={(event) => {
  const touch = event.changedTouches[0];
  const rect = event.currentTarget.getBoundingClientRect();
  const pxlX = touch.screenX - rect.left;
  const pxlY = touch.screenY - rect.top;
  // other stuff with (pxlX, pxlY)
}} />

My Solution in the end

import { useState } from "react";
import ImageSrc from "./assets/image.png";


export default function App() {
    const [text, setText] = useState("undefined");

    return <div className="w-screen h-screen overflow-hidden">
        <img src={ImageSrc} alt="" className="object-scale-down w-full h-full" onMouseMove={(event) => {
            const {x, y} = getXY(event);
            setText(`${x}/${y}`);
        }} />
        <span className="fixed top-0 left-0 pointer-events-none">{text}</span>
    </div>;
}

function getXY(event: React.MouseEvent<HTMLImageElement, MouseEvent>): {x: number, y: number} {
    const img = event.currentTarget;
    const [imgWidth, imgHeight] = [img.naturalWidth, img.naturalHeight];
    const [boxWidth, boxHeight] = [img.width, img.height];
    const imgRatio = imgWidth / imgHeight;
    const boxRatio = boxWidth / boxHeight;
    if (imgRatio < boxRatio) {
        const x0 = (boxWidth - boxHeight * imgWidth/imgHeight) / 2;
        const x = Math.round(event.nativeEvent.offsetX - x0);
        return {x: x, y: event.nativeEvent.offsetY};
    } else {
        const y0 = (boxHeight - boxWidth * imgHeight/imgWidth) / 2;
        const y = Math.round(event.nativeEvent.offsetY - y0);
        return {x: event.nativeEvent.offsetX, y: y};
    }
}

Solution

  • Use img.naturalHeight and img.naturalWidth to determine the position at which the effective image starts.

    Briefly, the following quantity will be negative if the Y of the event is relative to a point above the image, so capture it in your event and check:

    y0 = (400 - 400*mainImg.naturalHeight/mainImg.naturalWidth)/2;
    var effectiveY = event.offsetY - y0;
    

    A little math is involved here, feel free to ask if you need a general description when the image can be a portrait, or if you still need help detecting events below the image.

    var y0;
    
    window.addEventListener("load", function(ev) {
      y0 = (400 - 400*mainImg.naturalHeight/mainImg.naturalWidth)/2;
    });
    
    mainImg.addEventListener("mousemove", function(ev) {
      infoDiv.innerText = ev.offsetY - y0;
    });
        img {
            object-fit: scale-down;
            border: 1px solid #AAA;
        }
    effectiveY is: 
    <span id="infoDiv"></span>
    <br>
    <img id="mainImg" src="https://st.hzcdn.com/simgs/pictures/gardens/garden-with-oval-lawns-fenton-roberts-garden-design-img~1971dc9307c80c73_4-8900-1-60a5647.jpg" width=400 height=400>