近段时间在写组件,页面有一个输入框,点击输入框,弹出一个国家控件或者是城市控件。但是这个控件的位置该如何放一直是一个头疼的问题。可能是开始没有沉下心来想,总觉得这是个技术难题,还在网上和群里 向很多高手请教,他们给出的答案也并不是我想要的。最后不得不自己想想该如何解决这个问题了,现在把自己的一些思考写出来,一来防止忘记,二来也算个分享。
思路:
前提条件:只能拿到一个输入框对象。
1、position的取值:absolute、relative和fixed三种。relative是相对定位,很明显不适合,因为在实际操作中,根本不确定input是否有父节点,以及父节点的定位是什么,那么相对定位,相对的对象是不可知的,所以可以排除relative。其次是固定定位fixed,这个也是不可行的。举个简单的例子,如果把控件定位到(100,100)的位置,那么如果有滚动条,该控件不会跟着页面滚动,要是还是不明白,想想博客园页面上回到顶部的功能就应该明白了。
2、经过以上思考,至少可以确定,控件的position一定是absolute了,那么它的left值和top值该怎么计算呢?肯定要先从input输入框入手。
3、由于在开发中用的jquery库,那么首先想到的就是$.offset()方法,查看API得知,offset是用于设置或返回当前匹配元素相对于当前文档的偏移,也就是相对于当前文档的坐标,说白了就是获取当前元素的绝对位置。且该位置是相对于当前文档(页面)的。既然是这样,控件的left就应该是当前元素的left值,控件的top就应该是当前元素的top值加上当前元素的自身高度了。为了验证,立马写了一个例子:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript" src="demo/jquery-1.9.1.js" ></script> <style type="text/css"> .layer{width:400px; height:300px; background:red;position: absolute;} </style> </head> <body style="height:2000px;"> <input type="text" id="input1" placeholder="请选择 " style="margin-top:100px;" onclick="addLayer();"/> </body> </html> <script type="text/javascript"> function addLayer(){ var pointer = $("#input1").offset(); var $div = $("<div class='layer'></div>"); $div.css({left:pointer.left, top:pointer.top+$("#input1").height()}); $("body").append($div); } </script>
运行正常,也不会随着滚动条的变动而变动位置,很不错。
4、然而由于我的整个项目是用的angular的路由来控制的,左边是导航,右边是内容区,发现异常了,控件和input输入框分离了,找了半天原因,也没找到,开始怀疑自己写的控件有问题,亦或计算位置的思路不正确?纠结了半天,没找到问题的原因,最后不得不用那万能的排除法了。
5、怎么排除呢?首先在网上找了一个日历控件(My97 datepicker),引用进来,发现还是存在这个问题。非常开心,看来不是控件写的有问题,那到底是啥原因呢?继续找。。。。。
6、然后各种实验,最后终于找到原因了:滚动条的问题,如果不是body的滚动条或者是iframe的滚动条,就会遇到这个问题。其实沉下心来想想,很容易就想通,因为offset是根据document计算的相对偏移量,虽然input框所在的区域包含滚动条,但是这个滚动条不是属于document的,所以在滚动的时候,input会随着走,而控件始终留在相对于document的位置。另外,input会跟着动,是因为input并不是根据document来定位的。也就是说,我控件的位置是根据input相对于document的偏移量来获取的,但是并不代表input的位置就是按照这个偏移量来设置的。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title></title> 6 <script type="text/javascript" src="demo/jquery-1.9.1.js" ></script> 7 <style type="text/css"> 8 .layer{width:400px; height:300px; background:red;position: absolute;} 9 .wrap{height:500px;overflow: auto;} 10 .inner{height:1000px;} 11 </style> 12 </head> 13 <body style="height:2000px;"> 14 <div class="wrap"> 15 <div class="inner"> 16 <input type="text" id="input1" placeholder="请选择 " style="margin-top:100px;" onclick="addLayer();"/> 17 </div> 18 </div> 19 </body> 20 </html> 21 <script type="text/javascript"> 22 function addLayer(){ 23 var pointer = $("#input1").offset(); 24 var $div = $("<div class='layer'></div>"); 25 $div.css({left:pointer.left, top:pointer.top+$("#input1").height()}); 26 $("body").append($div); 27 } 28 </script>
7、看来这个问题真是没办法解决了,真的没有办法呢?不死心,接着想。。。。。
8、一不小心,又发现了jquery的position()方法,那就用这个方法试试,思路是先用position()方法获取input相对父元素的偏移量,然后给控件设置位置。另外用递归查找input的所有父元素中第一个position为relative的的父元素,把控件append上去。实验表明,如果input的父节点直接是relative的,没有问题,但是要是input的所有父节点都没有,最后还是找到body上去了。结果和上面一样的。
最后结论:最初的方法应该就是正确的方法。所以有时候真的没有必要把问题想的太复杂了,否则进入死巷子出不来。