Files
Sheep-Service/web/lib/customElements/swipeUpdater.js
2026-06-07 01:16:17 +03:00

280 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Вебкомпонент для ініціації оновлення сторінки (Pull-to-Refresh)
* за допомогою свайпу вниз на пристроях з iOS/iPadOS у режимі PWA.
*/
class SwipeUpdater extends HTMLElement {
constructor() {
super();
// 1. Створення Shadow DOM
// Використовуємо тіньовий DOM для інкапсуляції стилів та структури
const shadow = this.attachShadow({ mode: 'open' });
// 2. Внутрішня функція оновлення за замовчуванням
this._appReload = () => {
console.log('Стандартна функція: Перезавантаження сторінки');
// Стандартна дія - перезавантаження сторінки
window.location.reload();
};
// 3. Внутрішній стан
this._isReadyToReload = false; // Прапорець, що вказує на готовність до оновлення
this._isStandalone = false; // Кеш для перевірки PWA режиму
this._isTouching = false; // Чи тримає користувач палець на екрані
// 4. Створення елементів (Внутрішній HTML)
shadow.innerHTML = `
<div id="swipe_updater">
<div id="swipe_block">
<svg id="swipe_icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-state="active">
<path d="M413.1 222.5l22.2 22.2c9.4 9.4 9.4 24.6 0 33.9L241 473c-9.4 9.4-24.6 9.4-33.9 0L12.7 278.6c-9.4-9.4-9.4-24.6 0-33.9l22.2-22.2c9.5-9.5 25-9.3 34.3.4L184 343.4V56c0-13.3 10.7-24 24-24h32c13.3 0 24 10.7 24 24v287.4l114.8-120.5c9.3-9.8 24.8-10 34.3-.4z"></path>
</svg>
</div>
</div>
<style>
:host {
display: block; /* Важливо для позиціонування кореневого елемента */
}
#swipe_updater {
position: absolute;
top: 0px;
width: 100%;
z-index: 0; /* Базовий z-index */
/* Використання CSS-змінних для кастомізації кольорів */
--swipe-color-theme1: var(--ColorThemes2, #525151); /* Колір фону іконки */
--swipe-color-theme2: var(--ColorThemes3, #f3f3f3); /* Колір іконки та рамки */
}
#swipe_block {
/* Розрахунок ширини та відступу для центрифікації в певних макетах */
width: calc(100% - 252px);
margin-left: 252px;
height: 50px;
display: flex;
justify-content: center;
align-items: flex-end;
position: relative;
}
#swipe_icon {
width: 20px;
fill: var(--swipe-color-theme2);
transform: rotate(0deg); /* Початковий стан: стрілка вниз */
position: absolute;
/* Початкове приховане позиціонування */
margin-top: -30px;
top: -30px;
background: var(--swipe-color-theme1);
border: 2px solid var(--swipe-color-theme2);
border-radius: 50%;
padding: 10px;
display: flex;
overflow: hidden;
height: 0;
opacity: 0;
/* Анімація: прихована іконка плавно з'являється (активується/деактивується) */
transition: height 0ms 450ms, opacity 450ms 0ms, transform 450ms;
}
#swipe_icon[data-state="active"] {
height: 20px;
margin-top: -45px;
top: -45px;
opacity: 1;
/* Анімація: активна іконка видима */
transition: height 0ms 0ms, opacity 450ms 0ms, transform 450ms;
}
/* Адаптивні стилі для зміни центрифікації на різних екранах */
@media (max-width: 1100px){
#swipe_block {
width: calc(100% - 122px);
margin-left: 122px;
}
}
@media (max-width: 700px), (max-height: 540px) {
#swipe_block {
width: 100%;
margin-left: 0;
}
/* Зміна кольорів для менших екранів */
#swipe_updater {
--swipe-color-theme1: var(--ColorThemes0, #525151);
--swipe-color-theme2: var(--ColorThemes3, #f3f3f3);;
}
}
</style>
`;
// 5. Збереження посилань на елементи Shadow DOM
this._animID = shadow.getElementById('swipe_updater');
this._animIconID = shadow.getElementById('swipe_icon');
// 6. Прив'язка контексту `this` для обробників подій
this.handleScroll = this.handleScroll.bind(this);
this.handleTouchStart = this.handleTouchStart.bind(this);
this.handleTouchEnd = this.handleTouchEnd.bind(this);
}
/**
* Метод для встановлення користувацької функції оновлення.
* Замінює стандартне перезавантаження сторінки.
* @param {function} func - Користувацька функція, що буде викликана при свайпі.
*/
setReloadFunction(func) {
if (typeof func === 'function') {
this._appReload = func;
} else {
console.error('setReloadFunction вимагає передати функцію.');
}
}
// Фіксуємо, що користувач торкнувся екрана
handleTouchStart() {
this._isTouching = true;
}
// Обробка події, коли користувач відпускає екран
handleTouchEnd() {
this._isTouching = false;
const threshold = -165;
// Якщо палець відпустили, а ефект «гумового скролу» вже повернувся вище порогу
// (тобто користувач передумав і потягнув назад вгору перед тим як відпустити)
if (window.scrollY > threshold && this._isReadyToReload) {
this._resetRefreshState();
}
}
/**
* Обробник події скролу (головна логіка Pull-to-Refresh).
* Відстежує прокручування вище верхньої межі сторінки (`window.scrollY < 0`).
*/
handleScroll() {
// Перевірка на режим Standalone (PWA) - функціональність актуальна переважно тут
if (!this._isStandalone) return;
const scrollY = window.scrollY;
const threshold = -165; // Поріг прокрутки (наприклад, -125px) для активації оновлення
// 1. Анімація іконки під час прокручування за верхню межу (scrollY < 0)
if (scrollY <= -10) {
// Зміщення іконки разом із прокруткою
this._animIconID.style.top = `${scrollY / 1.5}px`;
this._animID.style.zIndex = '115'; // Піднімаємо z-index для видимості над контентом
} else {
this._animIconID.style.top = '0px';
this._animID.style.zIndex = '0'; // Повертаємо базовий z-index
}
// 2. Логіка активації "готовий до оновлення"
if (scrollY <= threshold) {
if (!this._isReadyToReload) {
this._isReadyToReload = true;
// Поворот іконки на 180 градусів
this._animIconID.style.transform = 'rotate(180deg)';
this._animIconID.setAttribute('data-state', ''); // Деактивація стану "active"
}
}
// Скасування жесту під час руху пальцем вгору (ще до відпускання)
else if (scrollY > threshold && scrollY < 0 && this._isTouching) {
if (this._isReadyToReload) {
this._isReadyToReload = false;
this._animIconID.style.transform = 'rotate(0deg)';
this._animIconID.setAttribute('data-state', 'active');
}
}
// 3. Логіка виклику оновлення, коли сторінка повернулась до нуля
else if (scrollY >= 0) {
if (this._isReadyToReload) {
this._appReload();
this._resetRefreshState();
}
}
}
/**
* Допоміжний метод для повного скидання графічного стану
*/
_resetRefreshState() {
this._isReadyToReload = false;
this._animIconID.style.transform = 'rotate(0deg)';
this._animIconID.setAttribute('data-state', 'active');
this._animIconID.style.top = '0px';
this._animID.style.zIndex = '0';
}
/**
* Lifecycle hook: викликається при додаванні елемента в DOM.
* Додаємо обробник події прокручування.
*/
connectedCallback() {
// Перевіряємо PWA режим ОДИН раз при додаванні компонента на сторінку
this._isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (this._isStandalone) {
window.addEventListener('scroll', this.handleScroll);
// passive: true покращує продуктивність скролу на мобільних пристроях
window.addEventListener('touchstart', this.handleTouchStart, { passive: true });
window.addEventListener('touchend', this.handleTouchEnd, { passive: true });
}
}
/**
* Lifecycle hook: викликається при видаленні елемента з DOM.
* Видаляємо обробник події прокручування для запобігання витоку пам'яті.
*/
disconnectedCallback() {
// Чистимо абсолютно всі додані слухачі
window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('touchstart', this.handleTouchStart);
window.removeEventListener('touchend', this.handleTouchEnd);
}
}
// Реєстрація веб-компонента
if (!customElements.get('swipe-updater')) {
customElements.define('swipe-updater', SwipeUpdater);
}
/*
============================
ПРИКЛАД ВИКОРИСТАННЯ
============================
*/
/*
1. Додайте цей елемент у свій HTML:
<swipe-updater id="swipe-updater"></swipe-updater>
2. Отримайте посилання на компонент у JS:
const Updater = document.querySelector('swipe-updater');
3. Користувацька функція оновлення
function customReload() {
const now = new Date().toLocaleTimeString();
console.log(`Користувацьке оновлення: оновлено о ${now}`);
document.querySelector('h1').textContent = `Сторінка оновлена о ${now}`;
// Тут можна виконати AJAX-запит, оновити DOM тощо.
}
4. Перевизначення функції оновлення компонента
if (Updater && Updater.setReloadFunction) {
Updater.setReloadFunction(customReload);
} else {
console.error('Компонент SwipeUpdater не знайдено або не готовий.');
}
💡 Приклад стандартного використання (якщо не викликати setReloadFunction):
<swipe-updater></swipe-updater>
При свайпі буде викликано window.location.reload();
*/