/** * Вебкомпонент для ініціації оновлення сторінки (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 = `
`; // 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: 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): При свайпі буде викликано window.location.reload(); */