mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 07:28:53 -05:00
Improve mobile responsiveness for settings and chapters
Adds mobile-first responsive CSS for the custom settings menu and chapters overlay, including adaptive sizing, touch-friendly interactions, and prevention of body scroll when overlays are open. Updates CustomSettingsMenu.js to detect mobile devices, handle touch events for menu items, and manage scroll locking and haptic feedback for a better mobile user experience.
This commit is contained in:
parent
8eb196bf74
commit
f787087531
@ -290,6 +290,11 @@
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.chapter-close button svg {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
}
|
||||
|
||||
.chapter-close button:active {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: scale(0.95);
|
||||
@ -400,6 +405,11 @@
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.chapter-close button svg {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
}
|
||||
|
||||
.chapter-title h3 a {
|
||||
font-size: 13px !important;
|
||||
line-height: 16px !important;
|
||||
@ -462,6 +472,11 @@
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.chapter-close button svg {
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
}
|
||||
|
||||
.chapter-title h3 a {
|
||||
font-size: 12px !important;
|
||||
line-height: 15px !important;
|
||||
@ -525,6 +540,11 @@
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.chapter-close button svg {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
}
|
||||
|
||||
.chapter-title h3 a {
|
||||
font-size: 13px !important;
|
||||
line-height: 16px !important;
|
||||
|
||||
@ -213,3 +213,313 @@ sup.hd-badge {
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* ===== MOBILE RESPONSIVE DESIGN ===== */
|
||||
|
||||
/* Mobile-first responsive design for tablets and large phones */
|
||||
@media (max-width: 767px) {
|
||||
.custom-settings-overlay {
|
||||
right: 8px !important;
|
||||
left: auto !important;
|
||||
width: 260px !important;
|
||||
max-width: calc(100vw - 16px) !important;
|
||||
height: auto !important;
|
||||
max-height: calc(100vh - 100px) !important;
|
||||
bottom: 45px !important;
|
||||
border-radius: 10px !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.settings-close-btn {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.settings-close-btn svg {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
}
|
||||
|
||||
.settings-item {
|
||||
padding: 10px 12px;
|
||||
gap: 8px;
|
||||
min-height: 48px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.settings-item:active {
|
||||
background: rgba(255, 255, 255, 0.12) !important;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.settings-left {
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.settings-right {
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.submenu-header {
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.submenu-header:active {
|
||||
background: rgba(255, 255, 255, 0.12) !important;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.speed-option,
|
||||
.quality-option,
|
||||
.subtitle-option {
|
||||
padding: 10px 12px;
|
||||
min-height: 44px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.speed-option:active,
|
||||
.quality-option:active,
|
||||
.subtitle-option:active {
|
||||
background: rgba(255, 255, 255, 0.12) !important;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* Hide hover effects on touch devices */
|
||||
.settings-item:hover,
|
||||
.speed-option:hover,
|
||||
.quality-option:hover,
|
||||
.subtitle-option:hover,
|
||||
.submenu-header:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/* Small phones (portrait) - Ultra compact */
|
||||
@media (max-width: 480px) {
|
||||
.custom-settings-overlay {
|
||||
right: 6px !important;
|
||||
left: auto !important;
|
||||
width: 240px !important;
|
||||
max-width: calc(100vw - 12px) !important;
|
||||
height: auto !important;
|
||||
max-height: calc(100vh - 80px) !important;
|
||||
bottom: 35px !important;
|
||||
border-radius: 8px !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.settings-close-btn {
|
||||
width: 28px !important;
|
||||
height: 28px !important;
|
||||
}
|
||||
|
||||
.settings-close-btn svg {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
}
|
||||
|
||||
.settings-item {
|
||||
padding: 8px 10px;
|
||||
gap: 6px;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.settings-left {
|
||||
gap: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.settings-right {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.submenu-header {
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.speed-option,
|
||||
.quality-option,
|
||||
.subtitle-option {
|
||||
padding: 8px 10px;
|
||||
min-height: 40px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Smaller icons on mobile */
|
||||
.settings-item-svg svg,
|
||||
.submenu-header svg {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Very small screens (< 360px) - Maximum compactness */
|
||||
@media (max-width: 360px) {
|
||||
.custom-settings-overlay {
|
||||
right: 4px !important;
|
||||
left: auto !important;
|
||||
width: 220px !important;
|
||||
max-width: calc(100vw - 8px) !important;
|
||||
height: auto !important;
|
||||
max-height: calc(100vh - 70px) !important;
|
||||
bottom: 30px !important;
|
||||
border-radius: 6px !important;
|
||||
font-size: 11px !important;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
padding: 6px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.settings-close-btn {
|
||||
width: 26px !important;
|
||||
height: 26px !important;
|
||||
}
|
||||
|
||||
.settings-close-btn svg {
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
}
|
||||
|
||||
.settings-item {
|
||||
padding: 6px 8px;
|
||||
gap: 4px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.settings-left {
|
||||
gap: 4px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.settings-right {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.submenu-header {
|
||||
padding: 6px 8px;
|
||||
font-size: 12px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.speed-option,
|
||||
.quality-option,
|
||||
.subtitle-option {
|
||||
padding: 6px 8px;
|
||||
min-height: 36px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Even smaller icons for very small screens */
|
||||
.settings-item-svg svg,
|
||||
.submenu-header svg {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
}
|
||||
|
||||
sup.hd-badge {
|
||||
font-size: 8px;
|
||||
padding: 0px 3px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Landscape orientation on mobile - Compact for limited height */
|
||||
@media (max-width: 767px) and (orientation: landscape) {
|
||||
.custom-settings-overlay {
|
||||
height: auto !important;
|
||||
max-height: calc(100vh - 60px) !important;
|
||||
bottom: 25px !important;
|
||||
right: 6px !important;
|
||||
left: auto !important;
|
||||
width: 250px !important;
|
||||
max-width: calc(100vw - 12px) !important;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.settings-close-btn {
|
||||
width: 28px !important;
|
||||
height: 28px !important;
|
||||
}
|
||||
|
||||
.settings-close-btn svg {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
}
|
||||
|
||||
.settings-item {
|
||||
padding: 7px 10px;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.submenu-header {
|
||||
padding: 7px 10px;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.speed-option,
|
||||
.quality-option,
|
||||
.subtitle-option {
|
||||
padding: 6px 10px;
|
||||
min-height: 36px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Touch-friendly improvements for all mobile devices */
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.settings-item,
|
||||
.speed-option,
|
||||
.quality-option,
|
||||
.subtitle-option,
|
||||
.submenu-header {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
transform 0.1s ease;
|
||||
}
|
||||
|
||||
/* Ensure smooth scrolling on touch devices */
|
||||
.custom-settings-overlay,
|
||||
.speed-submenu,
|
||||
.quality-submenu,
|
||||
.subtitles-submenu {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-behavior: smooth;
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
|
||||
/* Remove hover states on touch devices */
|
||||
.settings-item:hover,
|
||||
.speed-option:hover,
|
||||
.quality-option:hover,
|
||||
.subtitle-option:hover,
|
||||
.submenu-header:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,9 @@ class CustomSettingsMenu extends Component {
|
||||
// Touch scroll detection (mobile)
|
||||
this.isTouchScrolling = false;
|
||||
this.touchStartY = 0;
|
||||
this.isMobile = this.detectMobile();
|
||||
this.isSmallScreen = window.innerWidth <= 480;
|
||||
this.touchThreshold = 150; // ms for tap vs scroll detection
|
||||
|
||||
// Bind methods
|
||||
this.createSettingsButton = this.createSettingsButton.bind(this);
|
||||
@ -35,6 +38,9 @@ class CustomSettingsMenu extends Component {
|
||||
this.refreshSubtitlesSubmenu = this.refreshSubtitlesSubmenu.bind(this);
|
||||
this.handleSubtitleChange = this.handleSubtitleChange.bind(this);
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||
this.detectMobile = this.detectMobile.bind(this);
|
||||
this.handleMobileInteraction = this.handleMobileInteraction.bind(this);
|
||||
this.setupResizeListener = this.setupResizeListener.bind(this);
|
||||
|
||||
// Initialize after player is ready
|
||||
this.player().ready(() => {
|
||||
@ -42,9 +48,101 @@ class CustomSettingsMenu extends Component {
|
||||
this.createSettingsOverlay();
|
||||
this.setupEventListeners();
|
||||
this.restoreSubtitlePreference();
|
||||
this.setupResizeListener();
|
||||
});
|
||||
}
|
||||
|
||||
detectMobile() {
|
||||
return (
|
||||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
|
||||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 0) ||
|
||||
window.matchMedia('(hover: none) and (pointer: coarse)').matches
|
||||
);
|
||||
}
|
||||
|
||||
handleMobileInteraction(element, callback) {
|
||||
if (!this.isMobile) return;
|
||||
|
||||
let touchStartTime = 0;
|
||||
let touchMoved = false;
|
||||
let touchStartY = 0;
|
||||
|
||||
element.addEventListener(
|
||||
'touchstart',
|
||||
(e) => {
|
||||
touchStartTime = Date.now();
|
||||
touchMoved = false;
|
||||
touchStartY = e.touches[0].clientY;
|
||||
|
||||
// Add visual feedback
|
||||
element.style.transform = 'scale(0.98)';
|
||||
element.style.transition = 'transform 0.1s ease';
|
||||
|
||||
// Add haptic feedback if available
|
||||
if (navigator.vibrate) {
|
||||
navigator.vibrate(30);
|
||||
}
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
element.addEventListener(
|
||||
'touchmove',
|
||||
(e) => {
|
||||
const touchMoveY = e.touches[0].clientY;
|
||||
const deltaY = Math.abs(touchMoveY - touchStartY);
|
||||
const scrollThreshold = this.isSmallScreen ? 5 : 8;
|
||||
|
||||
if (deltaY > scrollThreshold) {
|
||||
touchMoved = true;
|
||||
// Remove visual feedback when scrolling
|
||||
element.style.transform = '';
|
||||
}
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
element.addEventListener(
|
||||
'touchend',
|
||||
(e) => {
|
||||
const touchEndTime = Date.now();
|
||||
const touchDuration = touchEndTime - touchStartTime;
|
||||
|
||||
// Reset visual feedback
|
||||
element.style.transform = '';
|
||||
|
||||
// Only trigger if it's a quick tap (not a scroll)
|
||||
const tapThreshold = this.isSmallScreen ? 120 : this.touchThreshold;
|
||||
if (!touchMoved && touchDuration < tapThreshold) {
|
||||
e.preventDefault();
|
||||
callback(e);
|
||||
}
|
||||
},
|
||||
{ passive: false }
|
||||
);
|
||||
|
||||
element.addEventListener(
|
||||
'touchcancel',
|
||||
() => {
|
||||
// Reset visual feedback on cancel
|
||||
element.style.transform = '';
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
}
|
||||
|
||||
setupResizeListener() {
|
||||
const handleResize = () => {
|
||||
this.isSmallScreen = window.innerWidth <= 480;
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener('orientationchange', handleResize);
|
||||
|
||||
// Store reference for cleanup
|
||||
this.resizeHandler = handleResize;
|
||||
}
|
||||
|
||||
createSettingsButton() {
|
||||
const controlBar = this.player().getChild('controlBar');
|
||||
|
||||
@ -236,8 +334,57 @@ class CustomSettingsMenu extends Component {
|
||||
// Create subtitles submenu (YouTube-like)
|
||||
this.createSubtitlesSubmenu();
|
||||
|
||||
// Add mobile-specific optimizations
|
||||
if (this.isMobile) {
|
||||
this.settingsOverlay.style.cssText += `
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-behavior: smooth;
|
||||
overscroll-behavior-y: contain;
|
||||
touch-action: pan-y;
|
||||
`;
|
||||
|
||||
// Prevent body scroll when overlay is open on mobile
|
||||
this.settingsOverlay.addEventListener(
|
||||
'touchstart',
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
}
|
||||
|
||||
// Add to control bar
|
||||
this.player().el().appendChild(this.settingsOverlay);
|
||||
|
||||
// Add mobile touch handling to settings items
|
||||
if (this.isMobile) {
|
||||
const settingsItems = this.settingsOverlay.querySelectorAll('.settings-item');
|
||||
settingsItems.forEach((item) => {
|
||||
this.handleMobileInteraction(item, (e) => {
|
||||
// Handle the same logic as click events
|
||||
if (e.target.closest('[data-setting="playback-speed"]')) {
|
||||
this.speedSubmenu.style.display = 'flex';
|
||||
this.qualitySubmenu.style.display = 'none';
|
||||
if (this.subtitlesSubmenu) this.subtitlesSubmenu.style.display = 'none';
|
||||
}
|
||||
|
||||
if (e.target.closest('[data-setting="quality"]')) {
|
||||
this.qualitySubmenu.style.display = 'flex';
|
||||
this.speedSubmenu.style.display = 'none';
|
||||
if (this.subtitlesSubmenu) this.subtitlesSubmenu.style.display = 'none';
|
||||
}
|
||||
|
||||
if (e.target.closest('[data-setting="subtitles"]')) {
|
||||
this.refreshSubtitlesSubmenu();
|
||||
if (this.subtitlesSubmenu) {
|
||||
this.subtitlesSubmenu.style.display = 'flex';
|
||||
this.speedSubmenu.style.display = 'none';
|
||||
this.qualitySubmenu.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
createSpeedSubmenu() {
|
||||
@ -812,9 +959,28 @@ class CustomSettingsMenu extends Component {
|
||||
if (isVisible) {
|
||||
this.settingsOverlay.classList.remove('show');
|
||||
this.settingsOverlay.style.display = 'none';
|
||||
|
||||
// Restore body scroll on mobile when closing
|
||||
if (this.isMobile) {
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.width = '';
|
||||
}
|
||||
} else {
|
||||
this.settingsOverlay.classList.add('show');
|
||||
this.settingsOverlay.style.display = 'block';
|
||||
|
||||
// Add haptic feedback on mobile when opening
|
||||
if (this.isMobile && navigator.vibrate) {
|
||||
navigator.vibrate(30);
|
||||
}
|
||||
|
||||
// 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.speedSubmenu.style.display = 'none'; // Hide submenu when main menu toggles
|
||||
@ -1252,6 +1418,19 @@ class CustomSettingsMenu extends Component {
|
||||
// Remove event listeners
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
|
||||
// Clean up resize listener
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
window.removeEventListener('orientationchange', this.resizeHandler);
|
||||
}
|
||||
|
||||
// Restore body scroll on mobile when disposing
|
||||
if (this.isMobile) {
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.width = '';
|
||||
}
|
||||
|
||||
// Remove DOM elements
|
||||
if (this.settingsOverlay) {
|
||||
this.settingsOverlay.remove();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user