由于地域限制,一般在俄语区只能使用Yandex做地图展示,这里介绍yandex 常用的一些api
由于很多资料和ai都是2.1的版本,顾选择了较老的版本2.1做开发,最新更新到v3的版本
1.初始化
引入资源
js
<script src="https://api-maps.yandex.ru/2.1/?lang=zh_CN&onload=initMap" defer></script>
<div id="map"></div>
初始化
- 第一个参数为div的id
js
let map;
// 初始化地图
function initMap() {
const mapCenter = [31.0, 114.0]; // 中国中南部,让多个城市可见
map = new ymaps.Map("map", {
center: mapCenter,
zoom: 5
});
}

封装初始化
为了方便维护,我们封装一下 yandex的初始化。
这里通过动态插入js方式初始化
注意原来的onload 通过这种方式无法正常触发。可以正常同步方法编写代码即可。如在:
map = new (window as any).ymaps.Map(...)初始化后下面接着写需要初始化的逻辑
html
<template>
<div class="main-container" ref="mapContainer" v-loading="loading">
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
const props = defineProps({
apiKey: {
type: String,
required: true,
},
})
const loading = ref(false)
let map: any | null = null
const mapContainer = ref<HTMLElement | null>(null)
// 检查Yandex Maps是否已加载
const isYandexMapsLoaded = (): boolean => {
return typeof (window as any).ymaps !== 'undefined'
}
const loadYandexMaps = (): Promise<void> => {
return new Promise((resolve, reject) => {
// 检查是否已经加载过
if (isYandexMapsLoaded()) {
resolve()
return
}
// 创建script标签
const script = document.createElement('script')
const apiKey = props.apiKey // //
script.src = `https://api-maps.yandex.ru/2.1/?apikey=${apiKey}&lang=ru_RU`
script.type = 'text/javascript'
script.defer = true
// 加载完成回调
script.onload = () => {
;(window as any).ymaps.ready(() => {
resolve()
})
}
script.onerror = (error) => {
console.error('Failed to load Yandex Maps API', error)
reject(error)
}
document.head.appendChild(script)
})
}
// 初始化地图
const initMap = () => {
if (!mapContainer.value || !isYandexMapsLoaded()) return
let initLat = 55.751244
let initLng = 37.618423
let initZoom = 15
// 创建地图实例
map = new (window as any).ymaps.Map(mapContainer.value, {
center: [initLat, initLng], // 莫斯科坐标作为默认值
zoom: initZoom,
controls: ['fullscreenControl']
})
// 在这里正常写同步的代码
}
// 组件卸载时清理地图实例
onUnmounted(() => {
if (map) {
map.destroy()
map = null
}
})
onMounted(async () => {
loading.value = true
try {
await loadYandexMaps()
initMap()
loading.value = false
} catch (error) {
console.error('Error onMounted:', error)
loading.value = false
}
})
</script>
<style lang="scss" scoped>
.main-container {
position: relative;
width: 100%;
height: calc(100vh - 70px);
}
.loading {
text-align: center;
padding: 20px;
color: #909399;
}
</style>
2.标记
系统自带标记
- 第一个参数为坐标
- 第二个为弹框内容
js
const placemark = new ymaps.Placemark(
[39.9042, 116.4074],
{ balloonContent: '<h3>北京站点</h3><p>这是首都核心站点,功能齐全。</p>' },
);

点击效果 
自定义标记图标
由于自带的图标好丑,一般使用美工设计的icon。
- 通过参数iconLayout 指定渲染类型
- 通过参数iconImageHref 设置对应的图片
js
myPlacemarkWithContent = new ymaps.Placemark(
[39.9042, 116.4074],
{
hintContent: "A custom placemark icon with contents",
balloonContent: "This one --- for Christmas",
},
{
iconLayout: "default#imageWithContent",
iconImageHref: "dashboard/local1.png", // 自定义图片
// The size of the placemark.
iconImageSize: [40, 46],
iconImageOffset: [-24, -24],
iconContentOffset: [15, 15],
}
);
map.geoObjects.add(myPlacemarkWithContent);

图标追加信息显示
通过ymaps.templateLayoutFactory.createClass 我们可以在图标基础上追加信息
js
MyIconContentLayout = ymaps.templateLayoutFactory.createClass(
'<div style="color: #FFFFFF; font-weight: bold;">$[properties.iconContent]</div>'
),
myPlacemarkWithContent = new ymaps.Placemark(
[39.9042, 116.4074],
{
hintContent: "A custom placemark icon with contents",
balloonContent: "This one --- for Christmas",
iconContent: "M",
},
{
iconLayout: "default#imageWithContent",
// Custom image for the placemark icon.
iconImageHref: "dashboard/local1.png",
// The size of the placemark.
iconImageSize: [40, 46],
iconImageOffset: [-24, -24],
iconContentOffset: [15, 15],
iconContentLayout: MyIconContentLayout,
}
);
map.geoObjects.add(myPlacemarkWithContent);

3.汇总显示
系统自带Clusterer
系统自动Clusterer
js
var myMap = new ymaps.Map(
"map",
{
center: [55.751574, 37.573856],
zoom: 9,
},
{
searchControlProvider: "yandex#search",
}
),
clusterer = new ymaps.Clusterer({
preset: "islands#invertedVioletClusterIcons",
clusterHideIconOnBalloonOpen: false,
geoObjectHideIconOnBalloonOpen: false,
});
var getPointData = function (index) {
return {
balloonContentBody:
"placemark <strong>balloon " + index + "</strong>",
clusterCaption: "placemark <strong>" + index + "</strong>",
};
},
getPointOptions = function () {
return {
preset: "islands#violetIcon",
};
},
points = [
[55.831903, 37.411961],
[55.763338, 37.565466],
[55.763338, 37.565466],
[55.744522, 37.616378],
[55.780898, 37.642889],
[55.793559, 37.435983],
[55.800584, 37.675638],
[55.716733, 37.589988],
[55.775724, 37.56084],
[55.822144, 37.433781],
[55.87417, 37.669838],
[55.71677, 37.482338],
[55.78085, 37.75021],
[55.810906, 37.654142],
[55.865386, 37.713329],
[55.847121, 37.525797],
[55.778655, 37.710743],
[55.623415, 37.717934],
[55.863193, 37.737],
[55.86677, 37.760113],
[55.698261, 37.730838],
[55.6338, 37.564769],
[55.639996, 37.5394],
[55.69023, 37.405853],
[55.77597, 37.5129],
[55.775777, 37.44218],
[55.811814, 37.440448],
[55.751841, 37.404853],
[55.627303, 37.728976],
[55.816515, 37.597163],
[55.664352, 37.689397],
[55.679195, 37.600961],
[55.673873, 37.658425],
[55.681006, 37.605126],
[55.876327, 37.431744],
[55.843363, 37.778445],
[55.875445, 37.549348],
[55.662903, 37.702087],
[55.746099, 37.434113],
[55.83866, 37.712326],
[55.774838, 37.415725],
[55.871539, 37.630223],
[55.657037, 37.571271],
[55.691046, 37.711026],
[55.803972, 37.65961],
[55.616448, 37.452759],
[55.781329, 37.442781],
[55.844708, 37.74887],
[55.723123, 37.406067],
[55.858585, 37.48498],
],
geoObjects = [];
for (var i = 0, len = points.length; i < len; i++) {
geoObjects[i] = new ymaps.Placemark(
points[i],
getPointData(i),
getPointOptions()
);
}
clusterer.add(geoObjects);
myMap.geoObjects.add(clusterer);
myMap.setBounds(clusterer.getBounds(), {
checkZoomRange: true,
});




