33

I currently have a div structured with other elements inside of it.

Something similar to below;

<div id="container" style="position: relative; width: 500px; height: 500px;">
     <div style="position: absolute; left: 50px; top: 50px;"></div>
     <div style="position: absolute; left: 100px; top: 100px;"></div>
</div>

I am trying to get the mouse position relative to the div with the id container.

So far I have this;

function onMousemove(event) {

    x = event.offsetX;
    y = event.offsetY;
};

var elem = document.getElementById("container");
elem.addEventListener("mousemove", onMousemove, false);

This works fine if the div with the id container has no children. When the container div has children it gets the mouse co-ordinates relative to the child rather than the parent.

What I mean by this is if the mouse was at a position of x: 51, y: 51 relative to the parent div, it would actually return x: 1, y: 1 relative to the child div, using the html given above.

How can I achieve what I want, no libraries please.

EDIT

tymeJV has kindly made a jsfiddle of what is happening above.

http://jsfiddle.net/N6PJu/1

samrap
  • 5,595
  • 5
  • 31
  • 56
GriffLab
  • 2,076
  • 3
  • 20
  • 21

6 Answers6

91

The accepted answer didn't work for me in Chrome. Here's how I solved it:

function relativeCoords ( event ) {
  var bounds = event.target.getBoundingClientRect();
  var x = event.clientX - bounds.left;
  var y = event.clientY - bounds.top;
  return {x: x, y: y};
}
Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169
  • 4
    Very elegant solution. To answer the OP's question you just need to swap `event.target` for `document.getElementById("container")`. – homerjam Apr 05 '16 at 13:27
  • to whomever this may concern, the JSFiddle demo of the accepted answer (http://jsfiddle.net/N6PJu/3/) currently does work on Chrome. – Ezra Steinmetz Nov 16 '18 at 00:11
  • This worked well and does not give wrong coordinate in case of container is translated! – SWIK Dec 02 '20 at 08:16
  • 2
    It should be noted that `getBoundingClientRect` is expensive because it causes [layout thrashing](https://gist.github.com/paulirish/5d52fb081b3570c81e3a). – bluenote10 Jan 06 '21 at 20:07
9

In essence: 'mouseposition' - 'parent element position' = 'mouseposition relative to parent element'

So here I modified your function:

function onMousemove(e){
    var m_posx = 0, m_posy = 0, e_posx = 0, e_posy = 0,
           obj = this;
    //get mouse position on document crossbrowser
    if (!e){e = window.event;}
    if (e.pageX || e.pageY){
        m_posx = e.pageX;
        m_posy = e.pageY;
    } else if (e.clientX || e.clientY){
        m_posx = e.clientX + document.body.scrollLeft
                 + document.documentElement.scrollLeft;
        m_posy = e.clientY + document.body.scrollTop
                 + document.documentElement.scrollTop;
    }
    //get parent element position in document
    if (obj.offsetParent){
        do { 
            e_posx += obj.offsetLeft;
            e_posy += obj.offsetTop;
        } while (obj = obj.offsetParent);
    }
    // mouse position minus elm position is mouseposition relative to element:
    dbg.innerHTML = ' X Position: ' + (m_posx-e_posx) 
                  + ' Y Position: ' + (m_posy-e_posy);
}

var elem = document.getElementById('container');
elem.addEventListener('mousemove', onMousemove, false);

var dbg=document.getElementById('dbg');  //debut output div instead of console

Here is a working demo fiddle. As you can read in the code, this also looks at the document's scroll position.

PPK's article on 'event properties' and 'find position' are (as always) a good read!

Hope this helps!

Update:
I actually found an error in ppk's code (how rare is that?!): I corrected the erroneous var in:
if (!e) var e = window.event; to if (!e){e = window.event;}

GitaarLAB
  • 14,536
  • 11
  • 60
  • 80
  • I'm sure the topic poster appreciates the effort (and I do as well) but I hope this doesn't take away from 'search&find' as opposed to 'copy,use&don't understand'. Act responsibly here, topic poster ;-) – Willem Mulder Apr 22 '13 at 20:30
  • 1
    @WillemMulder: Thx!     Full explanation for the code is supplied in the PPK links. It explains the principle and proves it *and* (for who reads) points out all common pitfalls. However, I generally do agree: it's better to teach 'm how to fish. But in this case it would require a chapter from a book to point out all those (cross-browser) pitfalls. By the way, we also *don't know if* the asker (or any other future reader) will or will not take the time to understand the code. – GitaarLAB Apr 22 '13 at 20:39
  • @WillemMulder I don't copy paste only learn and write it myself :) – GriffLab Apr 23 '13 at 17:25
  • Thanks. Full explained, work and I upvoted. But there is a little problem. If container is translated to the percentage. There is the offset in the result – SWIK Dec 02 '20 at 08:11
8

Although the answer of Nikita Volkov is pretty solid, it might not work as expected in some cases.

I recommend using currentTarget instead of target:

const handleEvent = (event) => 
{
  const bounds = event.currentTarget.getBoundingClientRect();
  const x = event.clientX - bounds.left;
  const y = event.clientY - bounds.top;
  // Now x and y are the relative coordinates.
}

Note that:

  • event.target is the element that triggered the event (e.g., the user clicked on or hovered on).
  • event.currentTarget is the element that the event listener is attached to.

It makes the difference when you want to attach a listener to some element that has multiple children.

For example,

<div onMouseMove={handleEvent} id="div0">
  <div style={{'height': 200, 'width': 200}} id="div1">
  </div>
  <div style={{'height': 200, 'width': 200}} id="div2">
  </div>
</div>

If you use event.target and hover your mouse over div2, you will get relative coordinates regarding div2 while you might want them to be regarding div0. Then event.currentTarget will do the trick.

pyloolex
  • 182
  • 1
  • 7
2

Html

<div id="container" style="position: relative; width: 500px; height: 500px;">
<div style="position: absolute; left: 50px; top: 50px;"></div>
<div style="position: absolute; left: 100px; top: 100px;"></div>
</div>

Javascript

function onMousemove(event) {
    x = event.layerX; // position x relative to div
    y = event.layerY; // position y relative to div 
};

var elem = document.getElementById("container");
elem.addEventListener("mousemove", onMousemove, false);
Le Fem
  • 99
  • 1
  • 11
  • 3
    "[This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/layerX)" – mindplay.dk Oct 04 '17 at 11:56
1

Get the screenX and screenY properties to see where the mouse is on the screen (and possibly take the scrollHeight into account!).

Then get the left and top of the container element and calculate the offset between them: that is the position of the mouse in the element.

See https://developer.mozilla.org/en-US/docs/DOM/MouseEvent for more details.

Willem Mulder
  • 12,974
  • 3
  • 37
  • 62
1

With functional components, you can combine useRef with a click event listener (rather than needing to reference the DOM directly):

const MyElement = () => {
  const ref = useRef(null);

  useEffect(() => {
    const currentRef = ref.current;
    if (currentRef) {
      const clickListener = (e) => {
        const rightOfElement = currentRef.getBoundingClientRect().right;
        const leftOfElement = currentRef.getBoundingClientRect().left;
        const mousePosXRelativeToElement = e?.clientX ?? 0;

        const mousePosX = rightOfElement - mousePosXRelativeToElement;
        const fullWidth = rightOfElement - leftOfElement;

        // do something, e.g. changing state based on position
        if (mousePosX / fullWidth < 0.5) {
          setTheme(2);
        } else {
          setTheme(1);
        }
      };
      currentRef.addEventListener("click", clickListener);
      return () => {
        currentRef.removeEventListener("click", clickListener);
      };
    }
  }, [setTheme]);

  return (
    <div className="theme-selector-wrapper">
      <div className="theme-selector" ref={ref}></div>
    </div>
  );
};

Inside useEffect (i.e. once the element is loaded), add an event listener to the element assigned to the ref element which listens for clicks. In this example, I've changed "theme" based on the ratio of X position to the width of the element.

strider
  • 5,674
  • 4
  • 24
  • 29
James Whiteley
  • 3,363
  • 1
  • 19
  • 46