Карта в Тильде: Яндекс.Карты в корзине и класс-исключение .t-map
Карту на Тильду ставят блоком или своим кодом. Кода блока в разобранном рантайме нет, зато есть карта пунктов выдачи в корзине — живая интеграция с Яндекс.Картами, видная целиком: от загрузки скрипта до кластеризации меток. По ней понятно, почему карта не появляется без ключа, почему она всегда по-русски и почему на телефоне её не сдвинуть одним пальцем. И там же лежит ошибка, из-за которой клиент выберет не тот пункт.
Вопрос «как сделать карту в Тильде» распадается на два разных: поставить карту на страницу и заставить её вести себя правильно. На первый ответ короткий, на второй — интересный, потому что в рантайме Тильды есть одна полностью читаемая карта, и на ней видно все решения, которые придётся принять и вам.
Короткий ответ
Карту на страницу Тильда ставит блоком (его кода в разобранном рантайме нет —
он приезжает постраничным бандлом) или вашим кодом. Единственный провайдер, с
которым Тильда работает из кода, — Яндекс.Карты 2.1, и ровно в одном месте: карта
пунктов выдачи в корзине. Google, Leaflet, Mapbox, OpenStreetMap — по нулю
вхождений. Всё, что доказуемо про блок-карту, — что её контейнер носит класс
t-map и что автомасштаб обходится с ним особым образом.
Что в рантайме есть и чего нет
| что искал | JS (114 файлов) | где |
|---|---|---|
ymaps | 18 | только tilda-delivery-1.0.min.js |
api-maps.yandex | 2 | там же |
maps.googleapis | 0 | — |
google.maps | 0 | — |
leaflet | 0 | — |
mapbox | 0 | — |
openstreetmap | 0 | — |
t-map | 4 | только tilda-autoscale-1.0.min.js |
navigator.geolocation | 0 | — |
loading="lazy" | 0 | — |
IntersectionObserver | 75 | контроль замера |
Из этой таблицы сразу три практических вывода.
Google-карта на Тильде — это iframe. JS API Google в рантайме нет вовсе,
значит управлять такой картой из кода нечем: ни метку добавить, ни центр сдвинуть,
ни на клик подписаться. Всё, что можно, — вставить готовую рамку и задать ей
размер.
Тильда не спрашивает геолокацию. Ни одного обращения к
navigator.geolocation, то есть окно «разрешить доступ к местоположению» на
странице Тильды не всплывёт никогда. Город для доставки определяется цепочкой
restoreFromLastOrder → restoreFromLS → getCityFromGeoWithFailHandler — прошлый
заказ, потом сохранённые поля, потом определение на стороне сервера. Разрешения у
пользователя не просят.
Карту в рамке Тильда не отложит. loading="lazy" — ноль вхождений во всех
файлах и во всех сохранённых страницах, при этом IntersectionObserver в
рантайме используется 75 раз. Механика для отложенной загрузки есть, к рамкам она
не применяется. Значит атрибут loading="lazy" на свой iframe ставите вы, и
без него карта тянет свои килобайты сразу, даже если лежит в подвале.
Класс .t-map — единственное, что доказуемо про блок-карту
Имя класса встречается в одном файле, tilda-autoscale-1.0.min.js, и оба раза —
как исключение. Сначала карту исключают из пересчёта высот:
document.querySelectorAll(sel + " [" + attr + "]").forEach(function (el) {
if (!el.classList.contains("t-map")) {
var v = el.getAttribute(attr);
if (v) { var prop = c(el);
el.style.setProperty(prop, "calc(" + v + "vh / var(" + name + ", 1))") }
}
})
Потом ей отдельным проходом гасят масштаб:
function t_autoscale__processMaps() {
var d = t_autoscale__getAutoscaleData(), sel = d.scaledRecordSelector, name = d.variableName;
document.querySelectorAll(sel + " .t-map").forEach(function (el) {
el.style.zoom = "calc(1 / var(" + name + ", 1))";
…
})
}
Читается это так: масштабировать карту вместе с остальным содержимым нельзя —
поедут плитки и координаты клика, — поэтому Тильда компенсирует масштаб через
zoom, а высоту в vh ей вообще не пересчитывает.
Практический вывод, которого нет ни в одной справке. Если вы вставляете свою
карту в Zero Block с автомасштабом, дайте её контейнеру класс t-map. Иначе
контейнер попадёт в общий пересчёт, карта отмасштабируется вместе со всем блоком —
и клики начнут приходить не туда, куда вы ткнули. Класс здесь работает как
объявление «я карта, меня трогать особым образом».
Живая карта: пункты выдачи в корзине
Дальше — единственная карта, которую видно целиком. Она собирается тремя методами модуля доставки, и разбор стоит того: те же вопросы встанут перед любой своей картой.
Загрузка скрипта: одна строка и один хардкод
mapAppendScript: function (t, r) {
var n = document.createElement("script");
n.src = "https://api-maps.yandex.ru/2.1/?apikey=" + (r || "") + "&lang=ru_RU";
document.head.appendChild(n)
}
lang=ru_RU вписан насмерть. Запомните это место: через двадцать строк выяснится,
что подсказку над той же картой Тильда переводит на тринадцать языков.
И (r || "") — если ключа нет, в адрес уйдёт apikey= без значения. Впрочем, до
этого дело не доходит, потому что вызов стоит за проверкой:
r.deliveryState.ymapApiKey = t,
r.deliveryState.ymapApiKey && r.mapAppendScript("", t);
Ключ гасит всё. Тот же ymapApiKey && стоит перед mapInit("yandex"), перед
mapAddPoints(...) и перед определением координат города. Нет ключа — нет
скрипта, нет карты, нет метки, нет координат. Ни ошибки в консоли, ни сообщения
пользователю: карта просто не появляется. Это ответ номер один на «почему в
корзине нет карты».
Обратите внимание и на первый аргумент: вызов mapAppendScript("", t) передаёт
пустую строку, а тело функции параметр t не читает. То же в mapAddPoints("", n, v).
Два мёртвых параметра — след от заготовки под второго провайдера, которого так и
не появилось.
Создание карты: четыре решения, все спорные
window.t_delivery__map = new ymaps.Map("delivery-yandex-map", {
center: n.deliveryState.cityCoordinates || [55.76, 37.64],
zoom: 8, controls: ["zoomControl"], minZoom: 3, maxZoom: 20
}, {
yandexMapDisablePoiInteractivity: !0,
suppressMapOpenBlock: !0
});
[55.76, 37.64] — Москва. Не определились координаты города — карта открывается
в Москве, молча.
zoom: 8 — это масштаб области, не города: в кадре весь регион. Оправдано тем,
что сразу после расстановки меток вызывается setBounds, — но первый кадр
пользователь всё равно видит.
controls: ["zoomControl"] — из всех штатных элементов управления оставлен один.
Ни поиска, ни линейки, ни кнопки «где я», ни полного экрана.
yandexMapDisablePoiInteractivity и suppressMapOpenBlock — выключены
собственные объекты Яндекса и блок «открыть в Яндекс.Картах». Разумно: клик по
чужой кофейне посреди выбора пункта выдачи только мешает.
Жест двумя пальцами: приём, который стоит забрать
Классическая беда карты на странице: пользователь листает страницу, палец попадает на карту, и вместо прокрутки страница замирает, а карта уезжает. Тильда решает это так:
var pane = window.t_delivery__map.panes.get("events").getElement(),
hint = { EN: "Use two fingers to move the map",
RU: "Чтобы переместить карту проведите по ней двумя пальцами",
FR: "Utilisez deux doigts pour déplacer la carte",
DE: "Verschieben der Karte mit zwei Fingern", … }; // всего 13 языков
Object.keys(style).forEach(function (k) { pane.style[k] = style[k] });
window.tStoreIsMobile && (
window.t_delivery__map.behaviors.disable("drag"),
window.ymaps.domEvent.manager.add(pane, "touchmove", function (e) {
1 === e.get("touches").length && (
pane.style.transition = "opacity .3s",
pane.style.background = "rgba(0, 0, 0, .45)",
pane.textContent = hint[window.t_delivery__browserLang] || hint.EN,
pane.style.opacity = "1")
}),
window.ymaps.domEvent.manager.add(pane, "touchend", function () {
pane.style.transition = "opacity .8s"; pane.style.opacity = "0"
})
);
На мобильном перетаскивание одним пальцем отключается совсем
(behaviors.disable("drag")), а при касании одним пальцем на карту наплывает
затемнение с подсказкой: появление за 0.3 с, исчезновение за 0.8 с. Двумя
пальцами карта двигается как обычно. Приём хороший, воспроизводимый на любой
карте, и его стоит взять.
А вот языки: подсказка переведена на тринадцать языков, включая японский, китайский,
казахский и латышский, и берётся по t_delivery__browserLang — который сначала
считается из navigator.language, а потом, если у проекта задан язык, заменяется на
него:
window.t_delivery__browserLang =
(window.navigator.userLanguage || window.navigator.language).toUpperCase().slice(0, 2);
t_onReady(function () {
var el = document.getElementById("allrecords"),
lang = el ? el.getAttribute("data-tilda-project-lang") : "";
lang && (window.t_delivery__browserLang = lang)
});
То есть подсказка над картой будет на языке проекта, а сама карта — со своими
подписями, кнопкой зума и балунами — всегда на русском, потому что lang=ru_RU
вписан в адрес скрипта. Латышская подсказка над русской картой — прямое следствие
двух строк, которые писали разные люди.
Координаты города: порядок наоборот и тихий провал
xhr.open("GET", "https://geocode-maps.yandex.ru/1.x/?apikey="
+ this.deliveryState.ymapApiKey + "&format=json&geocode=" + query);
xhr.timeout = 3e4;
Ответ разбирается так:
try {
var t;
(t = e.response.GeoObjectCollection.featureMember[0].GeoObject.Point.pos.split(" "))
&& (t = [t[1], t[0]], r.deliveryState.cityCoordinates = t)
} catch (n) { console.error(n) }
Координаты меняются местами — и это не причуда Тильды, а свойство API:
геокодер отдаёт pos как «долгота широта», а ymaps.Map ждёт [широта, долгота].
Перепутать здесь — классика; строка [t[1], t[0]] ровно об этом.
featureMember[0] берётся без проверки. Город не нашёлся — массив пуст,
обращение к [0] бросает исключение, catch пишет его в консоль,
cityCoordinates остаётся null, карта открывается в Москве. Пользователь при
этом не узнаёт ничего.
Таймаут 30 секунд, и колбэк вызывается только при status === 200. Любой
другой ответ и любой таймаут заканчиваются тем же: тишина и Москва.
Находка: пункт без координат сдвигает подписи у всех следующих
Вот расстановка меток, сокращённая до сути:
var coords = [];
points.forEach(function (p) {
0 === p.coordinates[0] && 0 === p.coordinates[1] || coords.push(p.coordinates)
});
if (coords.length > 0) {
for (var clusterer = new ymaps.Clusterer({ preset: "islands#circleIcon", … }),
balloon = function (idx) {
var p = points[idx]; // ← полный список
…
v += '… data-point-index=' + idx + ' onclick="window.tcart__chosePointOnMap(this)">…';
window.tcart__chosePointOnMap = function (el) {
var p = points[el.dataset.pointIndex]; // ← тоже полный список
… i.deliveryState.pickupPointId = p.id …
};
return { balloonContentHeader: …, balloonContentBody: v, … }
},
marks = [], c = 0, d = coords.length; c < d; c++)
marks[c] = new ymaps.Placemark(coords[c], balloon(c), preset(c));
Смотрите на последнюю строку. Координата берётся из отфильтрованного массива
coords, а описание — из полного points по тому же индексу. Пока фильтр
ничего не выбросил, индексы совпадают. Но фильтр здесь не случайный: Тильда сама
проверяет координаты [0, 0], а такую проверку не пишут от нечего делать — значит
пункты без координат в выгрузках бывают.
И как только один такой пункт выброшен, всё расходится на единицу. Метка стоит по
координатам пункта №4, а в балуне показаны название, адрес, часы и телефон пункта
№3. Нажатие «Выбрать» вызывает tcart__chosePointOnMap, который читает
points[data-point-index] — тот же сдвинутый индекс, — и в заказ уходит пункт №3.
Последний пункт списка при этом не получает метки вовсе: цикл идёт до
coords.length, а он на единицу короче.
Оговорка честная: это прочитано в коде, а не увидено на живой корзине — выгрузки пунктов выдачи у нас нет, воспроизвести нечем. Но арифметика проверяется глазами, и последствие у неё денежное: клиент выбирает пункт рядом с домом, а получает предыдущий по списку.
Чему это учит в своём коде: фильтровать и описывать нужно один и тот же массив. Правильно было бы отфильтровать список пунктов целиком и дальше работать только с ним.
Из того же куска — две мелочи на будущее. geoObjects.removeAll() перед каждой
отрисовкой: карта перерисовывается целиком, а не разницей. И
window.tcart__chosePointOnMap переопределяется внутри цикла — на каждый пункт
заново, хотя достаточно одного раза.
Что из этого делать
Ключ Яндекс.Карт — первое, что проверять. Если карты в корзине нет, дело почти наверняка в нём, и никакого сообщения об этом не будет.
Свою карту в масштабируемом Zero Block помечайте классом t-map. Тогда
автомасштаб обойдётся с ней так, как обходится со штатной: без пересчёта высоты и
с погашенным масштабом.
loading="lazy" на iframe ставьте руками. Тильда этого не делает нигде.
Помните про порядок координат. Геокодер Яндекса отдаёт «долгота широта», карта ждёт «широта долгота». Половина «карта показывает Африку» — про это.
Не листайте карту одним пальцем. Отключайте drag на мобильном и показывайте
подсказку — приём из корзины Тильды переносится в свой код почти дословно.
Проверяйте пустые координаты до отрисовки, а не во время. Отфильтруйте список объектов целиком, и тогда никакой индекс не разъедется.
Откуда числа
Перепись провайдеров — поиск по 114 файлам JS, 37 файлам CSS и 22 сохранённым
страницам, счёт по вхождениям, а не по строкам. Контроли печатались рядом:
t_onFuncLoad — 271 по JS, data-record-type — 219 по страницам,
tcart|delivery — 878 в модуле доставки, IntersectionObserver — 75 по JS.
mapAppendScript, mapInit и mapAddPoints выписаны из
tilda-delivery-1.0.min.js (186.3 КБ) целиком; обработка .t-map — из
tilda-autoscale-1.0.min.js, оба вхождения.
Что проверено и оказалось не тем, чем казалось: T175 — не карта. В CSS у него
.t175__widepadding, .t175__title, .t175__descr, .t175__text, .t175__img
— это блок с текстом и картинкой. И ни в одной из 22 сохранённых страниц нет
iframe с картой: то есть разметку блока-карты нам сверять не с чем, и всё, что
сказано выше про блок, держится на классе t-map в автомасштабе — не больше.
И общая оговорка. Отсутствие имени в наборе файлов не равно отсутствию его в
Тильде: у Тильды отложенная привязка через t_onFuncLoad, и код блока может
лежать в постраничном бандле, которого у нас нет. Поэтому «в Тильде нет
блока-карты» здесь никто не утверждает — утверждается ровно то, что его кода нет в
разобранном рантайме, а Google-карты нет и там, и в виде JS API вообще.