Skip to content

Instantly share code, notes, and snippets.

@newbornfrontender
Created May 23, 2019 13:39
Show Gist options
  • Save newbornfrontender/678946eb30ef0560ece0db8b8831d181 to your computer and use it in GitHub Desktop.
Save newbornfrontender/678946eb30ef0560ece0db8b8831d181 to your computer and use it in GitHub Desktop.
Final map
<div class="map">
<div class="container map__results">
{% include "blocks/map-results/map-results.html" %}
</div>
<div id="map" class="map__inner" data-map-center="55.76, 37.64"></div>
</div>
import $ from 'jquery';
import SimpleBar from 'simplebar';
$(document).ready(() => {
const resultsList = document.querySelector('#results-list');
const map = document.querySelector('#map');
// Получаем центр карты
const getMapCenter = (target) => {
const value = target.dataset.mapCenter;
const splitValue = value.split(', ');
return [Number(splitValue[0]), Number(splitValue[1])];
};
// Создание кластеризатора с произвольным HTML-макетом иконки кластера
class CustomCluster {
// Зададаем макет метки кластера
createCluster() {
return ymaps.templateLayoutFactory.createClass(
'<div class="ymap-cluster-icon">{{ properties.geoObjects.length }}</div>',
);
}
// Зададаем активную область иконки кластера
createIconShape() {
return {
type: 'Circle',
coordinates: [0, 0],
radius: 25,
};
}
}
const init = () => {
const geoObjects = [];
const customMap = new ymaps.Map(
'map',
{
center: getMapCenter(map),
zoom: 12,
controls: [],
},
{
searchControlProvider: 'yandex#search',
},
);
const cluster = new CustomCluster();
const CustomBalloonLayout = ymaps.templateLayoutFactory.createClass(
`
<div class="ymap-popover">
<a class="ymap-popover-close" href="/">
<span></span>
</a>
<div class="ymap-popover-arrow"></div>
<div class="ymap-popover-inner clinic-card-preview">
$[[options.contentLayout observeSize minWidth=268 maxWidth=268]]
</div>
</div>
`,
{
// Строим экземпляр макета на основе шаблона и добавляем его в родительский HTML-элемент
build() {
this.constructor.superclass.build.call(this);
this._$element = $('.ymap-popover', this.getParentElement());
this.applyElementOffset();
this._$element.find('.ymap-popover-close').on('click', $.proxy(this.onCloseClick, this));
},
// Удаляем содержимое макета из DOM
clear() {
this._$element.find('.ymap-popover-close').off('click');
this.constructor.superclass.clear.call(this);
},
// Метод будет вызван системой шаблонов API при изменении размеров вложенного макета
onSublayoutSizeChange() {
CustomBalloonLayout.superclass.onSublayoutSizeChange.apply(this, arguments);
if (!this._isElement(this._$element)) {
return;
}
this.applyElementOffset();
this.events.fire('shapechange');
},
// Сдвигаем балун, чтобы "хвостик" указывал на точку привязки
applyElementOffset() {
this._$element.css({
left: -(this._$element[0].offsetWidth / 2),
// По какой-то причине prettier и stylelint считают строку ниже за CSS
// stylelint-disable
top: this._$element.find('.ymap-popover-arrow')[0].offsetHeight + 13,
});
},
// Закрываем балун при клике на крестик, кидая событие "userclose" на макете
onCloseClick(e) {
e.preventDefault();
this.events.fire('userclose');
},
// Используется для автопозиционирования (balloonAutoPan)
getShape() {
if (!this._isElement(this._$element)) {
return CustomBalloonLayout.superclass.getShape.call(this);
}
var position = this._$element.position();
return new ymaps.shape.Rectangle(
new ymaps.geometry.pixel.Rectangle([
[position.left, position.top],
[
position.left + this._$element[0].offsetWidth,
position.top +
this._$element[0].offsetHeight +
this._$element.find('.ymap-popover-arrow')[0].offsetHeight,
],
]),
);
},
_isElement(element) {
return element && element[0] && element.find('.ymap-popover-arrow')[0];
},
},
);
const CustomBalloonContentLayout = ymaps.templateLayoutFactory.createClass(
`
<a class="clinic-card-preview__title-link" href="$[properties.balloonTitleLink]">
<h3 class="clinic-card-preview__title">$[properties.balloonTitleText]</h3>
</a>
<ul class="clinic-card-preview__location">
<li class="clinic-card-preview__location-item">$[properties.balloonMetro]</li>
<li class="clinic-card-preview__location-item">$[properties.balloonGeo]</li>
</ul>
<p class="clinic-card-preview__time">$[properties.balloonTime]</p>
<div class="clinic-card-preview__feedback">
$[properties.balloonRating]
<p class="clinic-card-preview__score">$[properties.balloonScore]</p>
<a class="clinic-card-preview__comments" href="$[properties.balloonCommentsLink]">$[properties.balloonCommentsText]</a>
</div>
`,
);
$.ajax({
url: 'data.json',
}).done((data) => {
data.forEach((item) => {
const {
id,
geometry: { coordinates },
properties,
} = item;
// Создаем карточки клиник при помощи template тэга и добавляем их в список
// ---------------------------------------------------------------------
const resultsList = document.querySelector('.map-results__list');
const resultTemplate = document.querySelector('#search-result-card');
// Получаем элемент из template тэга
const getNodeFromTemplate = (template, node) => {
return template.content.querySelector(node);
};
// Добавляем текст
const setTextForTemplateNode = (node, text) => {
return (getNodeFromTemplate(resultTemplate, node).textContent = text);
};
// Добавляем путь ссылки
const setHrefForTemplateLink = (node, href) => {
return (getNodeFromTemplate(resultTemplate, node).href = href);
};
// Очищаем html
const trimHtml = (html) => {
return html.trim();
};
const templateFeedback = getNodeFromTemplate(
resultTemplate,
'.clinic-card-preview__feedback',
);
getNodeFromTemplate(resultTemplate, '.clinic-card-preview').dataset.id = id;
setTextForTemplateNode('.clinic-card-preview__title', properties.balloonTitleText);
setHrefForTemplateLink('.clinic-card-preview__title-link', properties.balloonTitleLink);
setTextForTemplateNode(
'.clinic-card-preview__location-item_metro',
properties.balloonMetro,
);
setTextForTemplateNode('.clinic-card-preview__location-item_geo', properties.balloonGeo);
setTextForTemplateNode('.clinic-card-preview__time', properties.balloonTime);
templateFeedback.innerHTML = trimHtml(`
${properties.balloonRating}
<p class="clinic-card-preview__score">${properties.balloonScore}</p>
<a class="clinic-card-preview__comments" href="${properties.balloonCommentsLink}">${
properties.balloonCommentsText
}</a>
`);
resultsList.appendChild(resultTemplate.content.cloneNode(true));
// Выводим количество клиник на карте
// ---------------------------------------------------------------------
const clinicsCounter = document.querySelector('.map-results__clinics span');
clinicsCounter.textContent = data.length;
// Создаем маркеры, балуны клиник и инициализируем их
// ---------------------------------------------------------------------
geoObjects[id] = new ymaps.GeoObject(
{
geometry: {
type: 'Point',
coordinates,
},
properties,
},
{
// Указываем тип макета
iconLayout: 'default#imageWithContent',
// Добавляем своё изображение иконки метки
iconImageHref: 'img/marker.svg',
// Указываем размеры метки
iconImageSize: [50, 50],
// Изменяем положение левого верхнего угла иконки относительно её точки привязки
iconImageOffset: [-25, -25],
// Не скрывать метку при открытии балуна
hideIconOnBalloonOpen: false,
balloonShadow: false,
balloonLayout: CustomBalloonLayout,
balloonContentLayout: CustomBalloonContentLayout,
balloonPanelMaxMapArea: 0,
},
);
});
const clusterer = new ymaps.Clusterer({
gridSize: 32,
clusterIconLayout: cluster.createCluster(),
clusterIconShape: cluster.createIconShape(),
});
clusterer.add(geoObjects);
customMap.geoObjects.add(clusterer);
// Инициализируем кастомный слайдер после добавления всех карточек
// -----------------------------------------------------------------------
new SimpleBar(resultsList, { autoHide: false });
// Перемещаемся к указанному маркеру из списка карточек и открваем балун
// -----------------------------------------------------------------------
const resultCards = document.querySelectorAll('.map-results__card');
resultCards.forEach((card) => {
const id = card.dataset.id;
const {
geometry: { _coordinates: coordinates },
} = geoObjects[id];
const moveToPlacemark = () => {
customMap.balloon.close();
// Перемещаемся к координатам маркера
customMap.panTo(coordinates).then(() => {
// Получаем нужный стейт
const objectState = clusterer.getObjectState(geoObjects[id]);
/**
* Проверяем находится ли маркер в кластере
* Если находится, тогда увеличиваем масштаб карты до тех пор, пока
* не появится нужный маркер. После чего открываем балун маркера
*/
if (objectState.isClustered) {
// Вызываем менеджер коэффициентов масштабирования
customMap.zoomRange.get(coordinates).then((range) => {
customMap
.setCenter(coordinates, range[1], {
// Контролируем доступность масштаба
checkZoomRange: true,
})
.then(() => {
// Карта спозиционировались, после чего открываем балун
geoObjects[id].balloon.open();
});
});
} else {
geoObjects[id].balloon.open();
}
});
};
card.addEventListener('click', () => moveToPlacemark());
card.removeEventListener('click', () => moveToPlacemark());
});
});
};
ymaps.ready(init);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment