Кастомизация карты
html
<yandex-map
:height="height"
:settings="{
location: {
center,
zoom,
},
theme,
showScaleInCopyrights: true,
}"
:width="width"
>
<yandex-map-default-scheme-layer :settings="{ customization }"/>
<yandex-map-controls :settings="{ position: 'top right', orientation: 'vertical' }">
<yandex-map-control>
<div
class="controls"
:style="{ '--map-height': height }"
>
<layers-customization-control
:change-handler="(type, diff) => changeColor(['water'], type, diff)"
:enabled-controls="['color', 'opacity', 'scale']"
title="Вода"
/>
<layers-customization-control
:change-handler="(type, diff) => changeColor(['landscape', 'admin', 'land', 'transit'], type, diff)"
:enabled-controls="['color', 'opacity']"
title="Земля"
/>
<layers-customization-control
:change-handler="(type, diff) => changeColor(['road'], type, diff)"
:enabled-controls="['color', 'opacity', 'scale']"
title="Дороги"
/>
<layers-customization-control
:change-handler="(type, diff) => changeColor(['building'], type, diff)"
:enabled-controls="['color', 'opacity']"
title="Строения"
/>
</div>
</yandex-map-control>
</yandex-map-controls>
</yandex-map>
<textarea
class="editor"
:value="JSON.stringify(customization, undefined, 2)"
@change="(e) => changeCustomization(e as InputEvent)"
/>
ts
import { YandexMap, YandexMapControl, YandexMapControls, YandexMapDefaultSchemeLayer } from 'vue-yandex-maps';
import type { CustomizationControls } from './LayersCustomizationControl.vue';
import LayersCustomizationControl from './LayersCustomizationControl.vue';
import type { VectorCustomization, VectorCustomizationItem } from '@yandex/ymaps3-types';
import { shallowRef, triggerRef } from 'vue';
const customization = shallowRef<VectorCustomization>([
{
tags: {
any: ['water'],
},
elements: 'geometry',
stylers: [
{
color: '#000000',
},
],
},
{
tags: {
any: ['landscape', 'admin', 'land', 'transit'],
},
elements: 'geometry',
stylers: [
{
color: '#212121',
},
],
},
{
tags: {
any: ['road'],
},
elements: 'geometry',
stylers: [
{
color: '#4E4E4E',
},
],
},
{
tags: {
any: ['building'],
},
elements: 'geometry',
stylers: [
{
color: '#757474',
},
],
},
]);
const changeCustomization = (event: InputEvent) => {
try {
customization.value = JSON.parse((event.target as HTMLTextAreaElement).value);
}
catch (e) {
console.error(e);
}
};
// Function generates a random color in HEX format
const generateColor = () => `#${ Math.floor(Math.random() * 16777215)
.toString(16) }`;
const changeColor = (controlTags: string[], type: CustomizationControls, diff?: number) => {
const customizationObject = customization.value.find(
(item: any) => typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags),
);
if (type === 'color') {
if (customizationObject) {
(customizationObject.stylers as any[])[0].color = generateColor();
}
else {
const newTagObject: VectorCustomizationItem = {
tags: { any: controlTags },
elements: 'geometry',
stylers: [{ color: generateColor() }],
};
customization.value.push(newTagObject);
}
}
else if (type === 'opacity') {
if (!customizationObject) {
const newTagObject: VectorCustomizationItem = {
tags: { any: controlTags },
elements: 'geometry',
stylers: [{ opacity: 0.5 }],
};
customization.value.push(newTagObject);
}
else if (Array.isArray(customizationObject.stylers)) {
if (customizationObject.stylers[0].opacity === undefined) {
customizationObject.stylers[0].opacity = 0.5;
}
else {
customizationObject.stylers[0].opacity = +(customizationObject.stylers[0].opacity + diff!).toFixed(
1,
);
if (customizationObject.stylers[0].opacity > 1) customizationObject.stylers[0].opacity = 1;
if (customizationObject.stylers[0].opacity < 0) customizationObject.stylers[0].opacity = 0;
}
}
}
else if (type === 'scale') {
if (!customizationObject) {
const newTagObject: VectorCustomizationItem = {
tags: { any: controlTags },
elements: 'geometry',
stylers: [{ scale: 2 }],
};
customization.value.push(newTagObject);
}
else if (Array.isArray(customizationObject.stylers)) {
if (customizationObject.stylers[0].scale === undefined) {
customizationObject.stylers[0].scale = 2;
}
else {
customizationObject.stylers[0].scale = Math.floor(customizationObject.stylers[0].scale + diff!);
}
}
}
triggerRef(customization);
};
css
<style scoped>
.container {
display: flex;
height: 100%;
}
.controls {
overflow: auto;
max-height: calc(var(--map-height) - 24px);
}
.editor {
width: 100%;
min-height: 300px;
margin-top: 20px;
padding: 10px;
}
</style>
vue
<template>
<div class="customization-control">
<div class="customization-control_title">
{{ title }}
</div>
<div
v-for="section in getSections"
:key="section.title"
class="customization-control_section"
>
<div class="customization-control_section_title">
{{ section.title }}
</div>
<div
v-for="(btn, btnIndex) in section.buttons"
:key="btn"
class="customization-control_section_btn"
@click="section.handlers[btnIndex]()"
>
{{ btn }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
export type CustomizationControls = 'color' | 'opacity' | 'scale';
interface CustomizationControlProps {
title: string;
enabledControls: CustomizationControls[];
changeHandler: (control: CustomizationControls, diff?: number) => void;
}
type CustomizationControlSection = { title: string } & (
{
buttons: [string];
handlers: [() => void];
} | {
buttons: [string, string];
handlers: [() => void, () => void];
});
const props = defineProps<CustomizationControlProps>();
const getSections = computed(() => {
const sections: CustomizationControlSection[] = [];
if (props.enabledControls.includes('color')) {
sections.push({
title: 'Цвет:',
buttons: ['Случайный'],
handlers: [() => props.changeHandler('color')],
});
}
if (props.enabledControls.includes('opacity')) {
sections.push({
title: 'Прозрачность:',
buttons: ['-', '+'],
handlers: [() => props.changeHandler('opacity', -0.1), () => props.changeHandler('opacity', 0.1)],
});
}
if (props.enabledControls.includes('scale')) {
sections.push({
title: 'Увеличение:',
buttons: ['-', '+'],
handlers: [() => props.changeHandler('scale', -1), () => props.changeHandler('scale', 1)],
});
}
return sections;
});
</script>
<style scoped>
.customization-control {
padding: 10px 0;
margin: 0 15px;
display: flex;
flex-direction: column;
gap: 15px;
}
.customization-control:not(:first-child) {
border-top: 1px solid rgba(170, 170, 170, 0.15);
}
.customization-control_title {
text-align: center;
font-size: 18px;
font-weight: 600;
}
.customization-control_section {
display: flex;
align-items: center;
gap: 5px;
}
.customization-control_section_title {
flex-basis: 50%;
}
.customization-control_section_btn {
cursor: pointer;
padding: 8px;
color: rgb(255, 255, 255);
border-radius: 8px;
background-color: rgba(0, 122, 252, 0.9);
transition: background-color 0.2s;
user-select: none;
}
.customization-control_section_btn:hover {
background-color: rgb(0, 110, 252);
}
.customization-control_section_btn:active {
background-color: rgb(0, 122, 252);
}
</style>