yii2、百度地图、bootstrap冲突的处理过程
前段时间,因为工作需要,借助百度地图api,写了一个小小的web工具,用于按关键词标注一些地点并展示出来。
解决了前期的关键点,工作完成了七七八八之后,我发现,yii2自带的bootstrap3和百度地图有点点冲突。
具体表现是,yii2自带的layout文件的第一行是 <!doctype html>,百度地图给的demo文件第一行是<html>
如果用yii2的模板,显示百度地图时会显示不正常,具体是不能把它和其它的bootstrap组件放在一起,特别是不能把地图容器放在其它div下面,不然不显示地图。
如果用百度地图的<html>做第一行,bs3的导航菜单显示就会有多余的空白出现,很难看。
(在写这篇文章时,我发现百度地图的iframe的第一行如果用yii2的,也能正常显示,以后再测试吧)
疫情隔离在家,正好有时间,我想好好解决一下这个问题。
我想到的方案是,在yii2的常规页面里,嵌入一个iframe,这个iframe里使用百度地图的显示代码,这样二者就不发生冲突了。
但又出现下面一些问题。
- 主页面如何访问iframe里的元素(主要是map变量)
- iframe里如何访问主页面的元素
- 如何判断iframe已加载完成
- 如何在iframe加载完成后,执行调用页面的自定义代码
- 百度地图标注时,不能显示自定义icon
一、主页面如何访问iframe里的map变量
上网查了解决方案:在显示iframe的主页面里设置全局变量,然后iframe里生成了map对象后,将之保存到这个全局变量里。
(主页面代码)
<script> // 定义全局变量给iframe生成了地图之后传回来 map=null; local=null; </script>
(iframe页面代码)
<script type="text/javascript"> // 百度地图API功能 var map = new BMapGL.Map("container"); map.centerAndZoom(new BMapGL.Point(117.16526, 35.11891), 13); map.enableScrollWheelZoom(true); var scaleCtrl = new BMapGL.ScaleControl(); // 添加比例尺控件 map.addControl(scaleCtrl); var local = new BMapGL.LocalSearch(map, { renderOptions:{map: map} }); map.addEventListener('click', function (e) { // jquery如何获取iframe https://www.muzhuangnet.com/show/53186/2.html $(window.parent.document).find('#coor').val(e.latlng.lng + ',' + e.latlng.lat); // alert('点击位置经纬度:' + e.latlng.lng + ',' + e.latlng.lat); }); //把自己的变量传回父窗口的全局变量 parent.map=map; parent.local=local; </script>
二、iframe里如何访问主页面的元素
有时候,我在iframe里的地图上点击后,需要更新主页面上的元素(填充点击位置的坐标值)。这里还用到了jquery,具体方法是:
map.addEventListener('click', function (e) { // jquery如何获取iframe https://www.muzhuangnet.com/show/53186/2.html $(window.parent.document).find('#coor').val(e.latlng.lng + ',' + e.latlng.lat); // alert('点击位置经纬度:' + e.latlng.lng + ',' + e.latlng.lat); });
三、如何判断iframe已加载完成
主页面上要执行的一些操作,需要在iframe已加载完成,map对象创建成功之后才可以继续执行,不然会出错。
上网查了一下,这里 给出了解决方案。用在我的代码里是这样:
<script> // iframe子页面与父页面元素的访问以及js变量的访问 https://www.cnblogs.com/Capricorn-HCL/articles/4216302.html $(document).ready(function(){ if (can_load_iframe()) load_iframe(); }); function can_load_iframe() { var url = window.location.search; action4=url.substr(3,4); return action4=='site'; } function load_iframe() { var iframe = document.createElement("iframe"); iframe.src = "<?=Url::to(['site/map-frame', 'type'=>0])?>"; if (iframe.attachEvent){ iframe.attachEvent("onload", function(){ console.log("Local iframe is now loaded1."); }); } else { iframe.onload = function(){ frame_onload() }; } document.getElementById('container').appendChild(iframe); h=window.innerHeight - document.getElementById('w0').height - document.getElementById('footer').height; h=h-$("[id^='w2-']").height(); iframe.width='100%'; iframe.height=h; iframe.id='iframe_map'; } function frame_onload() {。。。} </script>
四、如何在iframe加载完成后,执行调用页面的自定义代码
前面实现的是通用的layout,显示地图的地方都会调用它,但是每一个页面的脚本动作各有不同,比如有的页面需要在一开始时标注出若干个地图上的点。
如果在调用者页面上直接使用onload,还是会面临前面的iframe是否已经加载完成的问题,所以我给各页面里增加了一个函数 my_init(),然后在主layout模板中,在iframe的onload事件中,加一个判断,如果函数my_init()存在,则调用之。
这样就解决了不同页面的定制化需求的问题。
五、百度地图标注时,不能显示自定义icon
这个问题在原来的版本中并不存在,而是在引入了iframe之后,我发现,标注的点,都不再显示自定义的icon图标。
说明一下:标注代码是写在iframe的父页面的。
1 <script> 2 function add_point(map, x, y, title1, info, type){ 3 marker=add_marker(map, x, y, type); 4 // console.log("add_point(map, "+x+","+y+",'"+title1+"','"+info+"',"+type+");"); 5 6 // 创建信息窗口 7 var opts = { 8 width: 200, 9 height: 100, 10 title: title1 11 }; 12 var infoWindow = new BMapGL.InfoWindow(info, opts); 13 // 点标记添加点击事件 14 marker.addEventListener('click', function () { 15 map.openInfoWindow(infoWindow, new BMapGL.Point(x,y)); // 开启信息窗口 16 }); 17 18 add_label(map, x, y, title1); 19 } 20 21 function add_marker(map, x, y, type) 22 { 23 marker_url="images/mark"+type+".png"; 24 // console.log(marker_url + ' x=' + x + ' y=' + y + ', type=' + type); 25 var myIcon = new BMapGL.Icon(marker_url, new BMapGL.Size(32, 32), { 26 offset: new BMapGL.Size(10, 25), // 指定定位位置(不然图标会偏下一些) 27 }); 28 console.log(myIcon); 29 if (type) 30 var marker1 = new BMapGL.Marker(new BMapGL.Point(x,y), {icon:myIcon}); 31 else 32 var marker1 = new BMapGL.Marker(new BMapGL.Point(x,y)); 33 // var marker1 = new BMapGL.Marker(new BMapGL.Point(x,y)); 34 // 在地图上添加点标记 35 map.addOverlay(marker1); 36 return marker1; 37 } 38 39 function add_label(map, x, y, msg) 40 { 41 var opts = { 42 position: new BMapGL.Point(x,y), // 指定文本标注所在的地理位置 43 offset: new BMapGL.Size(20, -30) // 设置文本偏移量 44 }; 45 // 创建文本标注对象 46 var label = new BMapGL.Label(msg, opts); 47 // 自定义文本标注样式 48 label.setStyle({ 49 color: 'blue', 50 borderRadius: '5px', 51 borderColor: '#ccc', 52 padding: '10px', 53 fontSize: '16px', 54 height: '30px', 55 lineHeight: '10px', 56 fontFamily: '微软雅黑' 57 }); 58 map.addOverlay(label); 59 } 60 </script>
为什么原来的写法就可以,现在就不行呢?用笨办法逐一排除,最后发现,当在add_point()等方法放在iframe中并调用时,icon显示正常。
初步怀疑是iframe还是触碰了某种安全机制。网上找到一篇“百度地图api自定义marker图片不显示问题”,提到了是跨域的原因,但仍然没有试出解决办法。
怎么办呢?实在不甘心把自定义的标注代码写在iframe里面。。
想了一个办法:函数放在iframe里面,然后调用时,从父页面调用,像这样:
foreach ($units as $unit) { echo "document.getElementById('iframe_map').contentWindow.add_point(map, $unit->coor , '$unit->name1', '$unit->id', $unit->tagType);\n";
}
总结
以上的方案,由页面的主模板生成并插入iframe,在iframe中把生成的map等对象保存到主模板的全局变量中,在主模板中监听iframe的onload事件,然后执行调用者页面的my_init()函数。该方案可解决现有问题,但中间各种同步、异步,耦合度较高,互相调用的时序问题、变量问题等复杂度直线上升,不是一个理想的解决方案。此处先记录下来,不管怎么说,也是一些经验了。等待更好的方案的出现。