29

I'm storing click coordinates in my db and then reloading them later and showing them on the site where the click happened, how do I make sure it loads in the same place?

Storing the click coordinates is obviously the simple step, but once I have them if the user comes back and their window is smaller or larger the coordinates are wrong. Am I going about this in the wrong way, should I also store an element id/dom reference or something of that nature.

Also, this script will be run over many different websites with more than one layout. Is there a way to do this where the layout is independent of how the coordinates are stored?

Moiz Sohail
  • 558
  • 5
  • 22
Tomas
  • 3,054
  • 5
  • 27
  • 39
  • 1
    As a stop gap solution, store the Screen width/height, and resize the browser to that when replaying the clicks. – Jordan S. Jones Apr 13 '10 at 17:51
  • You're going about it the wrong way. Depending on your use case, maybe save screenshot together with coordinates, or page's state and the element you're clicking on somehow. – ivan_pozdeev Jun 01 '18 at 03:02

6 Answers6

86

Yeah, there are many, many ways a page's layout can alter between loads. Different window sizes, different font sizes, different font availability, different browser/settings (even a small change in layout or font preference can throw out the wrapping). Storing page-relative co-ordinates is unlikely to be that useful unless your page is almost entirely fixed-size images.

You could try looking up the ancestors of the clicked element to find the nearest easily-identifiable one, then make a plot from that element down to the element you want based on which child number it is.

Example using simple XPath syntax:

document.onclick= function(event) {
    if (event===undefined) event= window.event;                     // IE hack
    var target= 'target' in event? event.target : event.srcElement; // another IE hack

    var root= document.compatMode==='CSS1Compat'? document.documentElement : document.body;
    var mxy= [event.clientX+root.scrollLeft, event.clientY+root.scrollTop];

    var path= getPathTo(target);
    var txy= getPageXY(target);
    alert('Clicked element '+path+' offset '+(mxy[0]-txy[0])+', '+(mxy[1]-txy[1]));
}

function getPathTo(element) {
    if (element.id!=='')
        return 'id("'+element.id+'")';
    if (element===document.body)
        return element.tagName;

    var ix= 0;
    var siblings= element.parentNode.childNodes;
    for (var i= 0; i<siblings.length; i++) {
        var sibling= siblings[i];
        if (sibling===element)
            return getPathTo(element.parentNode)+'/'+element.tagName+'['+(ix+1)+']';
        if (sibling.nodeType===1 && sibling.tagName===element.tagName)
            ix++;
    }
}

function getPageXY(element) {
    var x= 0, y= 0;
    while (element) {
        x+= element.offsetLeft;
        y+= element.offsetTop;
        element= element.offsetParent;
    }
    return [x, y];
}

You can see it in action using this JSFiddle: http://jsfiddle.net/luisperezphd/L8pXL/

Luis Perez
  • 27,650
  • 10
  • 79
  • 80
bobince
  • 528,062
  • 107
  • 651
  • 834
  • Thanks guys. I think I may just have to store the skeleton of the layout in note form (e.g. ["Layout": "centered"; "Width": "960"] or ["Layout": "Fluid"; "Column1": "25%"; "Column2": "60%"; "Column3": "15%"]) I might try to dissect one of these heat map scripts to see how they log them and always display them right. Jordan might be right, may have to resize the browser to a particular size or something. That just gets messy though. – Tomas Apr 14 '10 at 01:35
  • Thank you! For me I used this function on an element from inside an iframe. So `document` was the parent window, but the element was from the iframe window. Therefore I replaced `document` with `element.getRootNode()`. – Nate Anderson Apr 18 '23 at 01:58
20

I prefer not using the id selector and just going recursive.

function getPathTo(element) {
    if (element.tagName == 'HTML')
        return '/HTML[1]';
    if (element===document.body)
        return '/HTML[1]/BODY[1]';

    var ix= 0;
    var siblings= element.parentNode.childNodes;
    for (var i= 0; i<siblings.length; i++) {
        var sibling= siblings[i];
        if (sibling===element)
            return getPathTo(element.parentNode)+'/'+element.tagName+'['+(ix+1)+']';
        if (sibling.nodeType===1 && sibling.tagName===element.tagName)
            ix++;
    }
}
Scott Izu
  • 2,229
  • 25
  • 12
1

Your coordinates should be relative to the page contents, not the window. Use the upper-left of your HTML as the origin.

You will need to do a calculation to determine this at the time the data is recorded.

