
话不多说直接看效果为啥要整这个呢就是项目有个需求要求底图改成带有山脉纹理的但是有不能有除中国以外的地方出现背景这里只能排除使用各种底图API了然后我就查阅Echart文档没有现成的可实现的方法网上有Three.js react实现的可是我自己又不会咋整呢当然这个年头了问AI呀问了说是使用Ehcart的graphic添加背景图然后我就网上各种找要这种除了区划边界之外都是透明的底图还真被我在GitHub上找到了是个项目直接本地运行根据选择的区划直接下载底图。背景图片找到了那就是写代码呗话不多说直接上源码源码在下边先听我说完按照ai的方式给代码写出来了发现边界数据和图片是两个层级且对不上差十万八千里难受继续叫他修改。。。。。此时改了很久终于改好了。主要解决了缩放拖拽时底图和区划边界不重合的问题其实就是每次缩放和拖拽都重新计算一下底图的位置。及有了底图有兼容了echarts的各种配置打点飞行图...不过不支持下钻想要下钻还需要找到对应地区的底图然后下钻之后再对应一下底图和区划边界想想就麻烦这里就不弄了。需要自己来取源码使用的geojson数据来源DataV。templatediv classchina-map-pagediv refmapRef classchina-map/div/div/templatescript setup langtsimport {ref,reactive,onMounted,onBeforeUnmount,nextTick,computed} from vueimport * as echarts from echartsimport { geoMercator } from d3-geoimport chinaBg from /assets/china.pngconst mapRef refHTMLElement | null(null)let chart: echarts.ECharts | null null/*** * 你现在已经调好的参数* ** 这几个值会作为「初始状态」保存。** 初始状态* scaleX 1* scaleY 1.3* offsetX 3* offsetY 93** 只要初始状态已经对齐后面的缩放、拖动都会基于* 这个状态进行同步不会重新把纹理强行 fit 到地图。* 根据自身情况可调整*/const textureConfig reactive({scaleX: 1,scaleY: 1.3,offsetX: 3,offsetY: 93})/*** * 中国大致经纬度范围根据自身情况可调整* */const CHINA_BOUNDS {left: 73.2,top: 54.1,right: 134.8,bottom: 17.8}// 根据CHINA_BOUNDS计算出横纵坐标的参考点const x computed(() {return (CHINA_BOUNDS.left CHINA_BOUNDS.right) / 2})const y computed(() {return (CHINA_BOUNDS.top CHINA_BOUNDS.bottom) / 2})// 这个点主要是为了测量横向缩放。也就是和x ,y 相差一些数值就可以近似表示地图当前的横向比例。const bl [134,20]/*** * 固定参考点* ** 不再使用** 当前地图矩形中心** 而是使用固定的地理坐标。** 这样地图发生 roam 后** 经纬度 - 当前像素** 可以准确知道地图到底发生了多少缩放和位移。*/const GEO_ANCHORS {/*** 中心参考点*/center: [x.value, y.value] as [number, number],/*** X 方向参考点** 和 center 保持相同纬度。*/x: [bl[0], y.value] as [number, number],/*** Y 方向参考点** 和 center 保持相同经度。*/y: [x.value, bl[1]] as [number, number]}/*** * 初始状态* ** 地图第一次渲染完成后把** 1. 参考点像素位置* 2. PNG 的位置* 3. PNG 的大小** 全部记录下来。** 后面的 roam 都基于这个状态计算。*/let initialState: {anchorCenter: [number, number]anchorX: [number, number]anchorY: [number, number]textureCenter: [number, number]textureWidth: numbertextureHeight: number} | null null/*** * 初始化地图* */async function initMap() {if (!mapRef.value) returnchart echarts.init(mapRef.value)/*** 加载中国 GeoJSON*/const chinaGeoJson await fetch(https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json).then(res {if (!res.ok) {throw new Error(加载中国地图失败${res.status})}return res.json()})/*** 注册地图*/echarts.registerMap(china, chinaGeoJson)/*** * d3 Mercator(使用第三方的投影坐标可以更好的让底图和区域边界重合)* */const projection geoMercator()/*** 先给一个中心*/projection.center([104, 35])/*** fitSize 只负责初始化地图。** 注意** 后面不会再使用这个东西去重新计算 PNG。*/projection.fitSize([1000, 700], chinaGeoJson)/*** * ECharts option* */const option: echarts.EChartsOption {backgroundColor: #000,geo: {map: china,/*** 开启拖动 缩放*/roam: true,/*** 初始缩放如果初始不是1那就需要重新调整textureConfig和CHINA_BOUNDS*/zoom: 1,/*** 地图层级*/z: 2,/*** 使用 d3 的 Mercator 投影*/projection: {project: (point) {return projection(point as [number, number])},unproject: (point) {return projection.invert(point as [number, number])!}},/*** 地图区域透明*/itemStyle: {areaColor: rgba(0, 0, 0, 0),borderColor: #00ffff,borderWidth: 2},/*** 鼠标移入*/emphasis: {itemStyle: {areaColor: rgba(0, 150, 255, 0.2)},label:{color:#fff}}},/*** 初始不放 graphic** 后面 updateChinaTexture 再添加。*/graphic: []}chart.setOption(option)await nextTick()/*** 等 ECharts 真正完成第一次布局*/requestAnimationFrame(() {requestAnimationFrame(() {/*** 第一次创建纹理。** 这里会记录「初始状态」。*/initTextureState()})})/*** * 地图缩放 / 拖动* */chart.on(georoam, () {updateChinaTexture()})}/*** * 经纬度 - 当前 ECharts 像素坐标* */function geoToPixel(point: [number, number]): [number, number] | null {if (!chart) return nullconst result chart.convertToPixel({geoIndex: 0},point) as number[]if (!result || result.length 2) {return null}if (!Number.isFinite(result[0]) ||!Number.isFinite(result[1])) {return null}return [result[0],result[1]]}/*** * 计算初始纹理* ** 注意** 这里仍然使用你之前的算法。** 所以** scaleX 1* scaleY 1.3* offsetX 3* offsetY 93** 得到的初始效果不会被改变。*/function initTextureState() {if (!chart) return/*** 获取三个固定参考点*/const anchorCenter geoToPixel(GEO_ANCHORS.center)const anchorX geoToPixel(GEO_ANCHORS.x)const anchorY geoToPixel(GEO_ANCHORS.y)if (!anchorCenter ||!anchorX ||!anchorY) {return}/*** * 获取当前中国地图矩形* */const leftTop geoToPixel([CHINA_BOUNDS.left,CHINA_BOUNDS.top])const rightBottom geoToPixel([CHINA_BOUNDS.right,CHINA_BOUNDS.bottom])if (!leftTop ||!rightBottom) {return}const baseLeft leftTop[0]const baseTop leftTop[1]const baseWidth rightBottom[0] - leftTop[0]const baseHeight rightBottom[1] - leftTop[1]if (baseWidth 0 ||baseHeight 0) {return}/*** * 根据你原来的参数计算 PNG 大小* */const textureWidth baseWidth * textureConfig.scaleXconst textureHeight baseHeight * textureConfig.scaleY/*** 原来的中心算法*/const centerX baseLeft baseWidth / 2const centerY baseTop baseHeight / 2/*** 原来的 offset*/const textureCenterX centerX textureConfig.offsetXconst textureCenterY centerY textureConfig.offsetY/*** * 保存初始状态* */initialState {anchorCenter: [anchorCenter[0],anchorCenter[1]],anchorX: [anchorX[0],anchorX[1]],anchorY: [anchorY[0],anchorY[1]],textureCenter: [textureCenterX,textureCenterY],textureWidth,textureHeight}/*** 第一次直接绘制*/renderTexture(textureCenterX,textureCenterY,textureWidth,textureHeight)}/*** * 根据地图当前状态更新 PNG* ** 这是整个方案最关键的地方。** 不再** 当前地图矩形 - 重新计算 PNG** 而是** 初始地图* ↓* 当前地图* ↓* 计算实际 X/Y 缩放* ↓* 计算实际位移* ↓* 同样的 transform 应用到 PNG*/function updateChinaTexture() {if (!chart) returnif (!initialState) {return}/*** * 获取当前三个参考点* */const currentCenter geoToPixel(GEO_ANCHORS.center)const currentX geoToPixel(GEO_ANCHORS.x)const currentY geoToPixel(GEO_ANCHORS.y)if (!currentCenter ||!currentX ||!currentY) {return}/*** * 计算初始参考距离* *//*** X 方向初始距离*/const initialDistanceX initialState.anchorX[0] -initialState.anchorCenter[0]/*** Y 方向初始距离*/const initialDistanceY initialState.anchorY[1] -initialState.anchorCenter[1]if (Math.abs(initialDistanceX) 0.0001 ||Math.abs(initialDistanceY) 0.0001) {return}/*** * 当前参考距离* */const currentDistanceX currentX[0] -currentCenter[0]const currentDistanceY currentY[1] -currentCenter[1]/*** * 得到地图真正的 X / Y 缩放比例* */const scaleX currentDistanceX /initialDistanceXconst scaleY currentDistanceY /initialDistanceY/*** 防止异常值*/if (!Number.isFinite(scaleX) ||!Number.isFinite(scaleY) ||scaleX 0 ||scaleY 0) {return}/*** * 计算纹理相对于初始锚点的位置* */const textureOffsetX initialState.textureCenter[0] -initialState.anchorCenter[0]const textureOffsetY initialState.textureCenter[1] -initialState.anchorCenter[1]/*** * 地图缩放后** 初始** Anchor -------- Texture** 缩放** Anchor ---------------- Texture** 所以纹理相对于 Anchor 的距离也需要一起缩放。* */const newTextureCenterX currentCenter[0] textureOffsetX * scaleXconst newTextureCenterY currentCenter[1] textureOffsetY * scaleY/*** * PNG 本身也跟着缩放* */const newTextureWidth initialState.textureWidth *scaleXconst newTextureHeight initialState.textureHeight *scaleY/*** 绘制*/renderTexture(newTextureCenterX,newTextureCenterY,newTextureWidth,newTextureHeight)}/*** * 真正绘制 PNG* */function renderTexture(centerX: number,centerY: number,width: number,height: number) {if (!chart) return/*** 根据中心点算左上角*/const left centerX -width / 2const top centerY -height / 2chart.setOption({graphic: {type: image,id: china-texture,left,top,/*** PNG 在下面** geo 在上面。** 这样青色边界可以覆盖 PNG。*/z: 1,silent: true,style: {image: chinaBg,width,height,opacity: 1}}})}/*** * 浏览器尺寸变化* */function handleResize() {if (!chart) returnchart.resize()/*** resize 后** 地图像素位置变了* 但当前地图的 roam 状态仍然存在。** 所以不能重新初始化 initialState。** 直接按照当前地图状态重新计算纹理。*/requestAnimationFrame(() {updateChinaTexture()})}/*** * mounted* */onMounted(async () {await nextTick()await initMap()window.addEventListener(resize,handleResize)})/*** * beforeUnmount* */onBeforeUnmount(() {window.removeEventListener(resize,handleResize)if (chart) {chart.off(georoam)chart.dispose()chart null}initialState null})/scriptstyle scoped.china-map-page {width: 100%;height: 100%;min-height: 600px;overflow: hidden;background: #000;position: relative;}.china-map {width: 100%;height: 100%;}/style