fix: Disable tooltip on touch devices (mobile and tablets)

This commit is contained in:
Yiannis Christodoulou 2025-09-28 00:31:55 +03:00
parent 119dc966fa
commit 57616c6b81
6 changed files with 203 additions and 12 deletions

View File

@ -8,6 +8,28 @@ html {
box-sizing: inherit; box-sizing: inherit;
} }
/* Global tooltip disabling for touch devices */
@media (hover: none) and (pointer: coarse) {
/* Disable all CSS-based tooltips on touch devices */
.video-js .vjs-control:hover::after,
.video-js .vjs-control:focus::after,
.video-js .vjs-control:active::after,
.video-js button.vjs-button:hover span.vjs-control-text,
.vjs-chapter-marker:hover .vjs-chapter-marker-tooltip,
.vjs-chapter-floating-tooltip,
.vjs-sprite-preview-tooltip {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
/* Exception: Allow intentional touch-activated tooltips */
.video-js .vjs-autoplay-toggle.touch-active::after {
opacity: 1 !important;
visibility: visible !important;
}
}
.playlist-items a { .playlist-items a {
text-decoration: none !important; text-decoration: none !important;
} }
@ -1342,6 +1364,56 @@ button.vjs-button > .vjs-icon-placeholder:before {
width: auto; width: auto;
opacity: 1; opacity: 1;
} }
/* Disable all tooltips on touch devices */
.video-js .vjs-control:hover::after,
.video-js .vjs-control:focus::after,
.video-js .vjs-control:active::after {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
.video-js .vjs-play-control:hover::after,
.video-js .vjs-mute-control:hover::after,
.video-js .vjs-volume-panel:hover::after,
.video-js .vjs-fullscreen-control:hover::after,
.video-js .vjs-picture-in-picture-control:hover::after,
.video-js .vjs-settings-control:hover::after,
.video-js .vjs-chapters-control:hover::after,
.video-js .vjs-autoplay-toggle:hover::after,
.video-js .vjs-next-video-control:hover::after,
.video-js .vjs-remaining-time:hover::after {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable Video.js native control text tooltips on touch devices */
.video-js button.vjs-button:hover span.vjs-control-text {
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable chapter marker tooltips on touch devices */
.vjs-chapter-marker:hover .vjs-chapter-marker-tooltip {
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable chapter floating tooltips on touch devices */
.vjs-chapter-floating-tooltip,
.vjs-sprite-preview-tooltip {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
/* Exception: Allow touch-activated autoplay tooltip on touch devices */
.video-js .vjs-autoplay-toggle.touch-active::after {
opacity: 1 !important;
visibility: visible !important;
}
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
@ -1410,6 +1482,7 @@ button.vjs-button > .vjs-icon-placeholder:before {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
} }
/* Disable all tooltips on tablets */
.video-js .vjs-control:hover::after, .video-js .vjs-control:hover::after,
.video-js .vjs-control:focus::after, .video-js .vjs-control:focus::after,
.video-js .vjs-control:active::after { .video-js .vjs-control:active::after {
@ -1432,6 +1505,26 @@ button.vjs-button > .vjs-icon-placeholder:before {
opacity: 0 !important; opacity: 0 !important;
visibility: hidden !important; visibility: hidden !important;
} }
/* Disable Video.js native control text tooltips on tablets */
.video-js button.vjs-button:hover span.vjs-control-text {
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable chapter marker tooltips on tablets */
.vjs-chapter-marker:hover .vjs-chapter-marker-tooltip {
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable chapter floating tooltips on tablets */
.vjs-chapter-floating-tooltip,
.vjs-sprite-preview-tooltip {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
} }
@media (max-width: 1024px) { @media (max-width: 1024px) {
@ -1531,6 +1624,7 @@ button.vjs-button > .vjs-icon-placeholder:before {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
} }
/* Disable all tooltips on mobile */
.video-js .vjs-control:hover::after, .video-js .vjs-control:hover::after,
.video-js .vjs-control:focus::after, .video-js .vjs-control:focus::after,
.video-js .vjs-control:active::after { .video-js .vjs-control:active::after {
@ -1554,6 +1648,27 @@ button.vjs-button > .vjs-icon-placeholder:before {
visibility: hidden !important; visibility: hidden !important;
} }
/* Disable Video.js native control text tooltips on mobile */
.video-js button.vjs-button:hover span.vjs-control-text {
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable chapter marker tooltips on mobile */
.vjs-chapter-marker:hover .vjs-chapter-marker-tooltip {
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable chapter floating tooltips on mobile */
.vjs-chapter-floating-tooltip,
.vjs-sprite-preview-tooltip {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
/* Exception: Allow touch-activated autoplay tooltip on mobile */
.video-js .vjs-autoplay-toggle.touch-active::after { .video-js .vjs-autoplay-toggle.touch-active::after {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
@ -1569,9 +1684,6 @@ button.vjs-button > .vjs-icon-placeholder:before {
.vjs-related-video-thumbnail { .vjs-related-video-thumbnail {
height: 100%; height: 100%;
} }
.vjs-chapter-floating-tooltip {
font-size: 11px !important;
}
.video-js-root-main .video-js.video-js-rounded-corners .custom-chapters-overlay { .video-js-root-main .video-js.video-js-rounded-corners .custom-chapters-overlay {
border-bottom-left-radius: 12px !important; border-bottom-left-radius: 12px !important;
border-bottom-right-radius: 12px !important; border-bottom-right-radius: 12px !important;
@ -1665,6 +1777,7 @@ button.vjs-button > .vjs-icon-placeholder:before {
padding: 0 10px; padding: 0 10px;
} }
/* Disable all tooltips on small mobile screens */
.video-js .vjs-control:hover::after, .video-js .vjs-control:hover::after,
.video-js .vjs-control:focus::after, .video-js .vjs-control:focus::after,
.video-js .vjs-control:active::after { .video-js .vjs-control:active::after {
@ -1688,6 +1801,26 @@ button.vjs-button > .vjs-icon-placeholder:before {
visibility: hidden !important; visibility: hidden !important;
} }
/* Disable Video.js native control text tooltips on small mobile */
.video-js button.vjs-button:hover span.vjs-control-text {
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable chapter marker tooltips on small mobile */
.vjs-chapter-marker:hover .vjs-chapter-marker-tooltip {
opacity: 0 !important;
visibility: hidden !important;
}
/* Disable chapter floating tooltips on small mobile */
.vjs-chapter-floating-tooltip,
.vjs-sprite-preview-tooltip {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
.vjs-related-video-thumbnail { .vjs-related-video-thumbnail {
height: 100%; height: 100%;
} }

View File

@ -113,6 +113,18 @@ class AutoplayToggleButton extends Button {
// Add touch support for mobile tooltips // Add touch support for mobile tooltips
addTouchSupport(button) { addTouchSupport(button) {
// Check if device is touch-enabled
const isTouchDevice =
this.options_.isTouchDevice ||
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0;
// Only add touch tooltip support on actual touch devices
if (!isTouchDevice) {
return;
}
let touchStartTime = 0; let touchStartTime = 0;
let touchHandled = false; let touchHandled = false;
@ -132,19 +144,20 @@ class AutoplayToggleButton extends Button {
(e) => { (e) => {
const touchDuration = Date.now() - touchStartTime; const touchDuration = Date.now() - touchStartTime;
// Only show tooltip for quick taps (not swipes) // Only show tooltip for quick taps (not swipes) and only on mobile screens
if (touchDuration < 500) { const isMobileScreen = window.innerWidth <= 767;
if (touchDuration < 500 && isMobileScreen) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
// Show tooltip // Show tooltip briefly
button.classList.add('touch-active'); button.classList.add('touch-active');
touchHandled = true; touchHandled = true;
// Hide tooltip after delay // Hide tooltip after shorter delay on mobile
setTimeout(() => { setTimeout(() => {
button.classList.remove('touch-active'); button.classList.remove('touch-active');
}, 2000); }, 1500);
} }
}, },
{ passive: false } { passive: false }

View File

@ -73,6 +73,18 @@ class ChapterMarkers extends Component {
} }
setupProgressBarHover() { setupProgressBarHover() {
// Check if device is touch-enabled (tablet/mobile)
const isTouchDevice =
this.options_.isTouchDevice ||
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0;
// Skip tooltip setup on touch devices
if (isTouchDevice) {
return;
}
const progressControl = this.player().getChild('controlBar').getChild('progressControl'); const progressControl = this.player().getChild('controlBar').getChild('progressControl');
if (!progressControl) return; if (!progressControl) return;

View File

@ -23,6 +23,18 @@ class SpritePreview extends Component {
} }
setupProgressBarHover() { setupProgressBarHover() {
// Check if device is touch-enabled (tablet/mobile)
const isTouchDevice =
this.options_.isTouchDevice ||
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0;
// Skip tooltip setup on touch devices
if (isTouchDevice) {
return;
}
const progressControl = this.player().getChild('controlBar').getChild('progressControl'); const progressControl = this.player().getChild('controlBar').getChild('progressControl');
if (!progressControl) return; if (!progressControl) return;

View File

@ -183,6 +183,10 @@ class EmbedInfoOverlay extends Component {
const player = this.player(); const player = this.player();
const overlay = this.el(); const overlay = this.el();
// Check if device is touch-enabled
const isTouchDevice =
'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
// Show/hide with controls // Show/hide with controls
player.on('useractive', () => { player.on('useractive', () => {
overlay.style.opacity = '1'; overlay.style.opacity = '1';
@ -190,8 +194,15 @@ class EmbedInfoOverlay extends Component {
}); });
player.on('userinactive', () => { player.on('userinactive', () => {
// On touch devices, keep overlay visible longer or don't hide it as aggressively
if (isTouchDevice) {
// Keep visible on touch devices when user inactive
overlay.style.opacity = '0.8';
overlay.style.visibility = 'visible';
} else {
overlay.style.opacity = '0'; overlay.style.opacity = '0';
overlay.style.visibility = 'hidden'; overlay.style.visibility = 'hidden';
}
}); });
// Always show when paused // Always show when paused

View File

@ -25,6 +25,11 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
// Check if this is an embed player (disable next video and autoplay features) // Check if this is an embed player (disable next video and autoplay features)
const isEmbedPlayer = videoId === 'video-embed'; const isEmbedPlayer = videoId === 'video-embed';
// Utility function to detect touch devices
const isTouchDevice = useMemo(() => {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
}, []);
// Environment-based development mode configuration // Environment-based development mode configuration
const isDevMode = import.meta.env.VITE_DEV_MODE === 'true' || window.location.hostname.includes('vercel.app'); const isDevMode = import.meta.env.VITE_DEV_MODE === 'true' || window.location.hostname.includes('vercel.app');
// Safely access window.MEDIA_DATA with fallback using useMemo // Safely access window.MEDIA_DATA with fallback using useMemo
@ -2152,6 +2157,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
try { try {
const autoplayToggleButton = new AutoplayToggleButton(playerRef.current, { const autoplayToggleButton = new AutoplayToggleButton(playerRef.current, {
userPreferences: userPreferences.current, userPreferences: userPreferences.current,
isTouchDevice: isTouchDevice,
}); });
// Add it before the chapters button (or at a suitable position) // Add it before the chapters button (or at a suitable position)
const chaptersButtonIndex = chaptersButton const chaptersButtonIndex = chaptersButton
@ -2378,16 +2384,19 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
// Use original ChapterMarkers with sprite functionality when chapters exist // Use original ChapterMarkers with sprite functionality when chapters exist
const chapterMarkers = new ChapterMarkers(playerRef.current, { const chapterMarkers = new ChapterMarkers(playerRef.current, {
previewSprite: mediaData.previewSprite, previewSprite: mediaData.previewSprite,
isTouchDevice: isTouchDevice,
}); });
seekBar.addChild(chapterMarkers); seekBar.addChild(chapterMarkers);
} else if (mediaData.previewSprite) { } else if (mediaData.previewSprite && !isTouchDevice) {
// Use separate SpritePreview component only when no chapters but sprite data exists // Use separate SpritePreview component only when no chapters but sprite data exists
// Skip on touch devices to avoid unwanted tooltips
const spritePreview = new SpritePreview(playerRef.current, { const spritePreview = new SpritePreview(playerRef.current, {
previewSprite: mediaData.previewSprite, previewSprite: mediaData.previewSprite,
isTouchDevice: isTouchDevice,
}); });
seekBar.addChild(spritePreview); seekBar.addChild(spritePreview);
// Setup sprite preview hover functionality // Setup sprite preview hover functionality (only on non-touch devices)
setTimeout(() => { setTimeout(() => {
spritePreview.setupProgressBarHover(); spritePreview.setupProgressBarHover();
}, 100); }, 100);
@ -2466,6 +2475,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
userPreferences: userPreferences.current, userPreferences: userPreferences.current,
qualities: availableQualities, qualities: availableQualities,
hasSubtitles: hasSubtitles, hasSubtitles: hasSubtitles,
isTouchDevice: isTouchDevice,
}); });
// If qualities change per video (e.g., via MEDIA_DATA update), refresh menu // If qualities change per video (e.g., via MEDIA_DATA update), refresh menu