diff --git a/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js b/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js index 0e2236fc..1709244d 100644 --- a/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js +++ b/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js @@ -20,6 +20,7 @@ class CustomChaptersOverlay extends Component { this.touchStartTime = 0; this.touchThreshold = 150; // ms for tap vs scroll detection this.isSmallScreen = window.innerWidth <= 480; + this.scrollY = 0; // Track scroll position before locking // Bind methods this.createOverlay = this.createOverlay.bind(this); @@ -31,6 +32,8 @@ class CustomChaptersOverlay extends Component { this.handleMobileInteraction = this.handleMobileInteraction.bind(this); this.setupResizeListener = this.setupResizeListener.bind(this); this.handleResize = this.handleResize.bind(this); + this.lockBodyScroll = this.lockBodyScroll.bind(this); + this.unlockBodyScroll = this.unlockBodyScroll.bind(this); // Initialize after player is ready this.player().ready(() => { @@ -65,6 +68,9 @@ class CustomChaptersOverlay extends Component { const el = this.player().el(); if (el) el.classList.remove('chapters-open'); + + // Restore body scroll on mobile when closing + this.unlockBodyScroll(); } setupResizeListener() { @@ -164,6 +170,8 @@ class CustomChaptersOverlay extends Component { this.overlay.style.display = 'none'; const el = this.player().el(); if (el) el.classList.remove('chapters-open'); + // Restore body scroll on mobile when closing + this.unlockBodyScroll(); }; chapterClose.appendChild(closeBtn); playlistTitle.appendChild(chapterClose); @@ -355,6 +363,37 @@ class CustomChaptersOverlay extends Component { } } + lockBodyScroll() { + if (!this.isMobile) return; + + // Save current scroll position + this.scrollY = window.scrollY || window.pageYOffset; + + // Lock body scroll with proper iOS handling + document.body.style.overflow = 'hidden'; + document.body.style.position = 'fixed'; + document.body.style.top = `-${this.scrollY}px`; + document.body.style.left = '0'; + document.body.style.right = '0'; + document.body.style.width = '100%'; + } + + unlockBodyScroll() { + if (!this.isMobile) return; + + // Restore body scroll + const scrollY = this.scrollY; + document.body.style.overflow = ''; + document.body.style.position = ''; + document.body.style.top = ''; + document.body.style.left = ''; + document.body.style.right = ''; + document.body.style.width = ''; + + // Restore scroll position + window.scrollTo(0, scrollY); + } + toggleOverlay() { if (!this.overlay) return; @@ -369,17 +408,11 @@ class CustomChaptersOverlay extends Component { navigator.vibrate(30); } - // Prevent body scroll on mobile when overlay is open - if (this.isMobile) { - if (isHidden) { - document.body.style.overflow = 'hidden'; - document.body.style.position = 'fixed'; - document.body.style.width = '100%'; - } else { - document.body.style.overflow = ''; - document.body.style.position = ''; - document.body.style.width = ''; - } + // Lock/unlock body scroll on mobile when overlay opens/closes + if (isHidden) { + this.lockBodyScroll(); + } else { + this.unlockBodyScroll(); } try { @@ -390,7 +423,9 @@ class CustomChaptersOverlay extends Component { m.classList.remove('vjs-lock-showing'); m.style.display = 'none'; }); - } catch (e) {} + } catch { + // Ignore errors when closing menus + } } updateCurrentChapter() { @@ -406,7 +441,6 @@ class CustomChaptersOverlay extends Component { currentTime >= chapter.startTime && (index === this.chaptersData.length - 1 || currentTime < this.chaptersData[index + 1].startTime); - const handle = item.querySelector('.playlist-drag-handle'); const dynamic = item.querySelector('.meta-dynamic'); if (isPlaying) { currentChapterIndex = index; @@ -463,11 +497,7 @@ class CustomChaptersOverlay extends Component { if (el) el.classList.remove('chapters-open'); // Restore body scroll on mobile - if (this.isMobile) { - document.body.style.overflow = ''; - document.body.style.position = ''; - document.body.style.width = ''; - } + this.unlockBodyScroll(); } } @@ -479,11 +509,7 @@ class CustomChaptersOverlay extends Component { if (el) el.classList.remove('chapters-open'); // Restore body scroll on mobile when disposing - if (this.isMobile) { - document.body.style.overflow = ''; - document.body.style.position = ''; - document.body.style.width = ''; - } + this.unlockBodyScroll(); // Clean up event listeners if (this.handleResize) { diff --git a/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js b/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js index 5c3dc163..6a66c861 100644 --- a/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js +++ b/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js @@ -25,6 +25,7 @@ class CustomSettingsMenu extends Component { this.isMobile = this.detectMobile(); this.isSmallScreen = window.innerWidth <= 480; this.touchThreshold = 150; // ms for tap vs scroll detection + this.scrollY = 0; // Track scroll position before locking // Bind methods this.createSettingsButton = this.createSettingsButton.bind(this); @@ -41,6 +42,8 @@ class CustomSettingsMenu extends Component { this.detectMobile = this.detectMobile.bind(this); this.handleMobileInteraction = this.handleMobileInteraction.bind(this); this.setupResizeListener = this.setupResizeListener.bind(this); + this.lockBodyScroll = this.lockBodyScroll.bind(this); + this.unlockBodyScroll = this.unlockBodyScroll.bind(this); // Initialize after player is ready this.player().ready(() => { @@ -656,6 +659,8 @@ class CustomSettingsMenu extends Component { if (btnEl) { btnEl.classList.remove('settings-clicked'); } + // Restore body scroll on mobile when closing + this.unlockBodyScroll(); }; closeButton.addEventListener('click', closeFunction); @@ -942,6 +947,37 @@ class CustomSettingsMenu extends Component { this.startSubtitleSync(); } + lockBodyScroll() { + if (!this.isMobile) return; + + // Save current scroll position + this.scrollY = window.scrollY || window.pageYOffset; + + // Lock body scroll with proper iOS handling + document.body.style.overflow = 'hidden'; + document.body.style.position = 'fixed'; + document.body.style.top = `-${this.scrollY}px`; + document.body.style.left = '0'; + document.body.style.right = '0'; + document.body.style.width = '100%'; + } + + unlockBodyScroll() { + if (!this.isMobile) return; + + // Restore body scroll + const scrollY = this.scrollY; + document.body.style.overflow = ''; + document.body.style.position = ''; + document.body.style.top = ''; + document.body.style.left = ''; + document.body.style.right = ''; + document.body.style.width = ''; + + // Restore scroll position + window.scrollTo(0, scrollY); + } + toggleSettings(e) { // e.stopPropagation(); const isVisible = this.settingsOverlay.classList.contains('show'); @@ -954,11 +990,7 @@ class CustomSettingsMenu extends Component { this.stopKeepingControlsVisible(); // Restore body scroll on mobile when closing - if (this.isMobile) { - document.body.style.overflow = ''; - document.body.style.position = ''; - document.body.style.width = ''; - } + this.unlockBodyScroll(); } else { this.settingsOverlay.classList.add('show'); this.settingsOverlay.style.display = 'block'; @@ -972,11 +1004,7 @@ class CustomSettingsMenu extends Component { } // Prevent body scroll on mobile when overlay is open - if (this.isMobile) { - document.body.style.overflow = 'hidden'; - document.body.style.position = 'fixed'; - document.body.style.width = '100%'; - } + this.lockBodyScroll(); } this.speedSubmenu.style.display = 'none'; // Hide submenu when main menu toggles @@ -1002,6 +1030,9 @@ class CustomSettingsMenu extends Component { this.settingsOverlay.classList.add('show'); this.settingsOverlay.style.display = 'block'; + // Lock body scroll when opening + this.lockBodyScroll(); + // Hide other submenus and show subtitles submenu this.speedSubmenu.style.display = 'none'; if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none'; @@ -1072,11 +1103,7 @@ class CustomSettingsMenu extends Component { } // Restore body scroll on mobile when closing - if (this.isMobile) { - document.body.style.overflow = ''; - document.body.style.position = ''; - document.body.style.width = ''; - } + this.unlockBodyScroll(); } } @@ -1417,6 +1444,8 @@ class CustomSettingsMenu extends Component { if (btnEl) { btnEl.classList.remove('settings-clicked'); } + // Restore body scroll on mobile when closing + this.unlockBodyScroll(); } } @@ -1493,11 +1522,7 @@ class CustomSettingsMenu extends Component { } // Restore body scroll on mobile when disposing - if (this.isMobile) { - document.body.style.overflow = ''; - document.body.style.position = ''; - document.body.style.width = ''; - } + this.unlockBodyScroll(); // Remove DOM elements if (this.settingsOverlay) {