js获取dom元素长宽之坑

此坑消耗我好久时间,mark一下。

1.需求描述

最近项目需要,要做一个根据鼠标事件位置显示popover的功能效果。要在鼠标点击位置之上,居中显示一个template的div,
因此我需要获得这个div的长宽。

2.遇到的问题

在获得这个div的长宽问题上,经历了一坑再坑的过程。就连chrome本身都有一个诡异的问题要戏耍我一下。
1.对于dom元素、jquery元素容易搞混
由于项目采用的angular,在jquery之上又封装了一层,对于没有系统用过jquery的来说,直接使用angular,很多概念确实容易混得更加厉害。
属性、方法属于原生dom的范畴还是jquery的范畴亦或是angular的范畴这一点就会比较混乱,在使用上也被坑了一下。好在这一点通过阅读各种资料可以克服,
另外,angularjs的官方文档也有说明, angular.element()返回的是jquery对象。

2.在dom对象插入前,无论如何获取不了这个dom对象的长宽的
这点导致我纠结了好久popover代码的显示逻辑

3.在dom对象插入以后,如果是隐藏的元素,通过jquery和dom对象获取的数据不一样

4.chrome调试器输出的jquery对象里面的值是根据你展开这个对象的时候再去获取的
在你打开之前,里面属性的状态和值是不确定的,但当你打开后就锁定了(薛定谔的猫,是你吗?)。
这点着实是大坑啊,在调试的时候这点太坑人了!也不知道算不算是chrome的bug。

起初我的代码是这么写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
scope.showPopover = function (e){
$popover = angular.element(template); // template是一串html文本,这行代码返回一个jquery对象
$compile($popover)(scope); // 使用angular编译器编译
console.log($popover);
console.log($popover.height()); // 输出 0
console.log($popover[0].clientHeight); // 输出 0
$container.append($popover); // container是另外一个jquery对象,用于放popover
var top = e.pageY - $popover.height();
var left = e.pageX - $popover.width(); // 根据鼠标,把弹出框放在合适的位置
console.log($popover);
console.log($popover.height()); // 输出 133
console.log($popover[0].clientHeight); // 输出 0
$popover
.css('top', top + 'px')
.css('left', left + 'px');
$popover.css('display', 'block'); // 因为是popover,所以该窗口由html里面的class控制它默认是不显示的,现在让它显示
console.log($popover);
console.log($popover.height()); // 输出 133
console.log($popover[0].clientHeight); // 输出 133
}

吊诡的问题在于,无论我打开哪个$popover的输出,里面的结构都是这样的:

明显clientHeight有值啊,为啥在display以前输出都是0,这简直就是我定义一个变量为1,它死活就输出0!?(这个问题后文还会涉及到)

然后我把display这一行注释掉:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
scope.showPopover = function (e){
$popover = angular.element(template); // template是一串html文本,这行代码返回一个jquery对象
$compile($popover)(scope); // 使用angular编译器编译
console.log($popover);
console.log($popover.height()); // 输出 0
console.log($popover[0].clientHeight); // 输出 0
$container.append($popover); // container是另外一个jquery对象,用于放popover
var top = e.pageY - $popover.height();
var left = e.pageX - $popover.width(); // 根据鼠标,把弹出框放在合适的位置
console.log($popover);
console.log($popover.height()); // 输出 133
console.log($popover[0].clientHeight); // 输出 0
$popover
.css('top', top + 'px')
.css('left', left + 'px');
//$popover.css('display', 'block'); // 因为是popover,所以该窗口由html里面的class控制它默认是不显示的,现在让它显示
console.log($popover);
console.log($popover.height()); // 输出 133
console.log($popover[0].clientHeight); // 输出 0
}

任意一个popover的输出都是这个样子的:

难道后执行的代码会影响前面已经执行的代码的输出?这点太不可思议了!

最诡异的一个测试用例的代码是这样写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
scope.showPopover = function (e){
$popover = angular.element(template); // template是一串html文本,这行代码返回一个jquery对象
$compile($popover)(scope); // 使用angular编译器编译
console.log($popover);
console.log($popover.height()); // 输出 0
console.log($popover[0].clientHeight); // 输出 0
$container.append($popover); // container是另外一个jquery对象,用于放popover
var top = e.pageY - $popover.height();
var left = e.pageX - $popover.width(); // 根据鼠标,把弹出框放在合适的位置
console.log($popover);
console.log($popover.height()); // 输出 133
console.log($popover[0].clientHeight); // 输出 0
$popover
.css('top', top + 'px')
.css('left', left + 'px');
setTimeout(function (){
$popover.css('display', 'block'); // 因为是popover,所以该窗口由html里面的class控制它默认是不显示的,现在让它显示
console.log($popover);
console.log($popover.height()); // 输出 133
console.log($popover[0].clientHeight); // 输出 133
}, 10000);
}

只要我在10秒内打开这个popover里面的内容,里面将永远展示第二张图的结果
只要我在10秒后打开这个popover里面的内容,里面将永远展示第一张图的结果

看来对于jquery对象这里面的输出难道还是根据你查看的时间定的?

3.总结

1.在dom对象或者是jquery对象还没有插入到页面中时,无论是通过jquery中的height()或者width()方法,
或者直接通过dom对象中的各种height包括(clientHeight, offsetHeight, scrollHeight)都是无法获得它的高度值的
stackoverflow上这个解答印证了这一点
http://stackoverflow.com/questions/7693244/javascript-jquery-how-to-get-the-width-of-an-element-css-class-that-hasnt-b

2.在对象style的display属性为none的作用下,也会一定程度上影响这个jquery对象的长宽取值
具体如下:
2.1 从jquery对象的height()和width()方法可以获取到隐藏元素的长宽
2.2 dom元素上的各种height和width属性的值均为0

如果有我错误或者理解不当的地方,还请各路豪杰在评论区指出!