1-й Покровский пр., 5, Котельники этаж 1
8 (905) 777-77-77
Пн-Вс 10:00 - 22:00
<div class="map-container">
<div id="mapyandex"></div>
</div>
<script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU" type="text/javascript"></script>
<script>
const CONFIG = {
MAP_ZOOM: 12,
SCROLL_SPEED: 500,
MOBILE_BREAKPOINT: 960
};
const state = {
coordinates: [],
currentMap: null,
geoCollection: null,
points: [],
selectedIndex: 0,
isMapReady: false
};
const DOM = {
mapElement: document.getElementById('mapyandex'),
mapContainer: document.querySelector('.map-container'),
tagsInput: null,
adressMain: null
};
function createUI() {
const adressWrapper = document.createElement('div');
adressWrapper.className = 'adress-wrapper-wrp';
adressWrapper.innerHTML = `
<div class="adress-wrapper-input t-text">
<input type="text" class="t-input tags" placeholder="Город" id="citySearch">
<div class="autocomplete-list" id="autocompleteList"></div>
</div>
<div class="adress-wrapper-main t-text" id="adressList"></div>
`;
DOM.mapElement.after(adressWrapper);
DOM.tagsInput = document.getElementById('citySearch');
DOM.adressMain = document.getElementById('adressList');
DOM.autocompleteList = document.getElementById('autocompleteList');
}
class Autocomplete {
constructor(input, source) {
this.input = input;
this.source = source;
this.list = document.getElementById('autocompleteList');
this.selectedIndex = -1;
this.isOpen = false;
this.input.addEventListener('input', () => this.handleInput());
this.input.addEventListener('focus', () => this.handleFocus());
this.input.addEventListener('blur', () => this.handleBlur());
this.input.addEventListener('keydown', (e) => this.handleKeydown(e));
document.addEventListener('click', (e) => {
if (!this.list.contains(e.target) && e.target !== this.input) {
this.hideList();
}
});
}
handleInput() {
const value = this.input.value.toLowerCase().trim();
const filtered = this.source.filter(item =>
item.toLowerCase().includes(value)
);
this.showList(filtered);
}
handleFocus() {
if (this.input.value.trim()) {
this.handleInput();
} else {
this.showList(this.source);
}
}
handleBlur() {
setTimeout(() => this.hideList(), 200);
}
handleKeydown(e) {
const items = this.list.querySelectorAll('.autocomplete-item');
if (items.length === 0) return;
switch(e.key) {
case 'ArrowDown':
e.preventDefault();
this.selectedIndex = Math.min(this.selectedIndex + 1, items.length - 1);
this.updateSelection(items);
break;
case 'ArrowUp':
e.preventDefault();
this.selectedIndex = Math.max(this.selectedIndex - 1, -1);
this.updateSelection(items);
break;
case 'Enter':
e.preventDefault();
if (this.selectedIndex >= 0) {
items[this.selectedIndex].click();
}
break;
case 'Escape':
this.hideList();
break;
}
}
updateSelection(items) {
items.forEach((item, index) => {
item.classList.toggle('active', index === this.selectedIndex);
});
if (this.selectedIndex >= 0) {
items[this.selectedIndex].scrollIntoView({ block: 'nearest' });
}
}
showList(items) {
if (items.length === 0) {
this.hideList();
return;
}
this.list.innerHTML = items.map((item, index) => `
<div class="autocomplete-item" data-index="${index}" data-value="${item}">
${item}
</div>
`).join('');
this.list.style.display = 'block';
this.isOpen = true;
this.selectedIndex = -1;
this.list.querySelectorAll('.autocomplete-item').forEach(el => {
el.addEventListener('mousedown', (e) => {
e.preventDefault();
});
el.addEventListener('click', () => {
this.input.value = el.dataset.value;
this.hideList();
this.input.focus();
handleCitySelect(state.points);
});
});
}
hideList() {
this.list.style.display = 'none';
this.isOpen = false;
this.selectedIndex = -1;
}
}
function getMapPoints() {
const points = document.querySelectorAll('.uc-map-point');
const result = [];
points.forEach(point => {
const title = point.querySelector('.t-section__title')?.textContent?.trim() || '';
const items = point.querySelectorAll('.t-slds__item');
const pointData = { title, items: [] };
items.forEach(item => {
const name = item.querySelector('.t-heading')?.innerHTML || '';
const coordText = item.querySelector('.t-descr')?.textContent || '';
const coords = coordText.split(',').map(c => c.trim());
const image = item.querySelector('.t-bgimg')?.getAttribute('data-original') || '';
const description = item.querySelector('.t-text')?.innerHTML || '';
pointData.items.push({
name,
coords,
image,
description,
balloonContent: `${name}<br>${description}`
});
});
result.push(pointData);
});
return result;
}
function handleCitySelect(points) {
const city = DOM.tagsInput.value.trim();
const selectedPoint = points.find(p => p.title === city);
if (selectedPoint) {
fillList(selectedPoint);
}
}
function fillList(pointData) {
DOM.adressMain.innerHTML = '';
DOM.mapElement.innerHTML = '';
state.coordinates = [];
state.isMapReady = false;
const adressItems = pointData.items.map((item, index) => {
const coordLine = [
item.coords[0],
item.coords[1],
item.name,
item.image,
item.balloonContent,
item.description
];
state.coordinates.push(coordLine);
return `
<div class="adress-item" data-tab-num="${index}">
<div class="adress-item-inner-wrap">
<div class="adress-title">${item.name}</div>
<div class="adress-descr">${item.description}</div>
</div>
</div>
`;
});
DOM.adressMain.innerHTML = adressItems.join('');
DOM.tagsInput.value = pointData.title;
document.querySelectorAll('.adress-item').forEach(item => {
item.addEventListener('click', handleItemClick);
});
const firstItem = document.querySelector('.adress-item[data-tab-num="0"]');
if (firstItem) {
firstItem.classList.add('activepoint');
state.selectedIndex = 0;
}
createMap();
}
function handleItemClick(e) {
const item = e.currentTarget;
const pointNum = parseInt(item.dataset.tabNum);
document.querySelectorAll('.adress-item').forEach(el => el.classList.remove('activepoint'));
item.classList.add('activepoint');
state.selectedIndex = pointNum;
if (state.coordinates[pointNum]) {
changePoint(pointNum);
}
if (window.innerWidth < CONFIG.MOBILE_BREAKPOINT) {
const mapYmaps = document.querySelector('#mapyandex ymaps');
if (mapYmaps) {
const top = mapYmaps.getBoundingClientRect().top + window.pageYOffset - 50;
window.scrollTo({ top, behavior: 'smooth' });
}
}
}
function createMap() {
if (state.coordinates.length === 0) return;
ymaps.ready(() => {
if (state.currentMap) {
state.currentMap.destroy();
state.currentMap = null;
}
const centerCoords = [state.coordinates[0][0], state.coordinates[0][1]];
state.currentMap = new ymaps.Map("mapyandex", {
center: centerCoords,
zoom: CONFIG.MAP_ZOOM,
controls: []
}, {
searchControlProvider: 'yandex#search',
suppressMapOpenBlock: true
});
state.currentMap.controls.add('zoomControl', {
position: {
right: 20,
top: 150
},
size: 'small'
});
state.geoCollection = new ymaps.GeoObjectCollection(null, {
preset: 'islands#yellowIcon'
});
state.coordinates.forEach((coord, index) => {
const placemark = new ymaps.Placemark(
[coord[0], coord[1]],
{
hintContent: coord[2],
balloonContent: coord[4],
},
{
iconLayout: 'default#imageWithContent',
openBalloonOnClick: false,
openHintOnHover: true,
hintTimeout: 300,
iconImageHref: coord[3],
iconImageSize: [50, 50],
iconImageOffset: [-25, -25],
iconContentOffset: [25, 25],
}
);
placemark.properties.set('index', index);
placemark.properties.set('coordIndex', index);
state.geoCollection.add(placemark);
});
state.currentMap.geoObjects.add(state.geoCollection);
state.geoCollection.options.set({
hintLayout: 'default#hint',
hintOffset: [0, -20],
hintTimeout: 200,
hintHideTimeout: 1000
});
state.geoCollection.events.add('click', function(e) {
const target = e.get('target');
const coords = target.geometry._coordinates;
const index = target.properties.get('coordIndex');
if (index !== undefined && state.coordinates[index]) {
state.currentMap.balloon.open(
coords,
state.coordinates[index][4]
);
const item = document.querySelector(`.adress-item[data-tab-num="${index}"]`);
if (item) {
document.querySelectorAll('.adress-item').forEach(el => el.classList.remove('activepoint'));
item.classList.add('activepoint');
state.selectedIndex = index;
const container = DOM.adressMain;
const itemOffset = item.offsetTop;
const containerScrollTop = container.scrollTop;
const containerOffset = container.offsetTop;
container.scrollTo({
top: containerScrollTop - containerOffset + itemOffset - 20,
behavior: 'smooth'
});
}
}
});
state.isMapReady = true;
setTimeout(() => {
if (state.currentMap) {
state.currentMap.container.fitToViewport();
}
}, 100);
});
}
function changePoint(index) {
if (!state.currentMap || !state.coordinates[index]) return;
const coords = [state.coordinates[index][0], state.coordinates[index][1]];
state.currentMap.balloon.open(coords, state.coordinates[index][4]);
setTimeout(() => {
state.currentMap.panTo([+coords[0], +coords[1]], {
delay: 2500,
flying: true
});
}, 200);
}
let resizeTimeout;
function handleResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (state.currentMap && state.isMapReady) {
state.currentMap.container.fitToViewport();
}
}, 200);
}
document.addEventListener('DOMContentLoaded', function() {
createUI();
state.points = getMapPoints();
if (state.points.length > 0) {
DOM.tagsInput.value = state.points[0].title;
const availableTags = state.points.map(p => p.title);
new Autocomplete(DOM.tagsInput, availableTags);
fillList(state.points[0]);
DOM.tagsInput.addEventListener('change', () => {
handleCitySelect(state.points);
});
}
window.addEventListener('resize', handleResize);
});
function openBalloonByIndex(index) {
if (state.currentMap && state.coordinates[index]) {
const coords = [state.coordinates[index][0], state.coordinates[index][1]];
state.currentMap.balloon.open(coords, state.coordinates[index][4]);
}
}
function getCurrentMap() {
return state.currentMap;
}
function getCoordinates() {
return state.coordinates;
}
window.openBalloonByIndex = openBalloonByIndex;
window.getCurrentMap = getCurrentMap;
window.getCoordinates = getCoordinates;
</script>
<style>
.uc-map-point {
display: none;
}
.adress-wrapper-input {
position: relative;
padding: 20px;
display: flex;
gap: 8px;
}
.adress-wrapper-input input {
border: 1px solid #e0e0e0;
height: 45px;
border-radius: 10px;
flex-grow:1;
font-size: 16px;
padding: 0 10px;
box-sizing: border-box;
outline: none;
}
.adress-wrapper-input input:focus {
border-color: #9a8a6e;
box-shadow: 0 0 0 2px rgba(154, 138, 110, 0.2);
}
.autocomplete-list {
display: none;
position: absolute;
top: calc(100% - 20px);
left: 10px;
right: 10px;
background: #ffffff;
border: 1px solid #cccccb;
border-radius: 5px;
max-height: 300px;
overflow-y: auto;
z-index: 9999999;
margin-top: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.autocomplete-item {
padding: 10px 15px;
cursor: pointer;
transition: background 0.2s ease;
color: #5d5d5d;
}
.autocomplete-item:hover,
.autocomplete-item.active {
background: #f5f0eb;
color: #000000;
}
.map-container {
height: 600px;
position: relative;
display: grid;
grid-template-columns: 1fr 380px;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 8px 32px rgba(0, 0, 0, 0.06), 0 24px 60px rgba(0, 0, 0, 0.04);
}
#mapyandex {
padding: 0;
margin: 0;
position: relative;
min-height: 400px;
}
.adress-wrapper-wrp {
height: inherit;
background-color: #fff;
}
.adress-wrapper-main {
top: 0;
right: 0;
z-index: 99;
height: inherit;
overflow: auto;
max-height: 535px;
color: #000;
}
.adress-wrapper-main::-webkit-scrollbar {
width: 6px;
height: 6px;
background: #f0f0f0;
}
.adress-wrapper-main::-webkit-scrollbar-thumb {
background: #a7a7a7;
border-radius: 10px;
}
.adress-wrapper-main::-webkit-scrollbar-thumb:hover {
background: #8a8a8a;
}
.adress-wrapper-main::-webkit-scrollbar-track {
background: #f0f0f0;
}
.adress-wrapper-main {
scrollbar-width: thin;
scrollbar-color: #a7a7a7 #f0f0f0;
}
.autocomplete-list::-webkit-scrollbar {
width: 6px;
height: 6px;
background: #f0f0f0;
border-radius: 0 5px 5px 0;
}
.autocomplete-list::-webkit-scrollbar-thumb {
background: #a7a7a7;
border-radius: 10px;
}
.autocomplete-list::-webkit-scrollbar-thumb:hover {
background: #8a8a8a;
}
.autocomplete-list::-webkit-scrollbar-track {
background: #f0f0f0;
border-radius: 0 5px 5px 0;
}
.autocomplete-list {
scrollbar-width: thin;
scrollbar-color: #a7a7a7 #f0f0f0;
}
.adress-item {
background-color: #fff;
padding: 12px 20px;
line-height: 1.5;
cursor: pointer;
border-bottom: 1px solid #d7d7d7;
transition: all 0.3s ease;
}
.adress-item:hover {
background: #f9f7f5;
}
.adress-item > div {
pointer-events: none;
}
.adress-item.activepoint {
background: rgba(154, 138, 110, 0.07);
box-shadow: inset 3px 0 0 0 #c4a86a;
}
.adress-title {
font-weight: 500;
margin-bottom: 4px;
}
.adress-title strong {
color: #9a8a6e;
font-weight: 400;
font-size: 16px !important;
}
.adress-descr {
font-size: 14px;
color: #666;
}
.adress-descr > * {
font-size: 14px !important;
}
ymaps[class$="-balloon__content"] strong {
padding: 5px 0;
}
@media screen and (max-width: 960px) {
.map-container {
grid-template-columns: 1fr;
height: max-content;
}
.adress-wrapper-main {
position: static;
width: 100%;
height: 370px;
margin-bottom: 20px;
}
.adress-title > div {
font-size: 16px !important;
}
.adress-descr,
.adress-descr > * {
font-size: 13px !important;
}
#mapyandex {
height: 400px;
margin-top: 0px;
}
.adress-item {
box-shadow: inset 0 0 0 1px #ededed;
}
.autocomplete-list {
max-height: 200px;
}
}
.adress-item-inner-wrap {
display: flex;
flex-direction: column;
gap: 2px;
}
@media screen and (max-width: 480px) {
#mapyandex {
height: 300px;
}
.adress-wrapper-main {
height: 250px;
}
}
</style>