Кастомизация карты
vanilla.html
react.html
vue.html
common.css
common.ts
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/theme/material.css" />
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.js"></script>
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/mode/javascript/javascript.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<!-- To make the map appear, you must add your apikey -->
<script src="https://api-maps.yandex.ru/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="./common.ts"
></script>
<script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
import type {VectorCustomizationItem, VectorCustomizationElements} from '@yandex/ymaps3-types';
import {LOCATION, initialCustomization, CustomizationControl, generateColor} from './common';
window.map = null;
main();
async function main() {
// Waiting for all api elements to be loaded
await ymaps3.ready;
const {YMap, YMapDefaultSchemeLayer, YMapControls, YMapControl} = ymaps3;
let customization = initialCustomization;
// Create an editor for manual customization of the map
const editor = CodeMirror.fromTextArea(document.getElementById('code') as HTMLTextAreaElement, {
mode: 'javascript',
theme: 'material',
lineNumbers: true
});
// Set the initial size and value to the editor
editor.setSize('40%', '100%');
editor.setValue(JSON.stringify(initialCustomization, null, 2));
// Add an onchange event to the editor that will update the map customization
const editorChangeHandler = _.debounce(() => {
customization = JSON.parse(editor.getValue());
layer.update({customization});
}, 200);
editor.on('change', editorChangeHandler);
// Create a function that we will call when changing the customization of the map through the control
function updateCustomization() {
editor.setValue(JSON.stringify(customization, null, 2));
}
// Initialize the map
map = new YMap(
// Pass the link to the HTMLElement of the container
document.getElementById('app'),
// Pass the map initialization parameters
{location: LOCATION, showScaleInCopyrights: true}
);
// Add a map schema layer with a custom customization props
const layer = new YMapDefaultSchemeLayer({
customization
});
map.addChild(layer);
// Create a CustomizationControl to change the appearance of the road
const roadControl = new CustomizationControl({
title: 'Road',
changeColorHandler: createChangeColorHandler(['road'], 'geometry'),
changeOpacityHandler: createChangeOpacityHandler(['road'], 'geometry'),
changeScaleHandler: createChangeScaleHandler(['road'], 'geometry')
});
// Create a CustomizationControl to change the appearance of the water
const waterControl = new CustomizationControl({
title: 'Water',
changeColorHandler: createChangeColorHandler(['water'], 'geometry'),
changeOpacityHandler: createChangeOpacityHandler(['water'], 'geometry'),
changeScaleHandler: createChangeScaleHandler(['water'], 'geometry')
});
// Create a CustomizationControl to change the appearance of the ground
const groundControl = new CustomizationControl({
title: 'Ground',
changeColorHandler: createChangeColorHandler(['landscape', 'admin', 'land', 'transit'], 'geometry'),
changeOpacityHandler: createChangeOpacityHandler(['landscape', 'admin', 'land', 'transit'], 'geometry')
});
// Create a CustomizationControl to change the appearance of the building
const buildingControl = new CustomizationControl({
title: 'Building',
changeColorHandler: createChangeColorHandler(['building'], 'geometry'),
changeOpacityHandler: createChangeOpacityHandler(['building'], 'geometry')
});
// Create a shared container for custom CustomizationControl's and add it to the map
const controls = new YMapControls({position: 'top right', orientation: 'horizontal'});
map.addChild(controls);
// Add controls to the map
controls
.addChild(new YMapControl().addChild(waterControl).addChild(groundControl))
.addChild(new YMapControl().addChild(roadControl).addChild(buildingControl));
// A function that creates a handler for changing the color of geo objects
function createChangeColorHandler(controlTags: string[], controlElements: VectorCustomizationElements) {
return () => {
const customizationObject = customization.find(
(item) => typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
);
if (customizationObject) {
customizationObject.stylers[0]['color'] = generateColor();
} else {
const newTagObject: VectorCustomizationItem = {
tags: {any: controlTags},
elements: controlElements,
stylers: [{color: generateColor()}]
};
customization.push(newTagObject);
}
updateCustomization();
};
}
// A function that creates a handler to change the opacity of geo objects
function createChangeOpacityHandler(controlTags: string[], controlElements: VectorCustomizationElements) {
return (diff: number) => {
const customizationObject = customization.find(
(item) => typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
);
if (!customizationObject) {
const newTagObject: VectorCustomizationItem = {
tags: {any: controlTags},
elements: controlElements,
stylers: [{opacity: 0.5}]
};
customization.push(newTagObject);
} else if (customizationObject.stylers[0]['opacity'] === undefined) {
customizationObject.stylers[0]['opacity'] = 0.5;
} else {
customizationObject.stylers[0]['opacity'] = +(customizationObject.stylers[0]['opacity'] + diff).toFixed(
1
);
}
updateCustomization();
};
}
// A function that creates a handler to change the scale of geo objects
function createChangeScaleHandler(controlTags: string[], controlElements: VectorCustomizationElements) {
return (diff: number) => {
const customizationObject = customization.find(
(item) => typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
);
if (!customizationObject) {
const newTagObject: VectorCustomizationItem = {
tags: {any: controlTags},
elements: controlElements,
stylers: [{scale: 2}]
};
customization.push(newTagObject);
} else if (customizationObject.stylers[0]['scale'] === undefined) {
customizationObject.stylers[0]['scale'] = 2;
} else {
customizationObject.stylers[0]['scale'] = Math.floor(customizationObject.stylers[0]['scale'] + diff);
}
updateCustomization();
};
}
}
</script>
<!-- prettier-ignore -->
<style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; } </style>
<link rel="stylesheet" href="./common.css" />
</head>
<body>
<div class="container">
<div id="app"></div>
<textarea id="code"></textarea>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/theme/material.css" />
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.js"></script>
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/mode/javascript/javascript.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<!-- To make the map appear, you must add your apikey -->
<script src="https://api-maps.yandex.ru/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script
data-plugins="transform-modules-umd"
data-presets="react, typescript"
type="text/babel"
src="./common.ts"
></script>
<script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
import type {
VectorCustomization,
VectorCustomizationItem,
VectorCustomizationElements
} from '@yandex/ymaps3-types';
import {LOCATION, initialCustomization, CustomizationControl, generateColor} from './common';
window.map = null;
main();
async function main() {
// For each object in the JS API, there is a React counterpart
// To use the React version of the API, include the module @yandex/ymaps3-reactify
const [ymaps3React] = await Promise.all([ymaps3.import('@yandex/ymaps3-reactify'), ymaps3.ready]);
const reactify = ymaps3React.reactify.bindTo(React, ReactDOM);
const {YMap, YMapDefaultSchemeLayer, YMapControls, YMapControl} = reactify.module(ymaps3);
// Using ymaps3-rectify, we turn a custom CustomizationControl into a React component
const {CustomizationControl: CustomizationControlR} = reactify.module({CustomizationControl});
const {useEffect, useCallback, useMemo, useRef, useState} = React;
// Create an editor component for manual customization of the map
interface CodeAreaProps {
customization: VectorCustomization;
setCustomization: (value: VectorCustomization) => void;
}
const CodeArea = ({customization, setCustomization}: CodeAreaProps) => {
const codeMirrorRef = useRef(null);
const editorRef = useRef(null);
const editorChangeHandler = useMemo(
() =>
_.debounce((_, action) => {
// Add a check so that the event is triggered only when manually changing the customization of the map
if (action.origin !== 'setValue') {
setCustomization(JSON.parse(editorRef.current.getValue()));
}
}, 200),
[]
);
useEffect(() => {
editorRef.current = CodeMirror.fromTextArea(codeMirrorRef.current, {
mode: 'javascript',
theme: 'material',
lineNumbers: true
});
// Set the initial size to the editor
editorRef.current.setSize('40%', '100%');
// Add an onchange event to the editor that will update the map customization
editorRef.current.on('change', editorChangeHandler);
return () => {
editorRef.current.toTextArea(); // Clean the editor when unmounting
editorRef.current.off('change', editorChangeHandler);
};
}, []);
// Create a useEffect hook that will trigger when changing the customization of the map through the control
useEffect(() => {
if (JSON.stringify(customization, null, 2) !== editorRef.current.getValue()) {
editorRef.current.setValue(JSON.stringify(customization, null, 2));
}
}, [customization]);
return <textarea ref={codeMirrorRef} />;
};
function App() {
const [customization, setCustomization] = useState < VectorCustomization > initialCustomization;
// A function that creates a handler for changing the color of geo objects
const createChangeColorHandler = useCallback(
(controlTags: string[], controlElements: VectorCustomizationElements) => {
return () => {
setCustomization((customization) => {
const customizationObject = customization.find(
(item) =>
typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
);
if (customizationObject) {
customizationObject.stylers[0]['color'] = generateColor();
} else {
const newTagObject: VectorCustomizationItem = {
tags: {any: controlTags},
elements: controlElements,
stylers: [{color: generateColor()}]
};
customization.push(newTagObject);
}
return [...customization];
});
};
},
[]
);
// A function that creates a handler to change the opacity of geo objects
const createChangeOpacityHandler = useCallback(
(controlTags: string[], controlElements: VectorCustomizationElements) => {
return (diff: number) => {
setCustomization((customization) => {
const customizationObject = customization.find(
(item) =>
typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
);
if (!customizationObject) {
const newTagObject: VectorCustomizationItem = {
tags: {any: controlTags},
elements: controlElements,
stylers: [{opacity: 0.5}]
};
customization.push(newTagObject);
} else if (customizationObject.stylers[0]['opacity'] === undefined) {
customizationObject.stylers[0]['opacity'] = 0.5;
} else {
customizationObject.stylers[0]['opacity'] = +(
customizationObject.stylers[0]['opacity'] + diff
).toFixed(1);
}
return [...customization];
});
};
},
[]
);
// A function that creates a handler to change the scale of geo objects
const createChangeScaleHandler = useCallback(
(controlTags: string[], controlElements: VectorCustomizationElements) => {
return (diff: number) => {
setCustomization((customization) => {
const customizationObject = customization.find(
(item) =>
typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
);
if (!customizationObject) {
const newTagObject: VectorCustomizationItem = {
tags: {any: controlTags},
elements: controlElements,
stylers: [{scale: 2}]
};
customization.push(newTagObject);
} else if (customizationObject.stylers[0]['scale'] === undefined) {
customizationObject.stylers[0]['scale'] = 2;
} else {
customizationObject.stylers[0]['scale'] = Math.floor(
customizationObject.stylers[0]['scale'] + diff
);
}
return [...customization];
});
};
},
[]
);
return (
<div className={'container'}>
<YMap location={LOCATION} showScaleInCopyrights={true} ref={(x) => (map = x)}>
{/* Add a map scheme layer with a custom customization props */}
<YMapDefaultSchemeLayer customization={customization} />
{/* Add a shared container to the map for custom CustomizationControl's */}
<YMapControls position={'top right'} orientation={'horizontal'}>
<YMapControl>
{/* Add a CustomizationControl to the map to change the appearance of the water */}
<CustomizationControlR
title="Water"
changeColorHandler={createChangeColorHandler(['water'], 'geometry')}
changeOpacityHandler={createChangeOpacityHandler(['water'], 'geometry')}
changeScaleHandler={createChangeScaleHandler(['water'], 'geometry')}
/>
{/* Add a CustomizationControl to the map to change the appearance of the ground */}
<CustomizationControlR
title="Ground"
changeColorHandler={createChangeColorHandler(
['landscape', 'admin', 'land', 'transit'],
'geometry'
)}
changeOpacityHandler={createChangeOpacityHandler(
['landscape', 'admin', 'land', 'transit'],
'geometry'
)}
/>
</YMapControl>
<YMapControl>
{/* Add a CustomizationControl to the map to change the appearance of the road */}
<CustomizationControlR
title="Road"
changeColorHandler={createChangeColorHandler(['road'], 'geometry')}
changeOpacityHandler={createChangeOpacityHandler(['road'], 'geometry')}
changeScaleHandler={createChangeScaleHandler(['road'], 'geometry')}
/>
{/* Add a CustomizationControl to the map to change the appearance of the building */}
<CustomizationControlR
title="Building"
changeColorHandler={createChangeColorHandler(['building'], 'geometry')}
changeOpacityHandler={createChangeOpacityHandler(['building'], 'geometry')}
/>
</YMapControl>
</YMapControls>
</YMap>
<CodeArea customization={customization} setCustomization={setCustomization} />
</div>
);
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('app')
);
}
</script>
<!-- prettier-ignore -->
<style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; } </style>
<link rel="stylesheet" href="./common.css" />
</head>
<body>
<div id="app"></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/theme/material.css" />
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/lib/codemirror.js"></script>
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.62.0/mode/javascript/javascript.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<!-- To make the map appear, you must add your apikey -->
<script src="https://api-maps.yandex.ru/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="./common.ts"
></script>
<script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
import {LOCATION, initialCustomization, CustomizationControl, generateColor} from './common';
import type {VectorCustomization} from '@yandex/ymaps3-types';
window.map = null;
async function main() {
// For each object in the JS API, there is a Vue counterpart
// To use the Vue version of the API, include the module @yandex/ymaps3-vuefy
const [ymaps3Vue] = await Promise.all([ymaps3.import('@yandex/ymaps3-vuefy'), ymaps3.ready]);
const vuefy = ymaps3Vue.vuefy.bindTo(Vue);
const {YMap, YMapDefaultSchemeLayer, YMapControls, YMapControl} = vuefy.module(ymaps3);
// Using ymaps3-vuefy, we turn a custom CustomizationControl into a Vue component
const {CustomizationControl: CustomizationControlV} = vuefy.module({CustomizationControl});
//const customization = Vue.ref(initialCustomization);
const customization = Vue.shallowRef(initialCustomization);
// A function that creates a handler for changing the color of geo objects
const createChangeColorHandler = (controlTags, controlElements) => {
return () => {
if (!customization.value.some((item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags))) {
const newTagObject = {
tags: {any: controlTags},
elements: controlElements,
stylers: [{color: generateColor()}]
};
customization.value.push(newTagObject);
} else {
const customizationObject = customization.value.find(
(item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
);
customizationObject.stylers[0]['color'] = generateColor();
}
customization.value = [...customization.value];
};
};
// A function that creates a handler to change the opacity of geo objects
const createChangeOpacityHandler = (controlTags, controlElements) => {
return (diff) => {
if (!customization.value.some((item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags))) {
const newTagObject = {
tags: {any: controlTags},
elements: controlElements,
stylers: [{opacity: 0.5}]
};
customization.value.push(newTagObject);
} else {
const customizationObject = customization.value.find(
(item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
);
if (customizationObject.stylers[0]['opacity'] === undefined) {
customizationObject.stylers[0]['opacity'] = 0.5;
} else {
customizationObject.stylers[0]['opacity'] = +(
customizationObject.stylers[0]['opacity'] + diff
).toFixed(1);
}
}
customization.value = [...customization.value];
};
};
// A function that creates a handler to change the scale of geo objects
const createChangeScaleHandler = (controlTags, controlElements) => {
return (diff) => {
if (!customization.value.some((item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags))) {
const newTagObject = {
tags: {any: controlTags},
elements: controlElements,
stylers: [{scale: 2}]
};
customization.value.push(newTagObject);
} else {
const customizationObject = customization.value.find(
(item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
);
if (customizationObject.stylers[0]['scale'] === undefined) {
customizationObject.stylers[0]['scale'] = 2;
} else {
customizationObject.stylers[0]['scale'] = Math.floor(customizationObject.stylers[0]['scale'] + diff);
}
}
customization.value = [...customization.value];
};
};
const CodeArea = Vue.defineComponent({
props: ['customization'],
emits: ['change'],
setup(props, {emit}) {
const codeMirrorRef = (refHtmlTextareaElement: HTMLTextAreaElement) => {
if (editorRef.value) return;
editorRef.value = CodeMirror.fromTextArea(refHtmlTextareaElement, {
mode: 'javascript',
theme: 'material',
lineNumbers: true
});
editorRef.value.setSize('40%', '100%');
editorRef.value.on('change', editorChangeHandler);
Vue.nextTick().then(() => {
editorRef.value.setValue(JSON.stringify(props.customization, null, 2));
});
};
const editorRef = Vue.ref(null);
const editorChangeHandler = (_, action) => {
// Add a check so that the event is triggered only when manually changing the customization of the map
if (action.origin !== 'setValue') {
emit('change', JSON.parse(editorRef.value.getValue()));
}
};
Vue.onBeforeUnmount(() => {
if (editorRef.value) {
editorRef.value.toTextArea();
editorRef.value.off('change', editorChangeHandler);
}
});
Vue.watch(
() => props.customization,
(newCustomization) => {
if (JSON.stringify(newCustomization, null, 2) !== editorRef.value.getValue()) {
editorRef.value.setValue(JSON.stringify(newCustomization, null, 2));
}
},
{deep: true}
);
return {
codeMirrorRef,
editorRef,
editorChangeHandler
};
},
template: `<textarea :ref="codeMirrorRef"/>`
});
const app = Vue.createApp({
setup() {
const refMap = (ref) => {
window.map = ref?.entity;
};
const onChange = (newCustomization) => {
customization.value = newCustomization;
};
return {
LOCATION,
refMap,
customization,
createChangeColorHandler,
createChangeOpacityHandler,
createChangeScaleHandler,
onChange
};
},
components: {
YMap,
YMapDefaultSchemeLayer,
YMapControls,
YMapControl,
CustomizationControlV,
CodeArea
},
template: `
<div class="container">
<!-- Initialize the map and pass initialization parameters -->
<YMap :location="LOCATION" :showScaleInCopyrights="true" :ref="refMap">
<!-- Add a map scheme layer with a custom customization props -->
<YMapDefaultSchemeLayer :customization="customization"/>
<!-- Add a shared container to the map for custom CustomizationControl's -->
<YMapControls position="top right" orientation="horizontal">
<YMapControl>
<!-- Add a CustomizationControl to the map to change the appearance of the water -->
<CustomizationControlV
title="Water"
:changeColorHandler="createChangeColorHandler(['water'], 'geometry')"
:changeOpacityHandler="createChangeOpacityHandler(['water'], 'geometry')"
:changeScaleHandler="createChangeScaleHandler(['water'], 'geometry')"
/>
<!-- Add a CustomizationControl to the map to change the appearance of the ground -->
<CustomizationControlV
title="Ground"
:changeColorHandler="createChangeColorHandler(['landscape', 'admin', 'land', 'transit'], 'geometry')"
:changeOpacityHandler="createChangeOpacityHandler(['landscape', 'admin', 'land', 'transit'], 'geometry')"
/>
</YMapControl>
<YMapControl>
<!-- Add a CustomizationControl to the map to change the appearance of the road -->
<CustomizationControlV
title="Road"
:changeColorHandler="createChangeColorHandler(['road'], 'geometry')"
:changeOpacityHandler="createChangeOpacityHandler(['road'], 'geometry')"
:changeScaleHandler="createChangeScaleHandler(['road'], 'geometry')"
/>
<!-- Add a CustomizationControl to the map to change the appearance of the building -->
<CustomizationControlV
title="Building"
:changeColorHandler="createChangeColorHandler(['building'], 'geometry')"
:changeOpacityHandler="createChangeOpacityHandler(['building'], 'geometry')"
/>
</YMapControl>
</YMapControls>
</YMap>
<CodeArea :customization="customization" @change="onChange"/>
</div>
`
});
app.mount('#app');
}
main();
</script>
<!-- prettier-ignore -->
<style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; } </style>
<link rel="stylesheet" href="./common.css" />
</head>
<body>
<div id="app"></div>
</body>
</html>
.container {
display: flex;
height: 100%;
}
.customizationControl {
padding: 10px 0;
margin: 0 15px;
display: flex;
flex-direction: column;
gap: 15px;
}
.customizationControl:first-child {
border-bottom: 1px solid #c0c0c1;
}
.customizationControl__title {
text-align: center;
font-size: 18px;
font-weight: 600;
}
.customizationControl__section {
display: flex;
align-items: center;
gap: 5px;
}
.customizationControl__sectionTitle {
flex-basis: 50%;
}
.customizationControl__btn {
border: none;
cursor: pointer;
min-width: 30px;
padding: 8px;
color: rgb(255, 255, 255);
border-radius: 8px;
background-color: rgba(0, 122, 252, 0.9);
transition: background-color 0.2s;
}
.customizationControl__btn:hover {
background-color: rgb(0, 110, 252);
}
.customizationControl__btn:active {
background-color: rgb(0, 122, 252);
}
import type codemirrorTypes from 'codemirror';
import type lodashTypes from 'lodash';
import type {VectorCustomization, YMapLocationRequest} from '@yandex/ymaps3-types';
declare global {
const CodeMirror: typeof codemirrorTypes;
const _: typeof lodashTypes;
}
export const LOCATION: YMapLocationRequest = {
center: [37.623082, 55.75254], // starting position [lng, lat]
zoom: 11 // starting zoom
};
// Starting parameters of map customization
export const initialCustomization: VectorCustomization = [
{
tags: {
any: ['road']
},
elements: 'geometry',
stylers: [
{
color: '#4E4E4E'
}
]
},
{
tags: {
any: ['water']
},
elements: 'geometry',
stylers: [
{
color: '#000000'
}
]
},
{
tags: {
any: ['landscape', 'admin', 'land', 'transit']
},
elements: 'geometry',
stylers: [
{
color: '#212121'
}
]
},
{
tags: {
any: ['building']
},
elements: 'geometry',
stylers: [
{
color: '#757474'
}
]
}
];
// Function generates a random color in HEX format
export const generateColor = () => {
return '#' + Math.floor(Math.random() * 16777215).toString(16);
};
// Create a custom control to change the customization of the map
export let CustomizationControl = null;
interface CustomizationControlProps {
title: string;
changeColorHandler?: () => void;
changeOpacityHandler?: (diff: number) => void;
changeScaleHandler?: (diff: number) => void;
}
// Wait for the api to load to access the entity system (YMapComplexEntity)
ymaps3.ready.then(() => {
class CustomizationControlClass extends ymaps3.YMapComplexEntity<CustomizationControlProps> {
private _element: HTMLDivElement;
private _detachDom: () => void;
constructor(props: CustomizationControlProps) {
super(props);
this._element = this._createElement(props);
}
// Creates a control's DOM element based on the passed properties
_createElement(props: CustomizationControlProps) {
const {title, changeColorHandler, changeOpacityHandler, changeScaleHandler} = props;
const customizationElement = document.createElement('div');
customizationElement.className = 'customizationControl';
const myControlTitle = document.createElement('div');
myControlTitle.className = 'customizationControl__title';
myControlTitle.textContent = title;
customizationElement.appendChild(myControlTitle);
if (changeColorHandler) {
const colorSection = this._createControlSection('Color:', ['random'], [changeColorHandler]);
customizationElement.appendChild(colorSection);
}
if (changeOpacityHandler) {
const opacitySection = this._createControlSection(
'Opacity:',
['-', '+'],
[() => changeOpacityHandler(-0.1), () => changeOpacityHandler(0.1)]
);
customizationElement.appendChild(opacitySection);
}
if (changeScaleHandler) {
const scaleSection = this._createControlSection(
'Scale:',
['-', '+'],
[() => changeScaleHandler(-1), () => changeScaleHandler(1)]
);
customizationElement.appendChild(scaleSection);
}
return customizationElement;
}
_createControlSection(title: string, buttonLabels: string[], onClickHandler: EventListener[]) {
const section = document.createElement('div');
section.className = 'customizationControl__section';
const sectionTitle = document.createElement('div');
sectionTitle.className = 'customizationControl__sectionTitle';
sectionTitle.textContent = title;
section.appendChild(sectionTitle);
for (let i = 0; i < buttonLabels.length; i++) {
const sectionButton = document.createElement('button');
sectionButton.className = 'customizationControl__btn';
sectionButton.textContent = buttonLabels[i];
sectionButton.addEventListener('click', onClickHandler[i]);
section.appendChild(sectionButton);
}
return section;
}
// Handler for attaching the control to the map
_onAttach() {
this._detachDom = ymaps3.useDomContext(this, this._element, this._element);
}
// Handler for detaching control from the map
_onDetach() {
this._detachDom();
this._detachDom = null;
this._element = null;
}
}
CustomizationControl = CustomizationControlClass;
});