1、我的需求
简单说下为什么我需要这么一个气泡类型的弹出窗口,甲方有一些智能设备,里面有经纬度信息以及一些设备采集的信息,希望把这些设备的经纬度落在地图上,并且能够直观的查看设备状态。
很多人马上就说了,这个太简单了,用ArcGIS API 4.12 for JS 自带的API全都能实现,首先获取经纬度,然后在地图上添加Graphic即可,另外显示设备状态就用自带的PopupTemplate就完全可以实现。首先,第一条没错,实现的思路也是这样,但是第二条,用PopupTemplate这个API,达不到我想要的效果:就是,网页打开默认就是一种很多智能设备的点,点边上就是标注窗口,但是内容是省略的,根据设备的状态,比如在线或者离线标注窗体的颜色也是不一样。然后鼠标悬停到智能设备点上的时候,开始显示详细的内容。下图可以看到是比较经典的PopupTemplate的样子,默认也不显示,需要点击才能弹出,样式也很平常,没有动画效果。
各位看官,下面是我开发的气泡标注窗,是不是感觉不太一样?我开发的tooltip默认显示省略内容,悬停或者点击可以显示详细内容,实现了我的预想,如下图所示:
这个就是利用html5tooltips这个开源插件开发出来的气泡提示窗口,首先,html5tooltips默认气泡tooltip是不显示的,我这里是修改了它的源码,让默认情况下显示省略内容,悬停情况下显示详细内容。下面一步步教大家怎么来操作。
2、html5tooltips的下载和安装
这里有两种下载方式,首先开源下载地址是:http://ytiurin.github.io/html5tooltipsjs/。另外就是通过VS Code开发环境中的npm命令进行加载,还是在VS Code的TERMINAL窗口中输入:npm install html5tooltips,系统会自动把html5tooltips源码下载到你的工程目录node_modules下面。
下载完成后,我们需要在HTML里面引入html5tooltips的样式(注意这个插件对IE10以下就不支持了)和JS代码。
<link rel="stylesheet" type="text/css" href="/node_modules/html5tooltipsjs/html5tooltips.css" />
<link rel="stylesheet" type="text/css" href="/node_modules/html5tooltipsjs/html5tooltips.animation.css" />
<script type="text/javascript" src="/node_modules/html5tooltipsjs/html5tooltips.js"></script>
这样html5tooltips这个气泡tooltip就被引入进来,可以在上面完成开发了。
3、气泡窗体Tooltip附着对象
如果你用过PopupTemplate就会知道,这个PopupTemplate气泡窗口是附着到Graphic或者GraphicLayer上的,也就是说PopupTemplate不能单独存在,没有Graphic或者GraphicLayer就不会有PopupTemplate气泡弹出窗口。同样的道理,html5tooltips这个气泡tooltip也是附着在一个特定的对象上的,不过ESRI的Graphic不提供这样的接口供html5tooltips附着,所以html5tooltips气泡弹出窗只能附着在HTML的DOM对象上,我这里用的是DIV,也就是说动态的创建一个个DIV,然后让html5tooltips气泡弹出窗附着上去,这样鼠标点击或者悬停到这个DIV的时候就会触发显示html5tooltips气泡弹出窗。
(1)动态创建DIV
动态创建DIV很简单,网上有很多办法,这里我用的是
var dv=document.createElement("p");
然后给创建好的这个“dv”赋Id值,以及修改样式:
dv.setAttribute("id","id"+popupInfo.id);
dv.setAttribute("style","width:20px;height:20px;position:absolute;z-index:999999;border-radius: 10px; background-color:rgb(0, 255, 64)");
这里必须给你创建的每一个“dv”赋值一个唯一的Id,我这里用的是设备的编码,如果没有则自己创建一个顺序的Id也可以,对于html元素的Id值,不能是以数字开头,必须以字母开头(所以上面代码中,我每一个Id值前面都加了字母“id”),否则后面你用getElementById方法的时候就会出错,找不到你想找的html元素。我这里把创建的“dv”的样式(style)设置了20像素的圆形,你可以按自己的需求设置不同形状。style属性中,position:absolute;z-index:999999;这两个值表示把创建好的DIV先放在视图最顶层,随后根据设备点的经纬度挪动DIV到对应的位置。
最后需要把创建好的DIV添加到HTML里面:
document.body.append(dv);
上面代码是chrome支持的用法,IE的话要用$("#myBody").append(dv);就是你需要给你HTML的body标签赋值一个Id,然后根据这个body的ID来添加动态创建的Div。
(2)根据经纬度来移动Div
首先确保你从后台来的经纬度坐标跟你当前地图的坐标系一致就可以,比如你的地图是高德地图或者百度地图,那么需要你把后台的经纬度转换为火星坐标系下的坐标,这方面的内同就不多讲,以后另开文章详说。
重点强调的是,后台请求过来的经纬度坐标,需要转换成当前的屏幕坐标,才能正确的把你动态创建的Div移动到经纬度对应位置。
第一步,要做require中引入"esri/geometry/Point"类,然后在代码中根据你的经纬度新建一个点mapPoint:
var mapPoint=new Point(
{
latitude: myLattitude,//后台的经纬度
longitude: myLongitude,//后台的经纬度
spatialReference: mapView.spatialReference
});
第二步,把mapPoint转换为屏幕像素坐标点screenPoint:
var screenPoint=mapView.toScreen(mapPoint);
第三步,根据screenPoint的x、y坐标移动Div:
var dx=screenPoint.x-10;
var dy=screenPoint.y-10;
$("#id"+popupInfo.id).css('transform', 'translate3d(' + dx + 'px, ' + dy+ 'px, 0)');
因为Div默认的坐标点是左上角,所以上面代码中的dx、dy是对Div的移动像素进行校正,使得Div的中心正好在经纬度点的中心上。$("#id"+popupInfo.id)是jQuery语句,用来通过ID查询HTMl元素,因为我的popupInfo.id是一个数字,所以我在前面加了字母修饰,防止出错。第三条语句就是把动态创建的Div进行移动的主要代码。通过上述步骤,动态创建的Div就可以移动到经纬度坐标点的位置了。
(3)对移动好位置的Div附着Tooltip气泡窗口
html5tooltips支持高度自定义,可以显示简略内容和详细内容以及动画效果,那么我们先创建单个的气泡窗口:
var singleTooltip= {
animateFunction: "fadein",//动画效果
color: "rouge", //窗口颜色
contentText: popupInfo.id,//气泡窗口的简略内容(我这里简略内容只显示设备ID)
contentMore: popupInfo.content,//气泡窗口的详细内容,设备状态等等
stickTo: "right",//气泡窗口的现实位置,这里是Div的右侧
targetSelector: "#id"+popupInfo.id//附着对象的HTML元素ID
};
上面代码是创建了一个Tooltip气泡窗口,并把它附着到对应ID的Div上,因为我这里的设备很多,所以我是通过循环创建多个Tooltip气泡窗口,然后把它们放到一个数组中。
var tooltipCollection=[];
tooltipCollection.push(singleTooltip);
注意,var tooltipCollection=[];这句代码要写在循环外面,不然你的数组永远只有一个值。
最后,所有的Tooltip气泡窗口添加到数组之后,通过html5tooltips来初始化这些Tooltip气泡窗口并且显示出来。很简单就一句代码:
html5tooltips(tooltipCollection);
注意,上述一行代码,也是要写到循环外面的。
(4)地图视图变化时更新Div位置
前面的内容是地图初始化的时候,创建动态Div并附着Tooltip气泡窗口,当视图放大或者缩小的时候,Div位置相对屏幕是不动的。因此我们要及时更新Div的位置,也就是需要添加mapView.watch("extent", function (evt) {})事件。
之前我们把从后台取到的设备信息包括经纬度点,都是存放到tooltipCollection这个数组中,因此在视图变化的时候,我们遍历这个数组中的设备点,重新把经纬度坐标转换为屏幕坐标点即可。代码如下:
mapView.watch("extent", function (evt) {
for (var i = 0; i < app.popupsInfo.length; i++) {
var popupInfo = app.popupsInfo[i];
//坐标转换
//坐标转换
var mapPoint=new Point(
{
latitude: popupInfo.lat,
longitude: popupInfo.lon,
spatialReference: app.mapView.spatialReference
});
var screenPoint=app.mapView.toScreen(mapPoint);
if(screenPoint==null)return;
var dx=screenPoint.x-10;
var dy=screenPoint.y-10;
$("#id"+popupInfo.id).css('transform', 'translate3d(' + dx + 'px, ' + dy+ 'px, 0)');
var tooltip =html5tooltips.getTooltipByTarget(document.getElementById("id"+popupInfo.id));
tooltip.moveTooltip();
}
});
上述代码中,首先根据数组里面的经纬度点,重新转换为视图变化后的屏幕坐标,然后移动Div。然后再通过html5tooltips.getTooltipByTarget()这个自带的方法来查找对应的气泡窗口tooltip,最后使用tooltip.moveTooltip()来更新气泡窗口的位置。
4、修改html5tooltips代码
首先在文件“html5tooltips.js”中找到,function show()这个函数。
function show()
{
isHiding = false;
if (ttElement.style.visibility !== 'visible') {
ttElement.style.visibility = 'visible';
setTimeout(function(){
moveTooltip();
applyAnimationClass(elBox, ttModel.animateFunction + "-from",
ttModel.animateFunction + "-to");
});
}
return this;
}
在这个函数下面添加一个自定义的函数,function showShort(),这个函数的功能就是显示简略内容。
function showShort() {
isHiding = false;
if (ttElement.style.visibility == 'visible') {
ttElement.style.visibility = 'visible';
if (ttModel.contentMore) {
elMore.style.display = 'none';
elMore.style.visibility = 'hidden';
}
applyAnimationClass(elBox, ttModel.animateFunction + "-from", ttModel.animateFunction + "-to");
moveTooltip();
}
return this;
}
创建好showShort()函数后,接着修改function createTooltip(target,options,tooltips)这个html5tooltips自带的函数,我这里就直接把修改好的贴进来了:
function createTooltip(target,options,tooltips)
{
var tooltip;
for(var i=tooltips.length;i--;)
if(tooltips[i].model.target===target){
tooltip=tooltips[i];
break;
}
if(!tooltip){
tooltip=new HTML5TooltipUIComponent;
tooltips.push(tooltip);
}
tooltip.set(options);
tooltip.set('target',target);
var hovered,focused;
/*
function tryUnmountTooltip()
{
if (target===hovered || tooltip.element===hovered)
return;
if (tooltip.model.persistent)
tooltip.hide(function(){
tooltip.unmount();
});
else
tooltip.unmount();
}
function disposeTooltip()
{
hovered = null;
if (target===focused)
return;
if (tooltip.model.persistent)
setTimeout(tryUnmountTooltip, tooltip.model.hideDelay);
else
tryUnmountTooltip();
}
*/
tooltip.mount();
tooltip.show();
target.addEventListener('mouseenter',function(){
tooltip.mount();
tooltip.showMore();
});
/*
target.addEventListener("mouseenter",function(){
if (this===hovered || this===focused)
return;
hovered = this;
setTimeout(function() {
if (this===hovered){
tooltip.mount();
tooltip.showMore();
}
}.bind(this), tooltip.model.delay);
});*/
//target.addEventListener("mouseleave",disposeTooltip);
target.addEventListener('mouseleave',function(){
tooltip.mount();
tooltip.showShort();
});
target.addEventListener("focus",function(){
if (["INPUT", "TEXTAREA"].indexOf(this.tagName) === -1 &&
this.getAttribute("contenteditable") === null)
return;
focused = this;
tooltip.mount();
tooltip.showMore();
});
/*
target.addEventListener("blur",function(){
focused = null;
if(hovered !== tooltip.element){
tooltip.unmount();
}
});
tooltip.element.addEventListener("mouseenter",function(){
hovered = this;
});*/
//tooltip.element.addEventListener("mouseleave",disposeTooltip);
/*
target.element.addEventListener('mouseleave',function(){
tooltip.mount();
tooltip.showShort();
});*/
}
修改好的createTooltip(target,options,tooltips)这个html5tooltips自带的函数,大家可以参考源码比较一下做了哪些改动,我这里就不一一细讲了,其中还注释掉了原来的一些监听函数。
最后,因为你添加了一个新的函数showShort(),你需要把这个函数放到接口位置,找到下面代码块的位置,并加入新建函数:
// PUBLIC INTERFACE
this.hide=hide;
this.show=show;
this.showShort=showShort;
this.showMore=showMore;
this.moveTooltip=moveTooltip;
所有关于html5tooltips修改的内容都在于此,文章顶部位置,提供我修改好的html5tooltips.js文件以供大家使用。