Skip to content

Кастомизация карты

html
<yandex-map
  :settings="{
    location: {
      center,
      zoom,
    },
    theme,
    showScaleInCopyrights: true,
  }"
  :width="width"
  :height="height"
>
  <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
          title="Вода"
          :enabled-controls="['color', 'opacity', 'scale']"
          :change-handler="(type, diff) => changeColor(['water'], type, diff)"
        />
        <layers-customization-control
          title="Земля"
          :enabled-controls="['color', 'opacity']"
          :change-handler="(type, diff) => changeColor(['landscape', 'admin', 'land', 'transit'], type, diff)"
        />
        <layers-customization-control
          title="Дороги"
          :enabled-controls="['color', 'opacity', 'scale']"
          :change-handler="(type, diff) => changeColor(['road'], type, diff)"
        />
        <layers-customization-control
          title="Строения"
          :enabled-controls="['color', 'opacity']"
          :change-handler="(type, diff) => changeColor(['building'], type, diff)"
        />
      </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>

Сделано с ♥ под лицензией MIT.