验证自带clusterer的瓶颈
通过随机方法创建4w节点
html
<template>
<div class="main-container" ref="mapContainer" v-loading="loading">
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
const props = defineProps({
apiKey: {
type: String,
required: true,
},
})
const loading = ref(false)
let map: any | null = null
const mapContainer = ref<HTMLElement | null>(null)
// 检查Yandex Maps是否已加载
const isYandexMapsLoaded = (): boolean => {
return typeof (window as any).ymaps !== 'undefined'
}
const loadYandexMaps = (): Promise<void> => {
return new Promise((resolve, reject) => {
// 检查是否已经加载过
if (isYandexMapsLoaded()) {
resolve()
return
}
// 创建script标签
const script = document.createElement('script')
const apiKey = props.apiKey // //
script.src = `https://api-maps.yandex.ru/2.1/?apikey=${apiKey}&lang=ru_RU`
script.type = 'text/javascript'
script.defer = true
// 加载完成回调
script.onload = () => {
;(window as any).ymaps.ready(() => {
resolve()
})
}
script.onerror = (error) => {
console.error('Failed to load Yandex Maps API', error)
reject(error)
}
document.head.appendChild(script)
})
}
const getRandomData = () => {
const COUNT = 40000 // 300000
const locations = []
// 深圳市中心坐标范围(大致)
const baseLat = 55.751244
const baseLng = 37.618423
for (let i = 0; i < COUNT; i++) {
const lat = baseLat + (Math.random() - 0.5) * 30.5 // +-0.25范围
const lng = baseLng + (Math.random() - 0.5) * 100.5
locations.push([ parseFloat(lat.toFixed(6)),
parseFloat(lng.toFixed(6))])
}
return locations
} // 初始化地图
const initMap = () => {
if (!mapContainer.value || !isYandexMapsLoaded()) return
let initLat = 55.751244
let initLng = 37.618423
let initZoom = 15
// 创建地图实例
map = new (window as any).ymaps.Map(mapContainer.value, {
center: [initLat, initLng], // 莫斯科坐标作为默认值
zoom: initZoom,
controls: ['fullscreenControl']
})
let clusterer = new (window as any).ymaps.Clusterer({
preset: "islands#invertedVioletClusterIcons",
clusterHideIconOnBalloonOpen: false,
geoObjectHideIconOnBalloonOpen: false,
});
let getPointData = function (index:number) {
return {
balloonContentBody:
"placemark <strong>balloon " + index + "</strong>",
clusterCaption: "placemark <strong>" + index + "</strong>",
};
},
getPointOptions = function () {
return {
preset: "islands#violetIcon",
};
};
let points = getRandomData()
let geoObjects = []
for (var i = 0, len = points.length; i < len; i++) {
geoObjects[i] = new ymaps.Placemark(
points[i],
getPointData(i),
getPointOptions()
);
}
clusterer.add(geoObjects);
map.geoObjects.add(clusterer);
map.setBounds(clusterer.getBounds(), {
checkZoomRange: true,
});
}
// 组件卸载时清理地图实例
onUnmounted(() => {
if (map) {
map.destroy()
map = null
}
})
onMounted(async () => {
loading.value = true
try {
await loadYandexMaps()
initMap()
loading.value = false
} catch (error) {
console.error('Error onMounted:', error)
loading.value = false
}
})
</script>
<style lang="scss" scoped>
.main-container {
position: relative;
width: 100%;
height: calc(100vh - 70px);
}
.loading {
text-align: center;
padding: 20px;
color: #909399;
}
</style>
在渲染时已经开始卡了。
这里可以参考之前谷歌地图的优化方案 juejin.cn/post/750111...
所以这里我们还是不直接使用自带的cluster,我们通过后台计算,前台自定义渲染返回的汇总数据
自定义渲染后台汇总数据
通过只绘制少量的汇总标记,来提升渲染效率
html
<template>
<div class="main-container" ref="mapContainer" v-loading="loading">
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
const props = defineProps({
apiKey: {
type: String,
required: true,
},
})
const loading = ref(false)
let map: any | null = null
const mapContainer = ref<HTMLElement | null>(null)
// 检查Yandex Maps是否已加载
const isYandexMapsLoaded = (): boolean => {
return typeof (window as any).ymaps !== 'undefined'
}
const loadYandexMaps = (): Promise<void> => {
return new Promise((resolve, reject) => {
// 检查是否已经加载过
if (isYandexMapsLoaded()) {
resolve()
return
}
// 创建script标签
const script = document.createElement('script')
const apiKey = props.apiKey // //
script.src = `https://api-maps.yandex.ru/2.1/?apikey=${apiKey}&lang=ru_RU`
script.type = 'text/javascript'
script.defer = true
// 加载完成回调
script.onload = () => {
;(window as any).ymaps.ready(() => {
resolve()
})
}
script.onerror = (error) => {
console.error('Failed to load Yandex Maps API', error)
reject(error)
}
document.head.appendChild(script)
})
}
const getRandomData = () => {
const COUNT = 10 // 300000
const locations = []
// 深圳市中心坐标范围(大致)
const baseLat = 55.751244
const baseLng = 37.618423
for (let i = 0; i < COUNT; i++) {
const lat = baseLat + (Math.random() - 0.5) * 35 // +-0.25范围
const lng = baseLng + (Math.random() - 0.5) * 35
locations.push({
id: `${i + 1}`,
type: i % 2 === 0 ? 1 : 0,
total: Math.random() * 100,
lat: parseFloat(lat.toFixed(6)),
lng: parseFloat(lng.toFixed(6))
})
}
return locations
}
const createClusterIcon = (count: number, type: string, size: number = 40) => {
const color = type === 'green' ? '#00BD24' : '#E0312C'
const svg = `
<svg width="${size}" height="${size}" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.280000">
<circle id="椭圆 201" cx="20" cy="20" r="20" fill="${color}" />
</g>
<g opacity="0.520000">
<circle id="椭圆 202" cx="20" cy="20" r="17" fill="${color}" />
</g>
<circle id="椭圆 203" cx="20" cy="20" r="14" fill="${color}" />
<text x="20" y="25" font-size="10" fill="#fff" text-anchor="middle" dominant-baseline="top">
${count}
</text>
</svg>
`
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
}
const createCluster = (title:string,own:number,total:number,lat:number,lng:number) => {
const color = own === 1 ? 'green' : 'red'
// 创建SVG格式的圆形图标
const iconHref = createClusterIcon(total, color, 40)
// 创建标记
const placemark = new (window as any).ymaps.Placemark(
[lat, lng],
{
hintContent: title,
// 保存完整的标记数据
markerData: {
title: title,
},
},
{
iconLayout: 'default#image', // 使用默认图片布局
iconImageHref: iconHref, // 引用 SVG 的 Data URI
iconImageSize: [40, 40], // 图标显示尺寸
iconImageOffset: [-40 / 2, -40 / 2], // 偏移量(中心点对齐坐标)
},
)
// 将标记添加到地图
map.geoObjects.add(placemark)
return placemark
}
const initMap = () => {
if (!mapContainer.value || !isYandexMapsLoaded()) return
let initLat = 55.751244
let initLng = 37.618423
let initZoom = 5
// 创建地图实例
map = new (window as any).ymaps.Map(mapContainer.value, {
center: [initLat, initLng], // 莫斯科坐标作为默认值
zoom: initZoom,
controls: ['fullscreenControl']
})
let points = getRandomData()
for (var i = 0, len = points.length; i < len; i++) {
const obj = points[i]
createCluster(obj.id,obj.type, obj.total, obj.lat,obj.lng)
}
}
// 组件卸载时清理地图实例
onUnmounted(() => {
if (map) {
map.destroy()
map = null
}
})
onMounted(async () => {
loading.value = true
try {
await loadYandexMaps()
initMap()
loading.value = false
} catch (error) {
console.error('Error onMounted:', error)
loading.value = false
}
})
</script>
<style lang="scss" scoped>
.main-container {
position: relative;
width: 100%;
height: calc(100vh - 70px);
}
.loading {
text-align: center;
padding: 20px;
color: #909399;
}
</style>

