The webkitConvertPointFromPageToNode(in Node node, in WebKitPoint p) method is awesome; give it a DOM node and a point in page-coordinates (say, the mouse cursor position) and it will give a coordinate back to you in that node's local coordinate system. Unfortunately, it's currently only available in webkit.

# Choose a node into which we'll map the mouse coordinates
node = $('#subjectElement').get(0)

handleMouseMove = (e) ->
  # Convert the mouse position to a Point
  mousePoint = new WebKitPoint(e.pageX, e.pageY)

  # Convert the mouse point into node coordinates using WebKit
  nodeCoords = webkitConvertPointFromPageToNode(node, mousePoint)

# Attach a handler to track the mouse position
$(document).on 'mousemove', handleMouseMove    

I've thrown my entire math-brain at the problem, but no matter how close I get, my implementation falls apart with one extra level of composition, or the application of 3D perspective.

It's time for a convertPointFromPageToNode polyfill that works as well as the WebKit implementation, in 3D. @4esn0k gave one a shot, but it only solves the 2D case.

Can you write one that makes this JSFiddle work?

webkitConvertPointFromPageToNode kicking ass and taking names on a complicatedly transformed element

http://jsfiddle.net/steveluscher/rA27K/

有帮助吗?

解决方案

This seems like an amazing question, but there is an ALMOST duplicate right here: How to get the MouseEvent coordinates for an element that has CSS3 Transform? but nobody is looking at my answer there and this seems to be much more general so I'll post it again here, with a few modifications to make it more clear:

Basically, it works by doing this: split the element you are trying to find relative coordinates for, and split it into 9 smaller elements. Use document.elementFromPoint to find if the coordinate is over that mini-element. If it is, split that element into 9 more elements, and keep doing this until a pretty accurate coordinate is possible. Then use getBoundingClientRect to find the on-screen coordinates of that mini-element. BOOM!

jsfiddle: http://jsfiddle.net/markasoftware/rA27K/8/

Here is the JavaScript function:

function convertPointFromPageToNode(elt,coords){
    ///the original innerHTML of the element
    var origHTML=elt.innerHTML;
    //now clear it
    elt.innerHTML='';
    //now save and clear bad styles
    var origPadding=elt.style.padding=='0px'?'':elt.style.padding;
    var origMargin=elt.style.margin=='0px'?'':elt.style.margin;
    elt.style.padding=0;
    elt.style.margin=0;
    //make sure the event is in the element given
    if(document.elementFromPoint(coords.x,coords.y)!==elt){
        //reset the element
        elt.innerHTML=origHTML;
        //and styles
        elt.style.padding=origPadding;
        elt.style.margin=origMargin;
        //we've got nothing to show, so return null
        return null;
    }
    //array of all places for rects
    var rectPlaces=['topleft','topcenter','topright','centerleft','centercenter','centerright','bottomleft','bottomcenter','bottomright'];
    //function that adds 9 rects to element
    function addChildren(elt){
        //loop through all places for rects
        rectPlaces.forEach(function(curRect){
            //create the element for this rect
            var curElt=document.createElement('div');
            //add class and id
            curElt.setAttribute('class','offsetrect');
            curElt.setAttribute('id',curRect+'offset');
            //add it to element
            elt.appendChild(curElt);
        });
        //get the element form point and its styling
        var eltFromPoint=document.elementFromPoint(coords.x,coords.y);
        var eltFromPointStyle=getComputedStyle(eltFromPoint);
        //Either return the element smaller than 1 pixel that the event was in, or recurse until we do find it, and return the result of the recursement
        return Math.max(parseFloat(eltFromPointStyle.getPropertyValue('height')),parseFloat(eltFromPointStyle.getPropertyValue('width')))<=1?eltFromPoint:addChildren(eltFromPoint);
    }
    //this is the innermost element
    var correctElt=addChildren(elt);
    //find the element's top and left value by going through all of its parents and adding up the values, as top and left are relative to the parent but we want relative to teh wall
    for(var curElt=correctElt,correctTop=0,correctLeft=0;curElt!==elt;curElt=curElt.parentNode){
        //get the style for the current element
        var curEltStyle=getComputedStyle(curElt);
        //add the top and left for the current element to the total
        correctTop+=parseFloat(curEltStyle.getPropertyValue('top'));
        correctLeft+=parseFloat(curEltStyle.getPropertyValue('left'));
    }
    //reset the element
    elt.innerHTML=origHTML;
    //restore element styles
    elt.style.padding=origPadding;
    elt.style.margin=origMargin;
    //the returned object
    var returnObj={
        x: correctLeft,
        y: correctTop
    }
    return returnObj;
}

IMPORTANT!!! You must also include this CSS for it to work:

.offsetrect{
    position: absolute;
    opacity: 0;
    height: 33.333%;
    width: 33.333%;
}
#topleftoffset{
    top: 0;
    left: 0;
}
#topcenteroffset{
    top: 0;
    left: 33.333%;
}
#toprightoffset{
    top: 0;
    left: 66.666%;
}
#centerleftoffset{
    top: 33.333%;
    left: 0;
}
#centercenteroffset{
    top: 33.333%;
    left: 33.333%;
}
#centerrightoffset{
    top: 33.333%;
    left: 66.666%;
}
#bottomleftoffset{
    top: 66.666%;
    left: 0;
}
#bottomcenteroffset{
    top: 66.666%;
    left: 33.333%;
}
#bottomrightoffset{
    top: 66.666%;
    left: 66.666%;
}

ALSO: I modified a little of your css by giving the "grandfather" div an id and referencing to it in your css using #div1 instead of div because my code generates divs, and your div styles were also applying to the ones my code uses and messed it up

ONE LAST THING: I don't know CoffeeScript so I adjusted your code to make it pure JavaScript. Sorry about that.

其他提示

I have written some TypeScript code which does some transformations: jsidea core library. Its not stable yet (pre alpha). You can use it like that:

Create you transform instance:

var transformer = jsidea.geom.Transform.create(yourElement);

The box model you want to transform to (default:"border"):

var toBoxModel = "border";

The box model where your input coordinates coming from (default:"border"):

var fromBoxModel = "border";

Tranform your global coordinates (here {x:50, y:100, z: 0}) to local space. The resulting point has 4 components: x, y, z and w.

var local = transformer.globalToLocal(50, 100, 0, toBoxModel, fromBoxModel);

I have implemented some other functions like localToGlobal and localToLocal. If you want to give a try, just download the release build and use the jsidea.min.js.

I have this issue and started trying to compute the matrix. I started a library around it: https://github.com/ombr/referentiel

$('.referentiel').each ->
  ref = new Referentiel(this)
  $(this).on 'click', (e)->
    input = [e.pageX, e.pageY]
    p = ref.global_to_local(input)
    $pointer = $('.pointer', this)
    $pointer.css('left', p[0]-1)
    $pointer.css('top', p[1]-1)

What do you think ?

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top