OpneLayers 创建地图卷帘

注:当前使用的是 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之路】 已经接入了智能助手,欢迎关注,欢迎提问。

欢迎访问我的博客网站-长谈GIShttp://shanhaitalk.com

都看到这了,不要忘记点赞、收藏 + 关注

本号不定时更新有关 GIS开发 相关内容,欢迎关注 !

相关推荐
钢铁男儿15 分钟前
Python内置类型子类化的陷阱与解决方案
开发语言·前端·python
野盒子25 分钟前
前端面试题 微信小程序兼容性问题与组件适配策略
前端·javascript·面试·微信小程序·小程序·cocoa
Hilaku33 分钟前
为什么我不再追流行,而是重新研究了 jQuery
前端·javascript·jquery
兔子1213534 分钟前
浏览器内容缓存数据量大时的优化方案
前端
G等你下课36 分钟前
JavaScript 中 Promise 的深度解析:异步编程的革新之路
前端·javascript
啃火龙果的兔子1 小时前
安全有效的 C 盘清理方法
前端·css
海天胜景1 小时前
vue3 数据过滤方法
前端·javascript·vue.js
天生我材必有用_吴用1 小时前
深入理解JavaScript设计模式之策略模式
前端
海上彼尚1 小时前
Vue3 PC端 UI组件库我更推荐Naive UI
前端·vue.js·ui
述雾学java1 小时前
Vue 生命周期详解(重点:mounted)
前端·javascript·vue.js