Diodeus - James MacFarlane
  • 112,730
  • 33
  • 157
  • 176
  • I don't see why this was voted down. It's a logical approach. Record the page width and the current x:y using the upper-left hand corner as 0:0, then recalculate based on the width of the screen when it's retrieved. Same thing that Jordan recommended in the OP. – Adrian J. Moreno Apr 13 '10 at 18:21
  • Ok, this is the most promising post so far Diodeus. However, how do you go about getting the page width accurately and where is 0,0. If this is a left aligned design it's easy, but if it's centered or fluid? – Tomas Apr 14 '10 at 03:00
  • You need some "anchor div" that is in the top corner of your design. That becomes your new "0,0". Calclulate all points relative to this coordinate. – Diodeus - James MacFarlane Apr 14 '10 at 12:34
  • Ok, this does not take in to account the layout. It's a fair effort but it's a single case fix only. If you have a layout that has 3 columns (25%, 50%, 25%) and the window is 1000px wide and the user clicked at 100, 100 and then the user comes back and their screen is 500px wide the click coordinates are wrong. It would show at 100, 100 instead of 50, 100 where it should in relation to the layout. I believe the only way this can be accomplished is to setup the site layout as params and record the coordinates in relation to that. – Tomas Apr 14 '10 at 17:44
  • Sure it does. The anchor point is the top-left of your HTML, not the BROWSER WINDOW. Regardless of where your page rests within the viewport, you use the upper-left then record all coordinates relative to this point. You need to calculate the new origin point when you both record the data, and when you play it back, based on the position of your page in the browser window. – Diodeus - James MacFarlane Apr 14 '10 at 17:55
  • Umm, maybe I'm missing something here. If I have that 3 column layout and I put this anchor in the top-left of the first column the click will be relative to that 100,100 and when the browser is resized that column will be narrower and therefore the point will no longer be 100, 100. I'd have to store that it happened within that column and figure the % as the relative point. I'd have to see a bit more before I can agree with this. – Tomas Apr 14 '10 at 18:03
  • Yes, In the case if a fluid layout you would also need to record the the overall page width as well. – Diodeus - James MacFarlane Apr 14 '10 at 19:12
1

It probably depands on the meaning of the click. That is, are you concerned about which elements of the page that you user clicked on? If that is the case then I would store the coordinates relative to the element.

So the user clicked on element X. Do you need the precise location in element X? Then store that coordinate with the origin at top left of the element itself. This way, when the element moves relative to other content on the page then the position within the element remains valid.

Vincent Ramdhanie
  • 102,349
  • 23
  • 137
  • 192
1

I was hoping that someone had a much more brilliant solution to this problem, but my original thoughts must be the only way to effectively do this.

  1. Each website must have a base setup described (e.g. [Centered layout, 960px] or [Fluid layout, Col1: 25%, Col2: 60%, Col3: 15%]
  2. Click coordiantes must be recorded in relation to the screen:x/scroll:y along with screen coordinates.
  3. On return the click coords will look at the stored layout, current screen size and calculate based on that.
Tomas
  • 3,054
  • 5
  • 27
  • 39
0

I'm doing something similar here where I need to record where an element was drag and dropped on the page. I can store some data of the drop location in a database, in order to pull it out and place the element back where it was dropped. The only requirement is that I want the dropped element to be as close as possible to the element on which it was dropped, on all screen sizes.

Due to the responsive nature of the modern web, elements can move to completely different locations depending on screen size.

My solution is to ignore all DOM selectors, and instead simply record where the element is in the DOM tree by recording a child index on every 'layer' of the DOM, all the way down to to the element in question.

I do this by traversing up the DOM tree from the event.target element, looking at currentNode.parentNode.children to find which child index my currentNode inhabits. I record that in an array, which I can then use to index all the way back down the DOM tree to find my element. I also save the dropped elements offset as a percentage, in case the dropzone element has changed pixel size.

Here's my cut down code:

    var rect = mouseEvent.target.getBoundingClientRect()
    // get position of mouseEvent in target as a percentage so we can be ok if it changes size
    var xpos = Math.floor(mouseEvent.offsetX / rect.width * 100)
    var ypos = Math.floor(mouseEvent.offsetY / rect.height * 100)
    
    // traverse backwards up the dom tree, recording this 'branch' of it as we go:
    var curEl = mouseEvent.target
    var tree = []
    while(curEl.parentNode){
        for( var i = 0; i < curEl.parentNode.children.length; i ++ ){
            var curChild = curEl.parentNode.children[i]
            if( curChild === curEl ){ // i is our child index
                tree.unshift(i) // unshift to push to the front of the array
                break
            }
        }
        curEl = curEl.parentNode
    }
    

And then in order to find my node again, I simply traverse back down the dom:

    var curEl = document
    for(var i = 0; i < tree.length; i ++){
        curEl = curEl.children[tree[i]]
    }

All I save to the database is the tree array (a flat array of integers - how can you get smaller?) and my x and y offsets!

Abraham Brookes
  • 1,720
  • 1
  • 17
  • 32