Карта в меню, чистка повторов, подпись разработчика
This commit is contained in:
@@ -55,7 +55,11 @@
|
|||||||
.fullbtn:hover{border-color:var(--accent2)}
|
.fullbtn:hover{border-color:var(--accent2)}
|
||||||
.fullbtn .chev{float:right;transition:.2s;color:var(--muted)}
|
.fullbtn .chev{float:right;transition:.2s;color:var(--muted)}
|
||||||
.fullbtn.open .chev{transform:rotate(180deg)}
|
.fullbtn.open .chev{transform:rotate(180deg)}
|
||||||
.fulltitle{display:none;margin:0;padding:14px 12px 2px;font-size:16px;font-weight:700}
|
|
||||||
|
/* кнопка карты — под блоком полного расписания, только в приложении */
|
||||||
|
#mapBtn{display:none;margin:6px 0 6px}
|
||||||
|
body.app #mapBtn{display:block}
|
||||||
|
.mapbtn::before{content:"⌖ ";color:var(--accent2)}
|
||||||
|
|
||||||
/* строка заголовка: стрелка назад слева, меню справа, название ровно по центру */
|
/* строка заголовка: стрелка назад слева, меню справа, название ровно по центру */
|
||||||
.headwrap{position:relative}
|
.headwrap{position:relative}
|
||||||
@@ -68,6 +72,8 @@
|
|||||||
body.app .iconbtn,body.about .iconbtn,body.map .iconbtn{display:inline-flex}
|
body.app .iconbtn,body.about .iconbtn,body.map .iconbtn{display:inline-flex}
|
||||||
/* на главном экране стрелки нет, но место под неё оставляем, иначе съедет центр */
|
/* на главном экране стрелки нет, но место под неё оставляем, иначе съедет центр */
|
||||||
body.app .iconbtn.back{visibility:hidden}
|
body.app .iconbtn.back{visibility:hidden}
|
||||||
|
/* но в справке стрелка «назад» нужна и должна быть кликабельной */
|
||||||
|
body.app.showhelp .iconbtn.back{visibility:visible}
|
||||||
/* троеточие в круглой кнопке приподнято: рамка остаётся на одной линии с названием */
|
/* троеточие в круглой кнопке приподнято: рамка остаётся на одной линии с названием */
|
||||||
#navMenu{padding-bottom:7px}
|
#navMenu{padding-bottom:7px}
|
||||||
.iconbtn:active{border-color:var(--accent2)}
|
.iconbtn:active{border-color:var(--accent2)}
|
||||||
@@ -89,10 +95,28 @@
|
|||||||
body.about .aboutcard{display:block}
|
body.about .aboutcard{display:block}
|
||||||
body.about .pickers,body.about .where,body.about .dir,
|
body.about .pickers,body.about .where,body.about .dir,
|
||||||
body.about #fullTable,body.about .fullbtn{display:none}
|
body.about #fullTable,body.about .fullbtn{display:none}
|
||||||
|
/* внизу «О приложении» те же даты и источник уже написаны своим текстом,
|
||||||
|
общая сноска про расписание тут только дублирует их */
|
||||||
|
body.about footer{display:none}
|
||||||
.aboutcard h2{margin:0 0 4px;font-size:17px}
|
.aboutcard h2{margin:0 0 4px;font-size:17px}
|
||||||
.aboutcard .ver{color:var(--muted);font-size:14px;margin-bottom:12px}
|
.aboutcard .ver{color:var(--muted);font-size:14px;margin-bottom:12px}
|
||||||
.aboutcard p{margin:0 0 10px;font-size:14px;line-height:1.5}
|
.aboutcard p{margin:0 0 10px;font-size:14px;line-height:1.5}
|
||||||
.aboutcard p.muted{color:var(--muted);font-size:13px}
|
.aboutcard p.muted{color:var(--muted);font-size:13px}
|
||||||
|
.aboutcard p.dev{color:var(--muted);font-size:13px;text-align:center;margin:18px 0 0}
|
||||||
|
.aboutcard h3{margin:16px 0 6px;font-size:14px;color:var(--accent2)}
|
||||||
|
.aboutcard a{color:var(--accent2)}
|
||||||
|
|
||||||
|
/* «Справка» — тот же экран, что и расписание, просто другой блок */
|
||||||
|
.helpcard{display:none;background:var(--card);border:1px solid var(--line);
|
||||||
|
border-radius:14px;padding:16px;margin-bottom:10px}
|
||||||
|
body.app.showhelp .helpcard{display:block}
|
||||||
|
body.app.showhelp .pickers,body.app.showhelp .where,body.app.showhelp .dir,
|
||||||
|
body.app.showhelp .fullbtn,body.app.showhelp #fullTable,
|
||||||
|
body.app.showhelp #mapBtn,body.app.showhelp footer{display:none}
|
||||||
|
.helpcard h2{margin:0 0 4px;font-size:17px}
|
||||||
|
.helpcard h3{margin:16px 0 6px;font-size:14px;color:var(--accent2)}
|
||||||
|
.helpcard h3:first-of-type{margin-top:12px}
|
||||||
|
.helpcard p{margin:0 0 10px;font-size:14px;line-height:1.5}
|
||||||
|
|
||||||
/* экран «Карта»: страница целиком занята картой, шапка остаётся */
|
/* экран «Карта»: страница целиком занята картой, шапка остаётся */
|
||||||
body.map{height:100vh;height:100dvh;min-height:0;padding:24px 16px 16px;overflow:hidden}
|
body.map{height:100vh;height:100dvh;min-height:0;padding:24px 16px 16px;overflow:hidden}
|
||||||
@@ -132,12 +156,6 @@
|
|||||||
.mapstatus .rows div{display:flex;gap:8px;align-items:baseline}
|
.mapstatus .rows div{display:flex;gap:8px;align-items:baseline}
|
||||||
.mapstatus .rows .when{color:var(--txt);font-variant-numeric:tabular-nums;
|
.mapstatus .rows .when{color:var(--txt);font-variant-numeric:tabular-nums;
|
||||||
white-space:nowrap;font-size:12px}
|
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}
|
.pin{transform:translate(-50%,-100%);white-space:nowrap;pointer-events:none}
|
||||||
@@ -191,7 +209,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="menu" id="menuBox" hidden>
|
<nav class="menu" id="menuBox" hidden>
|
||||||
<button class="menuitem" id="mMap">Карта</button>
|
<button class="menuitem" id="mMap">Карта маршрута</button>
|
||||||
|
<button class="menuitem" id="mHelp">Справка</button>
|
||||||
<button class="menuitem" id="mAbout">О приложении</button>
|
<button class="menuitem" id="mAbout">О приложении</button>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -212,7 +231,6 @@
|
|||||||
|
|
||||||
<button class="fullbtn" id="fullBtn">Полное расписание <span class="chev">▾</span></button>
|
<button class="fullbtn" id="fullBtn">Полное расписание <span class="chev">▾</span></button>
|
||||||
<section class="tablecard" id="fullTable">
|
<section class="tablecard" id="fullTable">
|
||||||
<h2 class="fulltitle">Полное расписание</h2>
|
|
||||||
<div class="days" id="dayPick">
|
<div class="days" id="dayPick">
|
||||||
<button data-day="weekday" class="active">Будни</button>
|
<button data-day="weekday" class="active">Будни</button>
|
||||||
<button data-day="saturday">Суббота</button>
|
<button data-day="saturday">Суббота</button>
|
||||||
@@ -227,56 +245,91 @@
|
|||||||
<div id="tableBody"></div>
|
<div id="tableBody"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<button class="fullbtn mapbtn" id="mapBtn">Карта маршрута</button>
|
||||||
|
|
||||||
<section class="mapcard" id="mapBox" hidden>
|
<section class="mapcard" id="mapBox" hidden>
|
||||||
<div class="mapcanvas loading" id="mapCanvas"></div>
|
<div class="mapcanvas loading" id="mapCanvas"></div>
|
||||||
<div class="mapsheet" id="mapSheet">
|
<div class="mapsheet" id="mapSheet">
|
||||||
<div class="grab" id="sheetGrab">
|
<div class="grab" id="sheetGrab">
|
||||||
<span class="bar"></span>
|
<span class="bar"></span>
|
||||||
<span class="cap" id="sheetCap">Автобусы и расписание</span>
|
<span class="cap" id="sheetCap">Автобусы в пути</span>
|
||||||
<span class="chev">▴</span>
|
<span class="chev">▴</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mapstatus">
|
<div class="mapstatus">
|
||||||
<div class="head" id="mapHead">Карта маршрута</div>
|
<div id="mapContent">
|
||||||
|
<div class="head" id="mapHead">Сейчас автобусов нет</div>
|
||||||
<div class="rows" id="mapRows"></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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="helpcard" id="helpBox">
|
||||||
|
<h2>Справка</h2>
|
||||||
|
|
||||||
|
<h3>Как читать расписание</h3>
|
||||||
|
<p>Таблица и списки показывают официальное время <b>отправлений</b>.
|
||||||
|
Время прибытия перевозчик не публикует, поэтому приложение считает его
|
||||||
|
само: до Зашейка — как выезд с Полярных Зорь плюс 20 минут, до
|
||||||
|
Кандалакши — плюс 50 минут.</p>
|
||||||
|
<p>Одна остановка — исключение. Когда автобус приезжает на Полярные Зори,
|
||||||
|
он сразу уходит дальше, поэтому время прибытия совпадает с отправлением
|
||||||
|
следующей остановки. В Зашейке автобус задерживается, и там прибытие
|
||||||
|
считается по времени в пути, а не по расписанию.</p>
|
||||||
|
<p>Из-за этого интервалы в обе стороны по одной дороге отличаются
|
||||||
|
на 5–10 минут. Так и есть в официальном расписании, это не ошибка
|
||||||
|
приложения.</p>
|
||||||
|
|
||||||
|
<h3>Время прибытия в карточках</h3>
|
||||||
|
<p>В списке рейсов под заголовком направления показано время прибытия
|
||||||
|
в Зашеек или в Кандалакшу — серым под временем отправления. Время
|
||||||
|
московское (МСК).</p>
|
||||||
|
|
||||||
|
<h3>Положение автобусов на карте</h3>
|
||||||
|
<p>GPS у приложения нет. Положение считается по расписанию: между
|
||||||
|
известными точками маршрута координаты интерполируются по времени рейса,
|
||||||
|
доля пути — по длине дороги, поэтому метка идёт по трассе, а не по прямой.
|
||||||
|
В Зашейке автобус стоит, пока не подошло время обратного выезда.</p>
|
||||||
|
<p>Точность — до нескольких минут. В пробки реальное отставание больше,
|
||||||
|
и метка может показывать автобус позади или впереди машины.</p>
|
||||||
|
<p>Список под картой обновляется каждые 20 секунд: для каждого автобуса
|
||||||
|
в пути указано направление и время прибытия на следующую остановку.</p>
|
||||||
|
|
||||||
|
<h3>Когда обновляется расписание</h3>
|
||||||
|
<p>Часы, ближайшие рейсы и положение автобусов обновляются автоматически.
|
||||||
|
Список рейсов — раз в минуту, часы — раз в секунду. В приложении нет
|
||||||
|
кнопки обновления: перезапускать ничего не нужно.</p>
|
||||||
|
|
||||||
|
<h3>Где брать точные данные</h3>
|
||||||
|
<p>Время отправления перевозчик меняет без предупреждения. Сроки
|
||||||
|
действия расписания и телефон диспетчерской службы указаны
|
||||||
|
в разделе «О приложении» — уточнять время лучше там.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="aboutcard" id="aboutBox">
|
<section class="aboutcard" id="aboutBox">
|
||||||
<h2>О приложении</h2>
|
<h2>О приложении</h2>
|
||||||
<div class="ver" id="aboutVer"></div>
|
<div class="ver" id="aboutVer"></div>
|
||||||
<p>Расписание автобуса №127 по маршруту Кандалакша — Полярные Зори — Зашеек.</p>
|
<p>Расписание автобуса №127 по маршруту Кандалакша — Полярные Зори — Зашеек.
|
||||||
<p>Ближайшие рейсы считаются по московскому времени и обновляются каждые 30 секунд.
|
Ближайшие рейсы по вашей остановке, полное расписание по будням, субботам
|
||||||
Полное расписание раскрывается прямо на этом экране — кнопкой под списком рейсов,
|
и воскресеньям, а также карта с положением автобусов в пути.</p>
|
||||||
с переключением между буднями, субботой и воскресеньем.</p>
|
<p>Всё время — московское (МСК). Данные обновляются автоматически,
|
||||||
<p>В меню есть «Карта»: там видно, где сейчас едут автобусы, и как пройден
|
интернет нужен только для карты.</p>
|
||||||
участок между остановками. Положение считается по расписанию — приложение
|
|
||||||
не получает данные с GPS, поэтому это оценка с точностью до нескольких минут.</p>
|
<h3>Расписание</h3>
|
||||||
<p class="muted">Расписание сверено с официальным: отправления взяты из данных
|
<p>Действует летнее расписание <b>с 1 мая по 31 октября 2026 года</b>.
|
||||||
диспетчерской службы перевозчика, летнее расписание
|
Отправления взяты из данных диспетчерской службы перевозчика,
|
||||||
с 01.05.2026 по 31.10.2026. Время московское (МСК).</p>
|
время прибытия приложение рассчитывает само — подробно в «Справке».</p>
|
||||||
<p class="muted">Карта и тайлы — VK Карты, геометрия дороги — OpenStreetMap.
|
<p>Источник: <a href="https://kandalaksha.org/static/auto.html" target="_blank" rel="noopener">kandalaksha.org/static/auto.html</a><br>
|
||||||
Пуш-уведомления приходят через RuStore Push — для этого нужен
|
Телефон диспетчерской службы: 8(921) 174-26-00, 8(911) 303-63-28</p>
|
||||||
|
|
||||||
|
<h3>Что внутри</h3>
|
||||||
|
<p>Карта и тайлы — VK Карты, геометрия дороги — OpenStreetMap.
|
||||||
|
Пуш-уведомления приходят через RuStore Push: для этого нужен
|
||||||
установленный и авторизованный RuStore на устройстве.</p>
|
установленный и авторизованный RuStore на устройстве.</p>
|
||||||
|
<p class="muted">Приложение не использует геолокацию и не передаёт
|
||||||
|
данные о вашем местоположении: положение автобусов вычисляется
|
||||||
|
по расписанию на устройстве.</p>
|
||||||
|
<p class="dev">Разработчик © UA1ZBE</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer>Летнее расписание с 01.05.2026 по 31.10.2026 · отправления по данным
|
<footer>Летнее расписание с 01.05.2026 по 31.10.2026 · отправления по данным
|
||||||
@@ -560,13 +613,47 @@ if (NATIVE) {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
menuBox.hidden = !menuBox.hidden;
|
menuBox.hidden = !menuBox.hidden;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// «Справка» — блок на этом же экране. Снимаем её и кнопкой «‹»,
|
||||||
|
// и аппаратной «назад»: для этого кладём в историю браузера состояние,
|
||||||
|
// чтобы система отдала его нам, а не закрыла приложение.
|
||||||
|
const helpOn = () => document.body.classList.contains("showhelp");
|
||||||
|
const showHelp = on => {
|
||||||
|
document.body.classList.toggle("showhelp", on);
|
||||||
|
if (on) window.scrollTo(0, 0);
|
||||||
|
};
|
||||||
|
document.getElementById("mMap").addEventListener("click",()=>{ closeMenu(); Bus.openMap(); });
|
||||||
|
document.getElementById("mHelp").addEventListener("click",()=>{
|
||||||
|
closeMenu();
|
||||||
|
showHelp(true);
|
||||||
|
history.pushState({ help: 1 }, "");
|
||||||
|
});
|
||||||
|
window.addEventListener("popstate", () => { if (helpOn()) showHelp(false); });
|
||||||
|
const hideHelp = () => {
|
||||||
|
if (!helpOn()) return false;
|
||||||
|
showHelp(false);
|
||||||
|
if (history.state && history.state.help) history.back(); // убрать и из истории
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Один шаг «назад»: справка, потом меню. false — значит, уходить из приложения.
|
||||||
|
const backStep = () => {
|
||||||
|
if (hideHelp()) return true;
|
||||||
|
if (!menuBox.hidden) { closeMenu(); return true; }
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
document.getElementById("navBack").addEventListener("click",()=>{
|
document.getElementById("navBack").addEventListener("click",()=>{
|
||||||
// «назад» сначала сворачивает открытое меню и только потом уходит назад
|
// «назад» сначала закрывает справку, потом сворачивает меню,
|
||||||
if (!menuBox.hidden) { closeMenu(); return; }
|
// и только потом уходит назад
|
||||||
|
if (backStep()) return;
|
||||||
Bus.goBack();
|
Bus.goBack();
|
||||||
});
|
});
|
||||||
document.getElementById("mMap").addEventListener("click",()=>{ closeMenu(); Bus.openMap(); });
|
// Системная кнопка «назад» WebView не перехватывает, поэтому Android
|
||||||
|
// спрашивает у страницы то же самое, что и стрелка «‹» (MainActivity).
|
||||||
|
window.__busBack = backStep;
|
||||||
document.getElementById("mAbout").addEventListener("click",()=>{ closeMenu(); Bus.openAbout(); });
|
document.getElementById("mAbout").addEventListener("click",()=>{ closeMenu(); Bus.openAbout(); });
|
||||||
|
document.getElementById("mapBtn").addEventListener("click",()=>Bus.openMap());
|
||||||
document.addEventListener("click",e=>{
|
document.addEventListener("click",e=>{
|
||||||
if (!e.target.closest(".menu") && !e.target.closest("#navMenu")) closeMenu();
|
if (!e.target.closest(".menu") && !e.target.closest("#navMenu")) closeMenu();
|
||||||
});
|
});
|
||||||
@@ -679,12 +766,25 @@ function pinElement(cls, num, text){
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Свёрнутая шторка подстраивается под содержимое, а список автобусов
|
||||||
|
// обновляется каждые 20 секунд. Ширину/высоту слушаем и у DOM, и у окна:
|
||||||
|
// первый срабатывает при переносе строк, второй — при смене шрифта.
|
||||||
|
// Заполняется в initSheet, ниже по файлу.
|
||||||
|
let setSheetHeight = null;
|
||||||
|
const refitSheet = () => { if (setSheetHeight) setSheetHeight(null); };
|
||||||
|
if (typeof ResizeObserver === "function") {
|
||||||
|
const st = document.getElementById("mapContent");
|
||||||
|
if (st) new ResizeObserver(refitSheet).observe(st);
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", refitSheet);
|
||||||
|
|
||||||
function renderMapStatus(list){
|
function renderMapStatus(list){
|
||||||
const head = document.getElementById("mapHead");
|
const head = document.getElementById("mapHead");
|
||||||
const rows = document.getElementById("mapRows");
|
const rows = document.getElementById("mapRows");
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
head.innerHTML = "Сейчас автобусов нет";
|
head.innerHTML = "Сейчас автобусов нет";
|
||||||
rows.innerHTML = `<div>Первый рейс — в ${SCHEDULES[todayKey()][0][0]} из Кандалакши.</div>`;
|
rows.innerHTML = `<div>Первый рейс — в ${SCHEDULES[todayKey()][0][0]} из Кандалакши.</div>`;
|
||||||
|
refitSheet();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const many = list.length > 1;
|
const many = list.length > 1;
|
||||||
@@ -693,6 +793,7 @@ function renderMapStatus(list){
|
|||||||
? `<div><span class="when">Зашеек</span><span>стоит на остановке, выезд в ${fmtHM(b.seg.b)}</span></div>`
|
? `<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>`
|
: `<div><span class="when">→ ${b.seg.to}</span><span>прибытие в ${fmtHM(b.seg.end)}</span></div>`
|
||||||
).join("");
|
).join("");
|
||||||
|
refitSheet(); // строк могло стать больше или меньше
|
||||||
}
|
}
|
||||||
|
|
||||||
function startMap(map, mmrgl){
|
function startMap(map, mmrgl){
|
||||||
@@ -842,12 +943,24 @@ function initSheet(){
|
|||||||
const grab = document.getElementById("sheetGrab");
|
const grab = document.getElementById("sheetGrab");
|
||||||
if (!box || !sheet || !grab) return;
|
if (!box || !sheet || !grab) return;
|
||||||
|
|
||||||
const MIN = 118;
|
const status = sheet.querySelector(".mapstatus");
|
||||||
const max = () => Math.max(MIN, Math.round(box.clientHeight * 0.72));
|
const content = document.getElementById("mapContent");
|
||||||
let open = false, h = MIN, startY = 0, startH = 0, moved = 0, dragged = false;
|
const max = () => Math.max(60, Math.round(box.clientHeight * 0.72));
|
||||||
|
// Свёрнутое состояние — ровно по содержимому: ручка, список автобусов и
|
||||||
|
// внутренние отступы прокручиваемого блока. Высоту берём у #mapContent,
|
||||||
|
// а не у .mapstatus: тот растягивается флексом, и его scrollHeight при
|
||||||
|
// раскрытой шторке равен clientHeight — из-за этого сворачивание
|
||||||
|
// «по содержимому» заканчивалось бы раздуванием до максимума.
|
||||||
|
const min = () => {
|
||||||
|
const cs = getComputedStyle(status);
|
||||||
|
const pad = (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0);
|
||||||
|
return Math.min(max(), Math.round(grab.getBoundingClientRect().height
|
||||||
|
+ content.offsetHeight + pad));
|
||||||
|
};
|
||||||
|
let open = false, h = min(), startY = 0, startH = 0, moved = 0, dragged = false;
|
||||||
|
|
||||||
function set(next, expanded){
|
function set(next, expanded){
|
||||||
h = Math.max(MIN, Math.min(max(), Math.round(next)));
|
h = Math.max(min(), Math.min(max(), Math.round(next)));
|
||||||
sheet.style.height = h + "px";
|
sheet.style.height = h + "px";
|
||||||
open = !!expanded;
|
open = !!expanded;
|
||||||
sheet.classList.toggle("open", open);
|
sheet.classList.toggle("open", open);
|
||||||
@@ -870,20 +983,26 @@ function initSheet(){
|
|||||||
moved = startY - e.clientY;
|
moved = startY - e.clientY;
|
||||||
if (Math.abs(moved) > 6) dragged = true;
|
if (Math.abs(moved) > 6) dragged = true;
|
||||||
const want = startH + moved;
|
const want = startH + moved;
|
||||||
set(want, want > MIN + 8);
|
set(want, want > min() + 8);
|
||||||
});
|
});
|
||||||
grab.addEventListener("pointerup", e=>{
|
grab.addEventListener("pointerup", e=>{
|
||||||
release(e);
|
release(e);
|
||||||
if (!dragged) { set(open ? MIN : max(), !open); return; } // просто тап
|
if (!dragged) { set(open ? min() : max(), !open); return; } // просто тап
|
||||||
const expanded = h > box.clientHeight * 0.4;
|
const expanded = h > box.clientHeight * 0.4;
|
||||||
set(expanded ? max() : MIN, expanded);
|
set(expanded ? max() : min(), expanded);
|
||||||
});
|
});
|
||||||
grab.addEventListener("pointercancel", ()=>{ set(open ? max() : MIN, open); });
|
grab.addEventListener("pointercancel", ()=>{ set(open ? max() : min(), open); });
|
||||||
|
|
||||||
|
// Высота шторки. next === null — просто пересчитать по текущему содержимому,
|
||||||
|
// не трогая состояние «раскрыта/свёрнута»: этим пользуется список автобусов.
|
||||||
|
setSheetHeight = next => {
|
||||||
|
set(next === null ? (open ? max() : min()) : next, open);
|
||||||
|
};
|
||||||
|
|
||||||
// поворот экрана или смена высоты окна — пересчитать предел раскрытия
|
// поворот экрана или смена высоты окна — пересчитать предел раскрытия
|
||||||
window.addEventListener("resize", ()=> set(open ? max() : MIN, open));
|
window.addEventListener("resize", ()=> set(open ? max() : min(), open));
|
||||||
|
|
||||||
set(MIN, false);
|
set(min(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MODE === "map") { initMap(); initSheet(); }
|
if (MODE === "map") { initMap(); initSheet(); }
|
||||||
|
|||||||
Reference in New Issue
Block a user