document.elementFromPoint

先说一下这个方法的参数

elemntFromPoint(x,y);//传入坐标值,返回当前页面上包含该坐标点的顶层元素

注意2点,坐标值和顶层元素

先说坐标,因为不同的人理解是不一样的,也就造就了这个方法在不同的浏览器中表现是不一样的,所以在传入坐标时就分 整体页面坐标 和 可视区域坐标,我们看上篇文章中的图来理解下:
document.elementFromPoint
中间的方块是可视区域,红点相对可视区域的左上角我们称之为 clientX和clientY,相对于页面起始处的左上角称之为 pageX和pageY

有的浏览器在调用elementFromPoint时要求传入clientX和clientY而有的要求pageX和pageY,具体的详见:http://www.quirksmode.org/webkit.html 看 elementFromPoint部分,上面也提到如果在标准中进行规则统一,也会规定使用可视区域的坐标

显然我们如果要用这个方法时,就要注意兼容了,而对于同一种浏览器,因为版本的不同,导致同样的这个方法可能要求传入的坐标也不同,比如chrome浏览器,那么,我们该如何去兼容呢?

The w3c specification says:

The elementFromPoint(x, y) method, when invoked, must return the element at coordinates x,y in the viewport. The element to be returned is determined through hit testing. If either argument is negative, x is greater than the viewport width excluding the size of a rendered scroll bar (if any), or y is greater than the viewport height excluding the size of a rendered scroll bar (if any), the method must return null. If there is no element at the given position the method must return the root element, if any, or null otherwise.

调用elementFromPoint时,必须传入可视区域内的坐标,如果你传入的坐标不在可视区域内,即使在这个坐标处有元素,也将返回null;我们可以根据这个特性来写兼容代码:

使用jq实现的代码如下,我们可以很方便的改写成自已的

(function($) {
    var check = false,
        isRelative = true;
    $.elementFromPoint = function(x, y) {
        if (!document.elementFromPoint) return null;
        if (!check) {
            var sl;
            if ((sl = $(document).scrollTop()) > 0) {
                isRelative = (document.elementFromPoint(0, sl + $(window).height() - 1) == null);
            } else if ((sl = $(document).scrollLeft()) > 0) {
                isRelative = (document.elementFromPoint(sl + $(window).width() - 1, 0) == null);
            }
            check = (sl > 0);
        }
        if (!isRelative) {
            x += $(document).scrollLeft();
            y += $(document).scrollTop();
        }
        return document.elementFromPoint(x, y);
    }
})(jQuery);

原理就是上面说的,如果页面有滚动,则尝试获取页面坐标最边缘处的元素,如果能获取到,说明是使用页面坐标,因为在有滚动的情况下,获取可视边缘处的坐标,页面坐标会大于可视区域的坐标,所以如果是用可视区域坐标,肯定返回null

嗯,一切看起来都还不错,IE6 7就不行了,仔细看了下这个方法,这个方法最早应该是IE特有的,最后被其它浏览器实现,在IE下,一直使用的是可视区域的坐标,但是在IE6 7的情况下,当你传大于可视区域的坐标时,也是可以获取到值的,也就造成了上面兼容代码无法在IE6 7下正常工作,所以上面的代码并没有考虑IE6 7的情况,需要你添加一些判断。

除了坐标问题外,该方法只能返回顶层元素,也就是在有2个元素重叠的情况下,只能返回最上层的元素。

之前我也有写过高效拖动的文章,比如拖动排序分类,常见的是鼠标在拖动的时候,不停的计算鼠标是在哪个分类上面,然后做出变换的效果,如果列表元素比较少的情况下还是可以的,如果大于1000个,而这些分类的高度不定,通过这个循环判断的方法显示就会觉得很卡

我在之前的文章中提到可以在mousemove时,不让任何元素挡着鼠标(通常我们可能会在拖动时,让被拖动的元素随鼠标一起移动,这时候元素可能挡在鼠标的下方),可通过事件的event.target获取鼠标指向的元素,这样列表中有多少个元素都不会影响到效果

回头说一下setCapture方法,该方法是IE下让元素捕获到事件,比如鼠标移到浏览器外边也可以响应得到,然而这个方法在firefox在4版本时被引进firefox中,引入后如果拖动时你调用了setCapture,就算鼠标下面没有东西挡着,event.target也始终是鼠标按下时的target,IE中则不是,调用setCapture后,mousemove时依然可以获取到,当然IE下可能要用event.srcElement获取

所以,保险起见,我们要调用setCapture,而调用后,firefox4以后又不能及时获取到鼠标下的元素,抛开这些,在ios设备上,touchmouse时,同样也是获取不到手指指向的元素,所以只好回头折腾这个elementFromPoint了。

在折腾这些的时候,还发现诸如getBoundingClientRect在ios设备上也是有问题的,这个方法正常返回相对可视区域的坐标,而在ios上,实现的却是相对页面的坐标,实在让人郁闷

回头再看一下兼容,之前做浏览器间的兼容,更多的是这个浏览器有这个方法,那个浏览器有那个方法,方法的不同而已,现在的兼容则是同样的方法,最终的结果不同,比如setCapture还有getBoundingClientRect等,可能还有其它一些有问题的方法,这种兼容起来更麻烦,远不如没有方法来的干脆些。

上一篇:JavaScript的语法要点 4 - 面向对象的基础


下一篇:无障碍树[转]