卷帘效果
卷帘效果是一种地图可视化技术,可以通过滑动分割线来对比两个不同的地图图层。这种效果常用于对比同一区域在不同时期或不同类型的地图数据。
示例
代码实现
vue
<template>
<div class="relative h-96">
<input
id="swipe"
type="range"
class="w-full absolute top-1/2 z-1 opacity-0 cursor-ew-resize pointer-events-none"
/>
<div class="swipe-line">
<div class="swipe-handle"></div>
</div>
<div id="map" class="w-full h-full"></div>
</div>
</template>
<script setup>
import { onMounted } from "vue";
import "ol/ol.css";
import Map from "ol/Map";
import View from "ol/View";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
import { fromLonLat } from "ol/proj";
import { getRenderPixel } from "ol/render";
onMounted(() => {
// 创建左侧图层(矢量图)
const leftLayer = new TileLayer({
source: new XYZ({
url: "http://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
}),
});
// 创建右侧图层(影像图)
const rightLayer = new TileLayer({
source: new XYZ({
url: "http://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=6",
}),
});
// 创建地图
const map = new Map({
target: "map",
layers: [leftLayer, rightLayer],
view: new View({
center: fromLonLat([116.397428, 39.90923]), // 北京
zoom: 12,
}),
});
// 获取滑块元素
const swipe = document.getElementById("swipe");
const swipeLine = document.querySelector(".swipe-line");
const swipeHandle = document.querySelector(".swipe-handle");
// 监听滑块手柄拖动
let isDragging = false;
let startX = 0;
let startLeft = 50;
swipeHandle.addEventListener("mousedown", function (e) {
isDragging = true;
startX = e.clientX;
startLeft = parseInt(swipeLine.style.left || 50);
e.preventDefault();
});
document.addEventListener("mousemove", function (e) {
if (!isDragging) return;
const dx = e.clientX - startX;
const containerWidth = map.getTargetElement().offsetWidth;
let newLeft = startLeft + (dx / containerWidth) * 100;
// 限制范围在0-100之间
newLeft = Math.max(0, Math.min(100, newLeft));
swipeLine.style.left = newLeft + "%";
swipe.value = newLeft;
map.render();
});
document.addEventListener("mouseup", function () {
isDragging = false;
});
// 渲染前处理
rightLayer.on("prerender", function (event) {
const ctx = event.context;
const mapSize = map.getSize();
const width = mapSize[0] * (swipe.value / 100);
const tl = getRenderPixel(event, [width, 0]);
const tr = getRenderPixel(event, [mapSize[0], 0]);
const bl = getRenderPixel(event, [width, mapSize[1]]);
const br = 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();
});
// 渲染后处理
rightLayer.on("postrender", function (event) {
const ctx = event.context;
ctx.restore();
});
});
</script>
<style scoped>
.h-96 {
height: 24rem;
}
.relative {
position: relative;
}
.swipe-line {
position: absolute;
top: 0;
bottom: 0;
width: 4px;
background: #fff;
z-index: 2;
pointer-events: none;
left: 50%;
transform: translateX(-50%);
}
.swipe-handle {
position: absolute;
width: 24px;
height: 24px;
background: #fff;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 3;
pointer-events: all;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
cursor: ew-resize;
}
.swipe-handle::before,
.swipe-handle::after {
content: "";
position: absolute;
width: 2px;
height: 10px;
background: #666;
left: 7px;
top: 7px;
}
.swipe-handle::after {
left: 15px;
}
</style>