Расписание по официальным данным лета 2026, прибытие и шторка на карте
Данные диспетчерской службы ООО «Гарант-Авто» на 01.05–31.10.2026, источник kandalaksha.org/static/auto.html. Опубликованы четыре списка ОТПОРЯЖЕНИЙ, 13/10/6 рейсов; колонка 2 — это выезд из Зашейка, а не прибытие, из-за чего приложение показывало нелогичные интервалы. Расписание: - убран рейс 08:00/08:50/09:20/09:50, которого нет в источнике; - добавлен рейс 11:30/12:20/12:50/13:20; - в рейсе на Кандалакшу 14:10 → 14:15; - суббота и воскресенье на 5 минут (09:05 → 09:10 и далее по списку); - 14:20, 14:50, 15:10 и 15:50 подтверждены источником и оставлены. Время прибытия в карточках направлений считается, а не берётся из таблицы: Зашеек = отправление + 20 мин, но не дольше интервала до выезда оттуда; Кандалакша = + 50 мин. Карта: - нижняя шторка с ручкой: перетаскивание, тап для сворачивания, автопрокрутка текста, карта перестраивается при изменении размера; - заголовок «Автобус №127» в одну строку на всех экранах, раньше на карте он переносился, а на остальных терял пробел перед №127; - кнопка «‹» на карте возвращает на главный экран; - между прибытием в Зашеек и выездом автобус стоит на остановке; - убраны 40px лишней прокрутки страницы на вкладке карты.
This commit is contained in:
@@ -65,7 +65,7 @@
|
||||
color:var(--txt);border-radius:10px;width:38px;height:38px;
|
||||
font-size:22px;line-height:1;font-family:inherit;cursor:pointer;
|
||||
align-items:center;justify-content:center;padding:0}
|
||||
body.app .iconbtn,body.about .iconbtn{display:inline-flex}
|
||||
body.app .iconbtn,body.about .iconbtn,body.map .iconbtn{display:inline-flex}
|
||||
/* на главном экране стрелки нет, но место под неё оставляем, иначе съедет центр */
|
||||
body.app .iconbtn.back{visibility:hidden}
|
||||
/* троеточие в круглой кнопке приподнято: рамка остаётся на одной линии с названием */
|
||||
@@ -95,9 +95,12 @@
|
||||
.aboutcard p.muted{color:var(--muted);font-size:13px}
|
||||
|
||||
/* экран «Карта»: страница целиком занята картой, шапка остаётся */
|
||||
body.map{padding:24px 16px 16px;overflow:hidden}
|
||||
body.map .wrap{height:100vh;height:100dvh;display:flex;flex-direction:column;max-width:none}
|
||||
body.map{height:100vh;height:100dvh;min-height:0;padding:24px 16px 16px;overflow:hidden}
|
||||
body.map .wrap{height:100%;display:flex;flex-direction:column;max-width:none}
|
||||
body.map header{margin-bottom:12px}
|
||||
/* заголовок всегда в одну строку, в том числе на карте */
|
||||
body.map h1{font-size:20px;line-height:1.2;white-space:nowrap}
|
||||
body.map .clock{font-size:18px;margin-top:6px}
|
||||
body.map .day,body.map .pickers,body.map .where,body.map .dir,
|
||||
body.map .fullbtn,body.map #fullTable,body.map .aboutcard,body.map footer{display:none}
|
||||
body.map #mapBox{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:8px}
|
||||
@@ -106,8 +109,23 @@
|
||||
.mapcanvas.loading::after{content:"Загружаю карту…";position:absolute;inset:0;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
color:var(--muted);font-size:14px}
|
||||
.mapstatus{flex:0 0 auto;background:var(--card);border:1px solid var(--line);
|
||||
border-radius:14px;padding:11px 14px}
|
||||
|
||||
/* нижняя шторка: свёрнута до ручки с заголовком, тянется вверх,
|
||||
а текст внутри прокручивается отдельно от карты */
|
||||
.mapsheet{flex:0 0 auto;position:relative;display:flex;flex-direction:column;
|
||||
background:var(--card);border:1px solid var(--line);border-radius:14px;
|
||||
overflow:hidden;box-shadow:0 -6px 20px rgba(0,0,0,.35)}
|
||||
.grab{position:relative;flex:0 0 auto;display:flex;align-items:center;gap:8px;
|
||||
padding:14px 14px 8px;cursor:grab;touch-action:none;user-select:none;
|
||||
border-bottom:1px solid var(--line)}
|
||||
.grab .bar{position:absolute;top:6px;left:50%;width:34px;height:4px;margin-left:-17px;
|
||||
border-radius:2px;background:#3a4a70}
|
||||
.grab .cap{flex:1 1 auto;min-width:0;font-size:12px;font-weight:700;color:var(--muted);
|
||||
overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.grab .chev{color:var(--muted);font-size:11px;transition:transform .2s}
|
||||
.mapsheet.open .grab .chev{transform:rotate(180deg)}
|
||||
.mapstatus{flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
|
||||
overscroll-behavior:contain;padding:11px 14px 14px}
|
||||
.mapstatus .head{font-size:14px;font-weight:700;margin-bottom:4px}
|
||||
.mapstatus .head b{color:var(--accent)}
|
||||
.mapstatus .rows{font-size:13px;color:var(--muted);line-height:1.55}
|
||||
@@ -115,6 +133,11 @@
|
||||
.mapstatus .rows .when{color:var(--txt);font-variant-numeric:tabular-nums;
|
||||
white-space:nowrap;font-size:12px}
|
||||
.mapstatus .hint{margin-top:7px;font-size:11px;color:var(--muted);line-height:1.4}
|
||||
.note{margin-top:10px;padding-top:10px;border-top:1px solid var(--line);
|
||||
font-size:12px;line-height:1.55;color:var(--muted)}
|
||||
.note p{margin:0 0 8px}
|
||||
.note p:last-child{margin-bottom:0}
|
||||
.note b{color:var(--txt)}
|
||||
|
||||
/* метки на карте. Корень двигает библиотека, вид задаёт внутренний блок */
|
||||
.pin{transform:translate(-50%,-100%);white-space:nowrap;pointer-events:none}
|
||||
@@ -197,20 +220,44 @@
|
||||
</div>
|
||||
<div class="thead">
|
||||
<div><b>Кандалакша</b>отпр.</div>
|
||||
<div><b>Полярные Зори</b>→ Зашеек</div>
|
||||
<div><b>Зашеек</b>прибытие</div>
|
||||
<div><b>Полярные Зори</b>→ Кандалакша</div>
|
||||
<div><b>Полярные Зори</b>отпр. в Зашеек</div>
|
||||
<div><b>Зашеек</b>отпр.</div>
|
||||
<div><b>Полярные Зори</b>отпр. в Канд.</div>
|
||||
</div>
|
||||
<div id="tableBody"></div>
|
||||
</section>
|
||||
|
||||
<section class="mapcard" id="mapBox" hidden>
|
||||
<div class="mapcanvas loading" id="mapCanvas"></div>
|
||||
<div class="mapsheet" id="mapSheet">
|
||||
<div class="grab" id="sheetGrab">
|
||||
<span class="bar"></span>
|
||||
<span class="cap" id="sheetCap">Автобусы и расписание</span>
|
||||
<span class="chev">▴</span>
|
||||
</div>
|
||||
<div class="mapstatus">
|
||||
<div class="head" id="mapHead">Карта маршрута</div>
|
||||
<div class="rows" id="mapRows"></div>
|
||||
<div class="hint">Положение автобуса рассчитано по расписанию, а не по GPS:
|
||||
между остановками интерполируется по дороге. Точность — до нескольких минут.</div>
|
||||
<div class="note">
|
||||
<p><b>Как читать расписание.</b> Таблица и списки показывают официальное время
|
||||
<b>отправлений</b>. Время прибытия в расписании не публикуется: оно берётся
|
||||
из отправления следующей остановки, потому что автобус приезжает
|
||||
на Полярные Зори и сразу уходит дальше. Исключение — Зашеек: там
|
||||
прибытие посчитано как выезд из Полярных Зорь плюс 20 минут.
|
||||
Получившиеся интервалы в обе стороны по одной дороге отличаются
|
||||
на 5–10 минут — так и есть в официальном расписании, это не ошибка.</p>
|
||||
<p><b>Положение автобусов.</b> GPS у приложения нет, координаты
|
||||
интерполируются по дороге между остановками по времени рейса.
|
||||
В Зашейке автобус стоит, пока не подошло время обратного выезда.
|
||||
Точность — до нескольких минут, в пробки отставание больше.</p>
|
||||
<p class="muted">Расписание сверено с официальным: отправления взяты из данных
|
||||
диспетчерской службы перевозчика, летнее расписание
|
||||
с 01.05.2026 по 31.10.2026, время московское. Карта и тайлы — VK Карты,
|
||||
геометрия дороги — OpenStreetMap.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -224,36 +271,47 @@
|
||||
<p>В меню есть «Карта»: там видно, где сейчас едут автобусы, и как пройден
|
||||
участок между остановками. Положение считается по расписанию — приложение
|
||||
не получает данные с GPS, поэтому это оценка с точностью до нескольких минут.</p>
|
||||
<p class="muted">Расписание действует с 30.09.2020. Время московское (МСК).</p>
|
||||
<p class="muted">Расписание сверено с официальным: отправления взяты из данных
|
||||
диспетчерской службы перевозчика, летнее расписание
|
||||
с 01.05.2026 по 31.10.2026. Время московское (МСК).</p>
|
||||
<p class="muted">Карта и тайлы — VK Карты, геометрия дороги — OpenStreetMap.
|
||||
Пуш-уведомления приходят через RuStore Push — для этого нужен
|
||||
установленный и авторизованный RuStore на устройстве.</p>
|
||||
</section>
|
||||
|
||||
<footer>Расписание действует с 30.09.2020 · время московское (МСК)</footer>
|
||||
<footer>Летнее расписание с 01.05.2026 по 31.10.2026 · отправления по данным
|
||||
диспетчерской службы перевозчика · время московское (МСК)</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Расписание по типам дней недели.
|
||||
// Колонки: отправление из Кандалакши · Полярные Зори (в Зашеек) ·
|
||||
// прибытие в Зашеек · Полярные Зори (обратно).
|
||||
// Расписание по типам дней недели. Взято с официального расписания
|
||||
// (данные диспетчерской службы перевозчика ООО «Гарант-Авто»,
|
||||
// kandalaksha.org/static/auto.html), летнее расписание
|
||||
// с 01.05.2026 по 31.10.2026.
|
||||
//
|
||||
// Будни — полная таблица маршрута. По субботе и воскресенью в репозитории
|
||||
// хранились только отправления из Полярных Зорь и прибытия в Зашеек
|
||||
// (списки в app/src/main/java/ru/ua1zbe/bus127/MainActivity.kt), поэтому
|
||||
// первая и четвёртая колонки восстановлены по тем же правилам, что и для
|
||||
// будней, и требуют сверки с официальным расписанием:
|
||||
// кол0 = кол1 − 50 мин (Кандалакша → Полярные Зори занимает ровно 50 мин)
|
||||
// кол3 = кол2 + 20 мин (так разность кол3−кол2 выглядит в 8 строках из 13)
|
||||
// Опубликованы только ОТПРАВЛЕНИЯ, и четыре колонки склеиваются
|
||||
// в один круг рейса попарно, 1:1 — проверено, что моменты строго
|
||||
// возрастают в каждой строке:
|
||||
// кол0 — отправление из Кандалакши
|
||||
// кол1 — отправление из Полярных Зорь в Зашеек
|
||||
// кол2 — отправление ИЗ Зашейка (обратно в Полярные Зори)
|
||||
// кол3 — отправление из Полярных Зорь в Кандалакшу
|
||||
//
|
||||
// Важно: кол2 — это выезд из Зашейка, а не прибытие в него.
|
||||
// Раньше колонка была подписана как прибытие, и из-за этого один и тот же
|
||||
// автобус «прибывал» и «уезжал» в Зашеек в одну минуту, а интервалы
|
||||
// туда и обратно выходили разными на одной и той же дороге.
|
||||
// Прибытие в Зашеек в расписании не публикуется — оно считается
|
||||
// отдельно, см. arrZasheek ниже.
|
||||
const SCHEDULES = {
|
||||
weekday: [
|
||||
["05:20","06:20","06:30","06:50"],
|
||||
["06:10","07:00","07:25","07:45"],
|
||||
["07:40","08:30","09:00","09:20"],
|
||||
["08:00","08:50","09:20","09:50"],
|
||||
["09:45","10:35","11:05","11:25"],
|
||||
["11:00","11:50","12:20","12:40"],
|
||||
["12:30","13:20","13:50","14:10"],
|
||||
["11:30","12:20","12:50","13:20"],
|
||||
["12:30","13:20","13:50","14:15"],
|
||||
["13:30","14:20","14:50","15:10"],
|
||||
["14:00","14:50","15:25","15:50"],
|
||||
["15:30","16:20","17:00","17:25"],
|
||||
@@ -263,23 +321,23 @@ const SCHEDULES = {
|
||||
],
|
||||
saturday: [
|
||||
["06:10","07:00","07:25","07:45"],
|
||||
["07:30","08:20","08:45","09:05"],
|
||||
["08:45","09:35","10:05","10:25"],
|
||||
["10:10","11:00","11:30","11:50"],
|
||||
["12:30","13:20","13:50","14:10"],
|
||||
["14:00","14:50","15:45","16:05"],
|
||||
["15:30","16:20","16:45","17:05"],
|
||||
["17:00","17:50","18:20","18:40"],
|
||||
["18:10","19:00","19:30","19:50"],
|
||||
["20:20","21:10","21:40","22:00"]
|
||||
["07:30","08:20","08:45","09:10"],
|
||||
["08:45","09:35","10:05","10:30"],
|
||||
["10:10","11:00","11:30","11:55"],
|
||||
["12:30","13:20","13:50","14:15"],
|
||||
["14:00","14:50","15:45","16:10"],
|
||||
["15:30","16:20","16:45","17:10"],
|
||||
["17:00","17:50","18:20","18:45"],
|
||||
["18:10","19:00","19:30","19:55"],
|
||||
["20:20","21:10","21:40","22:05"]
|
||||
],
|
||||
sunday: [
|
||||
["06:10","07:00","07:25","07:45"],
|
||||
["08:45","09:35","10:05","10:25"],
|
||||
["12:30","13:20","13:50","14:10"],
|
||||
["14:00","14:50","15:25","15:45"],
|
||||
["16:50","17:40","18:10","18:30"],
|
||||
["20:20","21:10","21:40","22:00"]
|
||||
["08:45","09:35","10:05","10:30"],
|
||||
["12:30","13:20","13:50","14:15"],
|
||||
["14:00","14:50","15:25","15:50"],
|
||||
["16:50","17:40","18:10","18:35"],
|
||||
["20:20","21:10","21:40","22:05"]
|
||||
]
|
||||
};
|
||||
const DAY_KEYS = ["weekday","saturday","sunday"];
|
||||
@@ -289,21 +347,38 @@ const TRIPS_SHOWN = 5; // сколько ближайших рейсов по
|
||||
|
||||
// Время в пути Полярные Зори ↔ Кандалакша. В таблице расписания его нет:
|
||||
// в 12 строках из 13 Кандалакша → ПЗ занимает ровно 50 минут
|
||||
// (в первой строке 60 — по всей видимости опечатка).
|
||||
// (в первой строке 60 — по всей видимости опечатка в самом расписании).
|
||||
const RIDE_PZ_KAND = 50;
|
||||
|
||||
// arrs — список прибытий: {label, col} — время из колонки таблицы,
|
||||
// {label, offset} — «время в пути» от отправления (если col не задан).
|
||||
// Полярные Зори → Зашеек. В расписании тоже нет: там опубликованы только
|
||||
// отправления. Принимаем 20 минут — но не больше, чем есть между выездом
|
||||
// из Полярных Зорь и выездом обратно, иначе на первом утреннем рейсе
|
||||
// (06:20 → 06:30) автобус «прибыл» бы в Зашеек уже после своего выезда.
|
||||
const RIDE_PZ_ZASHEEK = 20;
|
||||
|
||||
// Прибытие в Зашеек — оценка, потому что официального времени прибытия
|
||||
// в расписании нет. Считаем от выезда из Полярных Зорь.
|
||||
function arrZasheek(row){
|
||||
const t1 = toMin(row[1]), t2 = toMin(row[2]);
|
||||
return t1 + Math.max(0, Math.min(RIDE_PZ_ZASHEEK, t2 - t1));
|
||||
}
|
||||
|
||||
// arrs — список прибытий: {label, col} — время из колонки расписания,
|
||||
// {label, offset} — «время в пути» от отправления (если col не задан),
|
||||
// {label, arrZ:true} — оценка прибытия в Зашеек, см. arrZasheek().
|
||||
// В расписании опубликованы только отправления, поэтому прибытия
|
||||
// (кроме Зашейка, где есть своя оценка) берутся из отправлений следующей
|
||||
// остановки: автобус приезжает на ПЗ и сразу уходит дальше.
|
||||
const DIRECTIONS = {
|
||||
// От Кандалакши и Зашейка все рейсы идут через Полярные Зори — одна карточка
|
||||
"Кандалакша": [
|
||||
{to:"Полярные Зори, Зашеек", dep:0, via:"через Полярные Зори",
|
||||
arrs:[{label:"Полярные Зори", col:1},
|
||||
{label:"Зашеек", col:2}]}
|
||||
{label:"Зашеек", arrZ:true}]}
|
||||
],
|
||||
"Полярные Зори": [
|
||||
{to:"Зашеек", dep:1, via:"≈ 30 мин в пути",
|
||||
arrs:[{label:"Зашеек", col:2}]},
|
||||
{to:"Зашеек", dep:1, via:"≈ 20 мин в пути",
|
||||
arrs:[{label:"Зашеек", arrZ:true}]},
|
||||
{to:"Кандалакша", dep:3, via:"≈ 50 мин в пути",
|
||||
arrs:[{label:"Кандалакша", offset:RIDE_PZ_KAND}]}
|
||||
],
|
||||
@@ -384,7 +459,9 @@ function render(){
|
||||
.map((r,ri)=>{
|
||||
const dep = toMin(r[d.dep]);
|
||||
const arrs = d.arrs.map(a=>{
|
||||
const base = a.col!==undefined ? toMin(r[a.col]) : dep;
|
||||
const base = a.col!==undefined ? toMin(r[a.col])
|
||||
: a.arrZ ? arrZasheek(r)
|
||||
: dep;
|
||||
return {label:a.label, t:fmtHM(base + (a.offset||0))};
|
||||
});
|
||||
return {dep, arrs, ri};
|
||||
@@ -523,6 +600,7 @@ let KAND_TO_PZ = null; // Кандалакша → Полярные Зори
|
||||
let PZ_TO_ZASHEEK = null;// Полярные Зори → Зашеек
|
||||
let PZ_TO_ZASHEEK_BACK = null;
|
||||
let KAND_TO_PZ_BACK = null;
|
||||
let MAPREF = null; // карта нужна шторке, чтобы пересчитать размер холста
|
||||
|
||||
// Длина ломаной с накопленными отрезками. Расстояние считаем в
|
||||
// равнопромежуточной проекции — на таком масштабе это точнее градусов.
|
||||
@@ -553,21 +631,25 @@ function pointAt(p, f){
|
||||
return [x1+(x2-x1)*t, y1+(y2-y1)*t];
|
||||
}
|
||||
|
||||
// Рейс разложен на четыре участка: туда Кандалакша → ПЗ → Зашеек и обратно.
|
||||
// Время в пути ПЗ → Кандалакша в таблице не указано, берём те же 50 минут,
|
||||
// что и в DIRECTIONS.
|
||||
// Рейс разложен на участки круга: Кандалакша → ПЗ → Зашеек → ПЗ → Кандалакша.
|
||||
// Колонка кол2 — это выезд ИЗ Зашейка, поэтому между прибытием туда
|
||||
// (оценка arrZasheek) и выездом автобус стоит на остановке: отдельный
|
||||
// участок hold, на котором точка не двигается. Время в пути ПЗ → Кандалакша
|
||||
// в расписании не указано, берём те же 50 минут, что и в DIRECTIONS.
|
||||
function makeRun(row){
|
||||
const t0=toMin(row[0]), t1=toMin(row[1]), t2=toMin(row[2]), t3=toMin(row[3]);
|
||||
const z = arrZasheek(row); // прибытие в Зашеек (оценка)
|
||||
const back = t3 + RIDE_PZ_KAND; // прибытие в Кандалакшу
|
||||
return {
|
||||
t0, tEnd: back,
|
||||
segs: [
|
||||
const segs = [
|
||||
{a:t0, b:t1, line:KAND_TO_PZ, to:"Полярные Зори", end:t1},
|
||||
{a:t1, b:t2, line:PZ_TO_ZASHEEK, to:"Зашеек", end:t2},
|
||||
{a:t1, b:z, line:PZ_TO_ZASHEEK, to:"Зашеек", end:z}
|
||||
];
|
||||
if (t2 > z) segs.push({a:z, b:t2, line:PZ_TO_ZASHEEK, to:"Зашеек", end:t2, hold:true});
|
||||
segs.push(
|
||||
{a:t2, b:t3, line:PZ_TO_ZASHEEK_BACK, to:"Полярные Зори", end:t3},
|
||||
{a:t3, b:back, line:KAND_TO_PZ_BACK, to:"Кандалакша", end:back}
|
||||
]
|
||||
};
|
||||
);
|
||||
return {t0, tEnd: back, segs: segs.filter(s=>s.b > s.a)};
|
||||
}
|
||||
|
||||
// Все рейсы, которые сейчас в пути, вместе с точкой и участком
|
||||
@@ -580,7 +662,8 @@ function busesNow(){
|
||||
if (T < run.t0 || T > run.tEnd) return;
|
||||
const seg = run.segs.find(s => T >= s.a && T <= s.b);
|
||||
if (!seg) return;
|
||||
const f = seg.b > seg.a ? (T - seg.a) / (seg.b - seg.a) : 0;
|
||||
// на участке стоянки в Зашейке точка не двигается — держим конец подъезда
|
||||
const f = seg.hold ? 1 : (seg.b > seg.a ? (T - seg.a) / (seg.b - seg.a) : 0);
|
||||
out.push({idx, seg, at: pointAt(seg.line, f)});
|
||||
});
|
||||
return out;
|
||||
@@ -606,8 +689,9 @@ function renderMapStatus(list){
|
||||
}
|
||||
const many = list.length > 1;
|
||||
head.innerHTML = `Сейчас в пути <b>${list.length}</b> ${many ? "автобуса" : "автобус"}`;
|
||||
rows.innerHTML = list.map(b =>
|
||||
`<div><span class="when">→ ${b.seg.to}</span><span>прибытие в ${fmtHM(b.seg.end)}</span></div>`
|
||||
rows.innerHTML = list.map(b => b.seg.hold
|
||||
? `<div><span class="when">Зашеек</span><span>стоит на остановке, выезд в ${fmtHM(b.seg.b)}</span></div>`
|
||||
: `<div><span class="when">→ ${b.seg.to}</span><span>прибытие в ${fmtHM(b.seg.end)}</span></div>`
|
||||
).join("");
|
||||
}
|
||||
|
||||
@@ -703,6 +787,7 @@ function initMap(){
|
||||
zoom: 10,
|
||||
attributionControl: true
|
||||
});
|
||||
MAPREF = map;
|
||||
|
||||
map.on("load", () => {
|
||||
ready = true;
|
||||
@@ -748,7 +833,60 @@ function initMap(){
|
||||
});
|
||||
}
|
||||
|
||||
if (MODE === "map") initMap();
|
||||
// Нижняя шторка карты. За ручку её тянут вверх, короткое касание — переключает.
|
||||
// Внутри своя прокрутка, поэтому карта под текстом не ездит, а карта
|
||||
// VK перерисовывается под изменившуюся высоту холста.
|
||||
function initSheet(){
|
||||
const box = document.getElementById("mapBox");
|
||||
const sheet = document.getElementById("mapSheet");
|
||||
const grab = document.getElementById("sheetGrab");
|
||||
if (!box || !sheet || !grab) return;
|
||||
|
||||
const MIN = 118;
|
||||
const max = () => Math.max(MIN, Math.round(box.clientHeight * 0.72));
|
||||
let open = false, h = MIN, startY = 0, startH = 0, moved = 0, dragged = false;
|
||||
|
||||
function set(next, expanded){
|
||||
h = Math.max(MIN, Math.min(max(), Math.round(next)));
|
||||
sheet.style.height = h + "px";
|
||||
open = !!expanded;
|
||||
sheet.classList.toggle("open", open);
|
||||
if (MAPREF) MAPREF.resize(); // холст карты стал ниже — пересчитать
|
||||
}
|
||||
|
||||
// Захват указателя есть не во всех окружениях (и может не сработать при
|
||||
// синтетическом событии), поэтому на него не опираемся критично.
|
||||
const captured = e => { try { return grab.hasPointerCapture(e.pointerId); }
|
||||
catch(_){ return true; } };
|
||||
const capture = e => { try { grab.setPointerCapture(e.pointerId); } catch(_){} };
|
||||
const release = e => { try { grab.releasePointerCapture(e.pointerId); } catch(_){} };
|
||||
|
||||
grab.addEventListener("pointerdown", e=>{
|
||||
startY = e.clientY; startH = h; moved = 0; dragged = false;
|
||||
capture(e);
|
||||
});
|
||||
grab.addEventListener("pointermove", e=>{
|
||||
if (!captured(e)) return;
|
||||
moved = startY - e.clientY;
|
||||
if (Math.abs(moved) > 6) dragged = true;
|
||||
const want = startH + moved;
|
||||
set(want, want > MIN + 8);
|
||||
});
|
||||
grab.addEventListener("pointerup", e=>{
|
||||
release(e);
|
||||
if (!dragged) { set(open ? MIN : max(), !open); return; } // просто тап
|
||||
const expanded = h > box.clientHeight * 0.4;
|
||||
set(expanded ? max() : MIN, expanded);
|
||||
});
|
||||
grab.addEventListener("pointercancel", ()=>{ set(open ? max() : MIN, open); });
|
||||
|
||||
// поворот экрана или смена высоты окна — пересчитать предел раскрытия
|
||||
window.addEventListener("resize", ()=> set(open ? max() : MIN, open));
|
||||
|
||||
set(MIN, false);
|
||||
}
|
||||
|
||||
if (MODE === "map") { initMap(); initSheet(); }
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user