可以看到现在汇总的只是一个标记,并没有完全在地图全部生成。
4.自定义弹框

- 通过参数balloonLayout 自定义弹框内容
- 通过placemark.options.set('iconImageHref','xxx') 实时修改原来自定义的标记图片
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
#map {
width: 100%;
height: 600px;
border: 1px solid #ccc;
}
/* 气泡容器 - 用于三角定位 */
.balloon-wrapper {
position: relative;
overflow: visible;
/* 确保三角不被裁剪 */
}
/* 气泡内容区域 */
.custom-balloon {
background: #fff;
border-radius: 8px;
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.2);
padding: 15px;
width: 300px;
position: relative;
z-index: 2;
}
/* 左上角三角 - 指向左侧标记 */
.balloon-triangle {
position: absolute;
top: 20px;
/* 距离气泡顶部20px */
left: -10px;
/* 超出气泡左侧10px */
width: 0;
height: 0;
/* 三角形指向左侧(右向三角形) */
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 10px solid #fff;
/* 与气泡背景同色 */
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
/* 右侧阴影增强立体感 */
z-index: 1;
}
/* 加载状态样式 */
.loading {
text-align: center;
padding: 20px;
}
/* 错误状态样式 */
.error {
color: #e74c3c;
text-align: center;
padding: 20px;
}
/* 气泡头部样式 */
.balloon-header {
margin-bottom: 10px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.balloon-title {
margin: 0 0 5px 0;
color: #2c3e50;
}
/* 图片样式 */
.balloon-image {
width: 100%;
border-radius: 4px;
margin: 10px 0;
}
/* 统计信息样式 */
.balloon-stats {
display: flex;
gap: 15px;
margin: 10px 0;
font-size: 14px;
}
</style>
</head>
<body>
<div id="map"></div>
<!-- 引入 Yandex Maps API -->
<script src="https://api-maps.yandex.ru/2.1/?lang=zh_CN&onload=initMap" defer></script>
<script>
// 基础标记数据
const markersData = [
{ coords: [39.9042, 116.4074], title: "北京", id: "bj001" },
{ coords: [31.2304, 121.4737], title: "上海", id: "sh002" },
{ coords: [23.1291, 113.2644], title: "广州", id: "gz003" },
{ coords: [22.3193, 114.1694], title: "深圳", id: "sz004" }
];
let map;
let placemarks = [];
let dynamicBalloonLayout;
let activePlacemark = null;
const INITIAL_ZOOM = 5;
const DEFAULT_COLOR = '#3498db'; // 默认标记颜色
const ACTIVE_COLOR = '#e74c3c'; // 激活状态颜色
// 生成带数字的圆形图标
function generateCircleIconWithNumber(num, color = DEFAULT_COLOR) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const size = 50; // 图标大小
canvas.width = size;
canvas.height = size;
// 白色填充
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2 - 2, 0, 2 * Math.PI);
ctx.fill();
// 边框颜色
ctx.strokeStyle = color;
ctx.lineWidth = 3;
ctx.stroke();
// 数字颜色
ctx.fillStyle = color;
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(num.toString(), size / 2, size / 2);
return canvas.toDataURL();
}
// 模拟API请求获取详细数据
async function fetchMarkerDetails(markerId) {
return new Promise((resolve) => {
setTimeout(() => {
const mockData = {
"bj001": {
description: "北京是中国的首都,政治、文化和国际交流中心。",
population: "约2154万",
area: "16,410.54平方公里",
image: "https://picsum.photos/id/1016/400/200",
temperature: "18°C",
updateTime: "2023-10-01 12:00"
},
"sh002": {
description: "上海是中国的经济、金融中心,国际化大都市。",
population: "约2487万",
area: "6,340.5平方公里",
image: "https://picsum.photos/id/1015/400/200",
temperature: "20°C",
updateTime: "2023-10-01 12:10"
},
"gz003": {
description: "广州是华南地区的经济中心和交通枢纽。",
population: "约1530万",
area: "7,434.4平方公里",
image: "https://picsum.photos/id/1018/400/200",
temperature: "25°C",
updateTime: "2023-10-01 12:05"
},
"sz004": {
description: "深圳是中国的科技创新中心,新兴一线城市。",
population: "约1303万",
area: "1,997.47平方公里",
image: "https://picsum.photos/id/1019/400/200",
temperature: "24°C",
updateTime: "2023-10-01 12:15"
}
};
resolve(mockData[markerId]);
}, 800);
});
}
// 创建自定义气泡布局(左上角三角)
function createDynamicBalloonLayout() {
return ymaps.templateLayoutFactory.createClass(
// 气泡结构:外层容器 -> 内容区 + 左上角三角
'<div class="balloon-wrapper">' +
'<div id="dynamic-balloon-content" class="custom-balloon"></div>' +
'<div class="balloon-triangle"></div>' + // 左上角三角
'</div>',
{
// 构建气泡时触发
build: function () {
this.constructor.superclass.build.call(this);
const properties = this.getData().properties;
const contentContainer = document.getElementById('dynamic-balloon-content');
// 显示加载状态
contentContainer.innerHTML = `
<div class="loading">
<p>正在加载${properties.get('title')}的详细信息...</p>
<div style="margin-top:10px;">⏳</div>
</div>
`;
// 加载并渲染数据
this.loadAndRenderData(properties.get('id'), contentContainer);
},
// 加载数据并渲染内容
async loadAndRenderData(markerId, container) {
try {
const details = await fetchMarkerDetails(markerId);
const baseInfo = markersData.find(item => item.id === markerId);
// 渲染气泡内容
container.innerHTML = `
<div class="balloon-header">
<h3 class="balloon-title">${baseInfo.title}</h3>
<small>数据更新于: ${details.updateTime}</small>
</div>
<img src="${details.image}" class="balloon-image" />
<p>${details.description}</p>
<div class="balloon-stats">
<div>人口: ${details.population}</div>
<div>面积: ${details.area}</div>
</div>
<div>当前温度: ${details.temperature}</div>
<button onclick="handleBalloonAction('${markerId}')" style="margin-top:15px;padding:6px 12px;background:#3498db;color:white;border:none;border-radius:4px;cursor:pointer;">
查看更多信息
</button>
`;
} catch (error) {
// 错误状态
container.innerHTML = `
<div class="error">
<p>加载失败,请稍后重试</p>
</div>
`;
}
},
// 清除气泡时触发
clear: function () {
this.constructor.superclass.clear.call(this);
}
}
);
}
// 重置所有标记为默认样式
function resetAllMarkers() {
placemarks.forEach((placemark, index) => {
placemark.options.set('iconImageHref',
generateCircleIconWithNumber(index + 1, DEFAULT_COLOR));
});
activePlacemark = null;
}
// 设置标记为激活状态(红色)
function setMarkerActive(placemark, index) {
resetAllMarkers();
placemark.options.set('iconImageHref',
generateCircleIconWithNumber(index + 1, ACTIVE_COLOR));
activePlacemark = placemark;
}
// 重试加载数据
// 气泡内按钮点击事件
window.handleBalloonAction = function (markerId) {
const marker = markersData.find(item => item.id === markerId);
alert(`您点击了${marker.title}的"查看更多信息"按钮,ID: ${markerId}`);
};
// 关闭所有气泡
function closeAllBalloons() {
placemarks.forEach(placemark => {
if (placemark.balloon.isOpen()) {
placemark.balloon.close();
}
});
resetAllMarkers();
}
// 初始化地图
function initMap() {
const mapCenter = [31.0, 114.0];
map = new ymaps.Map("map", {
center: mapCenter,
zoom: INITIAL_ZOOM
});
// 点击地图空白处关闭气泡
map.events.add('click', function (e) {
const target = e.get('target');
if (target === map) {
closeAllBalloons();
}
});
createCustomMarkers();
}
// 创建自定义标记
function createCustomMarkers() {
dynamicBalloonLayout = createDynamicBalloonLayout();
markersData.forEach((data, index) => {
const placemark = new ymaps.Placemark(
data.coords,
{
title: data.title,
id: data.id
},
{
iconLayout: 'default#image',
iconImageHref: generateCircleIconWithNumber(index + 1),
iconImageSize: [50, 50],
iconImageOffset: [-25, -25], // 图标中心点对准坐标点
balloonLayout: dynamicBalloonLayout,
// 气泡偏移量:向右移动100px,向上微调20px(确保三角指向标记)
balloonOffset: [20, -20],
balloonShadow: false, // 禁用默认阴影,使用自定义阴影
hideIconOnBalloonOpen: false
}
);
// 标记点击事件
placemark.events.add('click', function () {
setMarkerActive(placemark, index);
});
map.geoObjects.add(placemark);
placemarks.push(placemark);
});
}
</script>
</body>
</html>
5.踩坑事项
缩放与移动统一事件
map.events.add('boundschange' 可以用来统一监听缩放与移动逻辑,不建议事情其他自带防止冲突
js
map.events.add('boundschange', (event: any) => {
if (event.get('newZoom') !== event.get('oldZoom')) { // 缩放了
// console.log('进入缩放事件', event)
} else { // 只是移动
// console.log('进入移动事件', event)
}
})
浏览器宽度变化,地图不能识别
google可以自己自适应,yandex没有,需要实时监听容器变化
js
// 创建 ResizeObserver 监听容器尺寸变化
const resizeObserver = new ResizeObserver(
debounce((entries: any) => {
if(!mapContainer.value) {
return
}
for (let entry of entries) {
if(mapContainer.value === entry.target) {
map.container.fitToViewport()
break;
}
}
}, 1000),
)
// 开始监听容器
resizeObserver.observe(mapContainer.value)
动态修改 marker.options.set('iconImageSize','xx'),再次访问异常
yandex当通过marker.options.set('iconImageSize','xx')动态修改元素的物理渲染信息时候,会修改原来对象的引用,不能通过 全局引用的对象保留,需要重新遍历索引。
js
const selectedMarker = ref<any | null>(null) // 如全局记录了上次选中对象
selectedMarker = xxx // 点击选中赋值
selectedMarker.value.events.fire('click', {}) // 这里会报错
if (selectedMarker && selectedMarker.value) {
const storeId = getStoreIdByMarker(selectedMarker.value)
let marker = findMarkerByStoreId(storeId) // 每次重新再查找一遍
if (marker) {
marker.events.fire('click', {})
}
}
移动和放大时候与boundschange事件冲突
系统自带map.panTo()来实现移动和放大,但是会导致触发boundschange事件的事件。可以通过全局状态位来控制逻辑
js
let isPanToNow = false
const myPanTo = (targetCoords: any) => {
return new Promise((resolve, reject) => {
isPanToNow = true
// 平滑移动
map.panTo(targetCoords, { duration: 500 , flying: true }).then(() => {
// 再平滑移动
isPanToNow = false
resolve(null)
})
})
}
// 添加缩放 + 拖拽 事件监听
map.events.add('boundschange', (event: any) => {
if (isPanToNow) {
console.log('isPanToNow 为 true', isPanToNow)
return
}
})
主动触发标记点击事件
通过marker.events.fire('click', {}) 主动触发,注意标记必须是渲染完成的,目前没找到稳定的回调渲染方法钩子,暂时加settimout再下一次事件循环里再触发确保已经渲染
js
marker.events.fire('click', {})
也可以通过提前加载图片,确保渲染标记时候不会阻塞
js
const customIconUrl1 = "/dashboard/local1.png";
const customIconUrl2 = "/dashboard/local2.png";
// 预先加载图标图片的函数
function loadIconImage(url:string) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = url;
img.onload = () => resolve(url); // 图片加载完成
img.onerror = () => reject("图标加载失败");
});
}
onMounted(async () => {
loading.value = true
try {
await loadYandexMaps()
// 等待图标加载完成
await loadIconImage(customIconUrl1);
await loadIconImage(customIconUrl2);
initMap()
loading.value = false
} catch (error) {
console.error('Error onMounted:', error)
mapLoading.value = false // 发生错误时也关闭加载状态
}
})
6.源码地址
7.资料参考
官方api文档 v2.1 yandex.com/dev/jsapi-v...
官方api文档 v3 yandex.com/maps-api/do...