全图及框选截图
本章将介绍如何在 OpenLayers 中实现全图截图和框选截图功能。
示例
代码实现
vue
<template>
<div class="h-screen">
<div id="map" class="w-full h-full"></div>
<div class="control-panel">
<button
id="fullScreenshot"
class="control-button"
@click="fullScreenshot"
>
📸 全图截图
</button>
<button
id="areaScreenshot"
class="control-button"
@click="toggleAreaScreenshot"
>
{{ areaScreenshotText }}
</button>
</div>
<div class="tooltip" ref="tooltip"></div>
<div class="selection-info" ref="selectionInfo"></div>
</div>
</template>
<script setup>
import { onMounted, ref } 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 { DragBox } from "ol/interaction";
import html2canvas from "html2canvas";
const tooltip = ref(null);
const selectionInfo = ref(null);
const isSelecting = ref(false);
const areaScreenshotText = ref("✂️ 框选截图");
let map;
let dragBox;
onMounted(() => {
// 创建地图
map = new Map({
target: "map",
view: new View({
center: fromLonLat([117.33083, 39.094899]), // 天津
zoom: 12,
}),
});
// 添加天地图图层
const tdtLayer = new TileLayer({
source: new XYZ({
url: "http://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=d32c6748c80f81a44acd8633cea41dfd",
crossOrigin: "anonymous",
}),
});
map.addLayer(tdtLayer);
// ESC键取消框选
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && isSelecting.value) {
cancelSelection();
}
});
// 更新提示框位置
map.on("pointermove", (e) => {
if (!isSelecting.value) return;
tooltip.value.style.left = e.originalEvent.pageX + 10 + "px";
tooltip.value.style.top = e.originalEvent.pageY + 10 + "px";
});
});
// 全图截图
const fullScreenshot = async (event) => {
const button = event.target;
button.disabled = true;
button.style.background = "linear-gradient(145deg, #4CAF50, #388E3C)";
button.textContent = "📸 截图中...";
try {
const canvas = await html2canvas(document.querySelector("#map"), {
allowTaint: false,
useCORS: true,
});
const link = document.createElement("a");
link.download = `地图全图截图_${new Date().toLocaleString()}.png`;
link.href = canvas.toDataURL();
link.click();
} catch (error) {
alert("截图失败,请重试");
} finally {
button.disabled = false;
button.style.background = "";
button.textContent = "📸 全图截图";
}
};
// 切换框选截图模式
const toggleAreaScreenshot = () => {
if (isSelecting.value) {
cancelSelection();
return;
}
// 开启框选模式
isSelecting.value = true;
areaScreenshotText.value = "❌ 取消框选";
map.getTargetElement().style.cursor = "crosshair";
tooltip.value.style.display = "block";
tooltip.value.textContent = "按住鼠标左键拖动选择区域,ESC取消";
// 添加框选交互
dragBox = new DragBox({
className: "selection-box",
});
map.addInteraction(dragBox);
dragBox.on("boxend", async () => {
const extent = dragBox.getGeometry().getExtent();
const bottomLeft = map.getPixelFromCoordinate([extent[0], extent[1]]);
const topRight = map.getPixelFromCoordinate([extent[2], extent[3]]);
const bounds = {
left: bottomLeft[0],
top: topRight[1],
width: topRight[0] - bottomLeft[0],
height: bottomLeft[1] - topRight[1],
};
if (bounds.width < 50 || bounds.height < 50) {
selectionInfo.value.textContent = "选区太小,请重新选择(最小 50×50 像素)";
selectionInfo.value.style.display = "block";
return;
}
try {
selectionInfo.value.textContent = "正在生成截图...";
selectionInfo.value.style.display = "block";
const canvas = await html2canvas(document.querySelector("#map"), {
x: bounds.left,
y: bounds.top,
width: bounds.width,
height: bounds.height,
allowTaint: false,
useCORS: true,
});
const link = document.createElement("a");
link.download = `地图区域截图_${new Date().toLocaleString()}.png`;
link.href = canvas.toDataURL();
link.click();
cancelSelection();
} catch (error) {
selectionInfo.value.textContent = "截图失败,请重试";
setTimeout(cancelSelection, 2000);
}
});
dragBox.on("boxdrag", () => {
const extent = dragBox.getGeometry().getExtent();
const bottomLeft = map.getPixelFromCoordinate([extent[0], extent[1]]);
const topRight = map.getPixelFromCoordinate([extent[2], extent[3]]);
const width = Math.abs(topRight[0] - bottomLeft[0]);
const height = Math.abs(bottomLeft[1] - topRight[1]);
selectionInfo.value.style.display = "block";
selectionInfo.value.textContent = `选区大小: ${width.toFixed(
0
)} × ${height.toFixed(0)} 像素`;
});
};
// 取消框选
const cancelSelection = () => {
isSelecting.value = false;
map.getTargetElement().style.cursor = "";
areaScreenshotText.value = "✂️ 框选截图";
tooltip.value.style.display = "none";
selectionInfo.value.style.display = "none";
if (dragBox) {
map.removeInteraction(dragBox);
dragBox = null;
}
};
</script>
<style scoped>
.h-screen {
height: 24rem;
}
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
.control-panel {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
background: rgba(255, 255, 255, 0.95);
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
transition: transform 0.3s ease;
}
.control-panel:hover {
transform: translateY(-2px);
}
.control-button {
display: block;
width: 100%;
margin: 8px 0;
padding: 10px 20px;
background: linear-gradient(145deg, #2196f3, #1976d2);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
letter-spacing: 0.5px;
transition: all 0.3s ease;
box-shadow: 0 2px 6px rgba(33, 150, 243, 0.3);
}
.control-button:hover {
background: linear-gradient(145deg, #1e88e5, #1565c0);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(33, 150, 243, 0.4);
}
.control-button:active {
transform: translateY(1px);
}
.control-button.active {
background: linear-gradient(145deg, #4caf50, #388e3c);
}
:deep(.selection-box) {
border: 2px solid #2196f3;
background: rgba(33, 150, 243, 0.1);
position: absolute;
pointer-events: none;
border-radius: 4px;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
animation: pulse 2s infinite;
z-index: 1000;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(33, 150, 243, 0.4);
}
70% {
box-shadow: 0 0 0 6px rgba(33, 150, 243, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(33, 150, 243, 0);
}
}
.tooltip {
position: fixed;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
z-index: 1001;
display: none;
}
.selection-info {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
display: none;
z-index: 1001;
}
</style>