弹出控件的位置该如何计算?

      近段时间在写组件,页面有一个输入框,点击输入框,弹出一个国家控件或者是城市控件。但是这个控件的位置该如何放一直是一个头疼的问题。可能是开始没有沉下心来想,总觉得这是个技术难题,还在网上和群里 向很多高手请教,他们给出的答案也并不是我想要的。最后不得不自己想想该如何解决这个问题了,现在把自己的一些思考写出来,一来防止忘记,二来也算个分享。

思路:

前提条件:只能拿到一个输入框对象。

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上去了。结果和上面一样的。

最后结论:最初的方法应该就是正确的方法。所以有时候真的没有必要把问题想的太复杂了,否则进入死巷子出不来。

原文地址:https://www.cnblogs.com/tengri/p/5668347.html