❝
注:当前使用的是 ol 9.2.4 版本,天地图使用的
key
请到天地图官网申请,并替换为自己的key
地图卷帘主要用于比较两个或多个叠加在一起的地图图层或影像之间的差异,是一种地图可视化技术。允许用户通过鼠标或其他交互方式,模拟将上层影像或图层像窗帘一样卷起或滑动,从而逐渐露出下层的影像或图层,使用户能够直观地比较图层的区别。本节主要介绍地图卷帘。
1. 创建卷帘要素
创建卷帘开关结构并设置样式,添加鼠标按下、松开和移动事件,用于记录卷帘开关位置并更新卷帘图层宽度。在鼠标按下事件中开启鼠标移动事件。
ini
<div class="swipe-content" onmouseup="mouseUp()">
<div class="handle" onmousedown="mouseDown()" onmouseout="mouseOut()"></div>
</div>
卷帘开关样式
css
/* 卷帘滑动块样式 */
.swipe-content {
display: none;
position: absolute;
top: 50px;
left: 50%;
opacity: 0.8;
width: 2px;
height: calc(100% - 50px);
margin-left: -1px;
background-color: #00A0E9;
z-index: 2;
}
.handle {
position: absolute;
height: 40px;
width: 40px;
border-radius: 50%;
background-color: #cee9ff;
background-image: url('swipe.jpg');
rotate: 90deg;
background-size: 100% 100%;
cursor: e-resize;
margin-left: -20px;
top: 50%;
margin-top: -20px;
}
2. 卷帘开关移动事件
在卷帘开关拖动事件中需要计算小球偏移量,即小球宽度的一半,根据拖动的距离设置左右边界阈值。在拖动完成后更新开关位置并重新渲染地图。
ini
function swipeMove(evt) {
const ballEle = document.querySelector(".handle")
const ballEleRect = ballEle.getBoundingClientRect()
const ballWidthOfHalf = ballEleRect.width / 2
const mapSize = map.getSize()
const offsetLeft = target.offsetLeft
swipeValue = offsetLeft
const rect = target.offsetParent.getBoundingClientRect()
let leftDistance = evt.clientX - rect.x
if (leftDistance <= ballWidthOfHalf) {
leftDistance = ballWidthOfHalf
}
if (leftDistance >= mapSize[0] - ballWidthOfHalf) {
leftDistance = mapSize[0] - ballWidthOfHalf
}
// 更新要素位置
target.style.left = leftDistance + "px"
map.render()
}
3. 监听地图渲染前事件
在地图渲染前计算卷帘图层宽度并裁剪图层。
ini
TDTVecLayer.on("prerender", event => {
const ctx = event.context;
const mapSize = map.getSize();
const width = swipeValue;
const tl = ol.render.getRenderPixel(event, [width, 0]);
const tr = ol.render.getRenderPixel(event, [mapSize[0], 0]);
const bl = ol.render.getRenderPixel(event, [width, mapSize[1]]);
const br = ol.render.getRenderPixel(event, mapSize);
ctx.save();
ctx.beginPath();
ctx.moveTo(tl[0], tl[1]);
ctx.lineTo(bl[0], bl[1]);
ctx.lineTo(br[0], br[1]);
ctx.lineTo(tr[0], tr[1]);
ctx.closePath();
ctx.clip();
});
4. 监听地图渲染后事件
地图渲染完成后只需保存上下文即可。
csharp
TDTVecLayer.on("postrender", (event) => {
const ctx = event.context;
ctx.restore();
});
5. 完整代码
其中libs
文件夹下的包需要更换为自己下载的本地包或者引用在线资源。
xml
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>地图卷帘</title>
<meta charset="utf-8" />
<script src="../../js/ol9.2.4.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/9.2.4/ol.min.css">
<style>
* {
padding: 0;
margin: 0;
font-size: 14px;
font-family: '微软雅黑';
}
html,
body {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
#map {
position: absolute;
top: 50px;
bottom: 0;
left: 0;
right: 0;
}
#top-content {
position: absolute;
width: 100%;
height: 50px;
line-height: 50px;
background: linear-gradient(135deg, #ff00cc, #ffcc00, #00ffcc, #ff0066);
color: #fff;
text-align: center;
}
.swipe-btn {
border-radius: 5px;
border: 1px solid #50505040;
padding: 5px 20px;
color: #fff;
margin: 0 10px;
background: #377d466e;
transition: background-color 10s ease-in-out 10s;
}
.swipe-btn:hover {
cursor: pointer;
filter: brightness(120%);
background: linear-gradient(135deg, #c850c0, #4158d0);
}
.active {
background: linear-gradient(135deg, #c850c0, #4158d0);
}
/* 卷帘滑动块样式 */
.swipe-content {
display: none;
position: absolute;
top: 50px;
left: 50%;
opacity: 0.8;
width: 2px;
height: calc(100% - 50px);
margin-left: -1px;
background-color: #00A0E9;
z-index: 2;
}
.handle {
position: absolute;
height: 40px;
width: 40px;
border-radius: 50%;
background-color: #cee9ff;
background-image: url('swipe.jpg');
rotate: 90deg;
background-size: 100% 100%;
cursor: e-resize;
margin-left: -20px;
top: 50%;
margin-top: -20px;
}
</style>
</head>
<body>
<div id="top-content">
<span class="swipe-btn open-swipe active" onclick="openSwipe()">打开卷帘</span>
<span class="swipe-btn open-swipe" onclick="closeSwipe()">关闭卷帘</span>
</div>
<div id="map" title="地图显示"></div>
<div class="swipe-content" onmouseup="mouseUp()">
<div class="handle" onmousedown="mouseDown()" onmouseout="mouseOut()"></div>
</div>
</body>
</html>
<script>
//地图投影坐标系
const projection = ol.proj.get('EPSG:3857');
//==============================================================================//
//============================天地图服务参数简单介绍==============================//
//================================vec:矢量图层==================================//
//================================img:影像图层==================================//
//================================cva:注记图层==================================//
//======================其中:_c表示经纬度投影,_w表示球面墨卡托投影================//
//===============================================================================//
const TDTImgLayer = new ol.layer.Tile({
title: "天地图影像图层",
source: new ol.source.XYZ({
url: "http://t0.tianditu.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=2a890fe711a79cafebca446a5447cfb2",
attibutions: "天地图注记描述",
crossOrigin: "anoymous",
wrapX: false
})
})
const TDTVecLayer = new ol.layer.Tile({
title: "天地图矢量图层",
source: new ol.source.XYZ({
url: "http://t0.tianditu.com/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=2a890fe711a79cafebca446a5447cfb2",
attibutions: "天地图矢量图层",
crossOrigin: "anoymous",
wrapX: false
})
})
const TDTImgCvaLayer = new ol.layer.Tile({
title: "天地图影像注记图层",
source: new ol.source.XYZ({
url: "http://t0.tianditu.com/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=2a890fe711a79cafebca446a5447cfb2",
attibutions: "天地图注记描述",
crossOrigin: "anoymous",
wrapX: false
})
})
const map = new ol.Map({
target: "map",
view: new ol.View({
center: [104.0635986160487, 30.660919181071225],
zoom: 5,
worldsWrap: true,
minZoom: 1,
maxZoom: 20,
projection: "EPSG:4326"
}),
layers: [TDTImgLayer, TDTImgCvaLayer]
})
map.on('click', evt => {
console.log(evt.coordinate)
})
/**
* 初始化卷帘图层宽度,即地图宽度的一半
*/
const target = document.querySelector(".swipe-content")
const mapSize = map.getSize()
const halfWidth = mapSize[0] / 2
let swipeValue = halfWidth
/**
* 默认打开卷帘
*/
map.addLayer(TDTVecLayer)
target.style.display = "block"
/**
* 打开卷帘
*/
function openSwipe() {
toogleAciveClass(event.target)
target.style.display = "block"
map.addLayer(TDTVecLayer)
}
/**
* 关闭卷帘
*/
function closeSwipe() {
toogleAciveClass(event.target)
target.style.display = "none"
map.removeLayer(TDTVecLayer)
}
/**
* 卷帘图层渲染前事件
* 计算卷帘图层宽度,即拖动距离,裁剪图层
*/
TDTVecLayer.on("prerender", event => {
const ctx = event.context;
const mapSize = map.getSize();
const width = swipeValue;
const tl = ol.render.getRenderPixel(event, [width, 0]);
const tr = ol.render.getRenderPixel(event, [mapSize[0], 0]);
const bl = ol.render.getRenderPixel(event, [width, mapSize[1]]);
const br = ol.render.getRenderPixel(event, mapSize);
ctx.save();
ctx.beginPath();
ctx.moveTo(tl[0], tl[1]);
ctx.lineTo(bl[0], bl[1]);
ctx.lineTo(br[0], br[1]);
ctx.lineTo(tr[0], tr[1]);
ctx.closePath();
ctx.clip();
});
/**
* 卷帘图层渲染完成事件
* 保存图层上下文
*/
TDTVecLayer.on("postrender", (event) => {
const ctx = event.context;
ctx.restore();
});
/**
* 卷帘开关按下事件
* 记录鼠标按下位置距离地图左侧边缘的距离
*/
function mouseDown(evt) {
const ballEle = document.querySelector(".handle")
// 禁用浏览器默认拖放行为
ballEle.ondragstart = function () {
return false
}
swipeValue = target.offsetLeft
map.getTargetElement().addEventListener('mousemove', swipeMove)
}
/**
* 卷帘开关拖动事件
* 根据拖动小球的一般宽度计算偏移量,控制左右边界距离。
* 更新开关位置并重新渲染地图。
*/
function swipeMove(evt) {
const ballEle = document.querySelector(".handle")
const ballEleRect = ballEle.getBoundingClientRect()
const ballWidthOfHalf = ballEleRect.width / 2
const mapSize = map.getSize()
const offsetLeft = target.offsetLeft
swipeValue = offsetLeft
const rect = target.offsetParent.getBoundingClientRect()
let leftDistance = evt.clientX - rect.x
if (leftDistance <= ballWidthOfHalf) {
leftDistance = ballWidthOfHalf
}
if (leftDistance >= mapSize[0] - ballWidthOfHalf) {
leftDistance = mapSize[0] - ballWidthOfHalf
}
// 更新要素位置
target.style.left = leftDistance + "px"
map.render()
}
function mouseUp(evt) {
map.getTargetElement().removeEventListener('mousemove', swipeMove)
}
function mouseOut() {
// this.map.getTargetElement().removeEventListener('mousemove',this.swipeMove)
}
/**
* 切换激活样式
*/
function toogleAciveClass(target) {
// 判断top-content子元素是否激活,并切换激活样式
const swipeBtnLike = document.querySelector('#top-content')
for (let element of swipeBtnLike.children) {
if (target === element) {
target.classList.add('active')
} else {
element.classList.remove('active')
}
}
}
</script>
❝
OpenLayers示例数据下载,请回复关键字:ol数据
全国信息化工程师-GIS 应用水平考试资料,请回复关键字:GIS考试
❝【GIS之路】 已经接入了智能助手,欢迎关注,欢迎提问。
欢迎访问我的博客网站-长谈GIS :
http://shanhaitalk.com
都看到这了,不要忘记点赞、收藏 + 关注 哦 !
本号不定时更新有关 GIS开发 相关内容,欢迎关注 !