feat: Implement mobile double-tap gestures for video player controls and apply CSS optimizations.

This commit is contained in:
kuekhaoyang
2026-02-16 23:27:45 +08:00
parent 98e314e31a
commit a9888353a3
12 changed files with 2344 additions and 24 deletions
+4
View File
@@ -23,12 +23,16 @@
.video-card-wrapper {
/* CSS containment for isolation */
contain: layout style paint;
}
@supports (content-visibility: auto) {
.video-card-wrapper {
/* Content visibility for lazy rendering */
content-visibility: auto;
/* Reduce layout shifts */
contain-intrinsic-size: auto 400px;
}
}
/* Reduce repaints on images */
+9 -1
View File
@@ -54,6 +54,7 @@ select:focus-visible {
}
::-webkit-scrollbar-thumb {
background: rgba(128, 128, 128, 0.5);
background: color-mix(in srgb, var(--glass-bg) 80%, transparent);
border-radius: var(--radius-full);
border: 2px solid transparent;
@@ -61,6 +62,7 @@ select:focus-visible {
}
::-webkit-scrollbar-thumb:hover {
background: rgba(0, 122, 255, 0.6);
background: color-mix(in srgb, var(--accent-color) 60%, transparent);
background-clip: padding-box;
}
@@ -74,6 +76,7 @@ select:focus-visible {
}
.search-history-dropdown::-webkit-scrollbar-thumb {
background: rgba(128, 128, 128, 0.5);
background: color-mix(in srgb, var(--glass-bg) 80%, transparent);
border-radius: var(--radius-full);
}
@@ -118,8 +121,13 @@ nav:where(.sticky, .fixed),
animation: fadeInUp 0.2s ease-out;
}
img {
@supports (content-visibility: auto) {
img {
content-visibility: auto;
}
}
img {
transform: translateZ(0);
}
+1
View File
@@ -26,6 +26,7 @@
.glass-input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.3);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-color) 30%, transparent);
}
+4
View File
@@ -50,11 +50,14 @@
}
.search-history-item:hover {
background: rgba(0, 122, 255, 0.1);
background: color-mix(in srgb, var(--accent-color) 10%, transparent);
}
.search-history-item.highlighted {
background: rgba(0, 122, 255, 0.15);
background: color-mix(in srgb, var(--accent-color) 15%, transparent);
box-shadow: inset 0 0 0 1px rgba(0, 122, 255, 0.3);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-color) 30%, transparent);
}
@@ -75,6 +78,7 @@
}
.search-history-remove:hover {
background: rgba(128, 128, 128, 0.2);
background: color-mix(in srgb, var(--text-color-secondary) 20%, transparent);
color: var(--text-color);
transform: scale(1.1);
+1
View File
@@ -2,6 +2,7 @@
.spinner {
width: 48px;
height: 48px;
border: 5px solid rgba(128, 128, 128, 0.35);
border: 5px solid color-mix(in srgb, var(--glass-bg) 50%, transparent);
border-top-color: var(--accent-color);
border-radius: var(--radius-full);
+27 -5
View File
@@ -10,6 +10,7 @@ import { DesktopControlsWrapper } from './desktop/DesktopControlsWrapper';
import { DesktopOverlayWrapper } from './desktop/DesktopOverlayWrapper';
import { usePlayerSettings } from './hooks/usePlayerSettings';
import { useIsIOS, useIsMobile } from '@/lib/hooks/mobile/useDeviceDetection';
import { useDoubleTap } from '@/lib/hooks/mobile/useDoubleTap';
import './web-fullscreen.css';
interface DesktopVideoPlayerProps {
@@ -141,6 +142,7 @@ export function DesktopVideoPlayer({
const {
handleMouseMove,
handleTouchToggleControls,
togglePlay,
handlePlay,
handlePause,
@@ -149,6 +151,28 @@ export function DesktopVideoPlayer({
handleVideoError,
} = logic;
// Mobile double-tap gesture for skip forward/backward
const { handleTap } = useDoubleTap({
onSingleTap: handleTouchToggleControls,
onDoubleTapLeft: () => {
logic.skipBackward();
handleMouseMove(); // Reset 3s auto-hide timer
},
onDoubleTapRight: () => {
logic.skipForward();
handleMouseMove(); // Reset 3s auto-hide timer
},
onSkipContinueLeft: () => {
logic.skipBackward();
handleMouseMove();
},
onSkipContinueRight: () => {
logic.skipForward();
handleMouseMove();
},
isSkipModeActive: data.showSkipForwardIndicator || data.showSkipBackwardIndicator,
});
return (
<div
ref={containerRef}
@@ -176,12 +200,10 @@ export function DesktopVideoPlayer({
onError={handleVideoError}
onWaiting={() => setIsLoading(true)}
onCanPlay={() => setIsLoading(false)}
onClick={(e) => {
// Prevent native behavior on iOS
// e.preventDefault();
// React synthetic event doesn't always stop native video toggle on iOS, but good practice
onClick={!isMobile ? (e) => {
togglePlay();
}}
} : undefined}
onTouchStart={isMobile ? handleTap : undefined}
{...({ 'webkit-playsinline': 'true' } as any)} // Legacy iOS support
/>
+4 -4
View File
@@ -142,14 +142,14 @@ export function DesktopOverlay({
<div
className={`absolute left-0 top-0 bottom-0 flex items-center justify-center p-4 md:p-8 transition-opacity duration-300 z-10 ${showNavButtons ? 'opacity-100' : 'opacity-0'
}`}
style={{ pointerEvents: 'none' }}
style={{ pointerEvents: showNavButtons ? 'auto' : 'none' }}
>
<button
onClick={(e) => {
e.stopPropagation();
onSkipBackward();
}}
className="group flex items-center justify-center w-10 h-10 md:w-16 md:h-16 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm transition-all duration-300 hover:scale-110 active:scale-95 pointer-events-auto cursor-pointer"
className="group flex items-center justify-center w-10 h-10 md:w-16 md:h-16 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm transition-all duration-300 hover:scale-110 active:scale-95 cursor-pointer"
aria-label="后退 10 秒"
>
<Icons.SkipBack className="w-5 h-5 md:w-8 md:h-8 text-white/80 group-hover:text-white" />
@@ -160,14 +160,14 @@ export function DesktopOverlay({
<div
className={`absolute right-0 top-0 bottom-0 flex items-center justify-center p-4 md:p-8 transition-opacity duration-300 z-10 ${showNavButtons ? 'opacity-100' : 'opacity-0'
}`}
style={{ pointerEvents: 'none' }}
style={{ pointerEvents: showNavButtons ? 'auto' : 'none' }}
>
<button
onClick={(e) => {
e.stopPropagation();
onSkipForward();
}}
className="group flex items-center justify-center w-10 h-10 md:w-16 md:h-16 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm transition-all duration-300 hover:scale-110 active:scale-95 pointer-events-auto cursor-pointer"
className="group flex items-center justify-center w-10 h-10 md:w-16 md:h-16 rounded-full bg-black/40 hover:bg-black/60 backdrop-blur-sm transition-all duration-300 hover:scale-110 active:scale-95 cursor-pointer"
aria-label="前进 10 秒"
>
<Icons.FastForward className="w-5 h-5 md:w-8 md:h-8 text-white/80 group-hover:text-white" />
@@ -107,11 +107,33 @@ export function useControlsVisibility({
return () => clearSpeedMenuTimeout();
}, [showSpeedMenu, startSpeedMenuTimeout, clearSpeedMenuTimeout]);
const handleTouchToggleControls = useCallback(() => {
if (showControls && isPlaying) {
// Controls visible + playing = hide immediately
if (controlsTimeoutRef.current) {
clearTimeout(controlsTimeoutRef.current);
}
setShowControls(false);
} else if (!showControls) {
// Controls hidden = show + start 3s auto-hide timer
setShowControls(true);
if (isPlaying) {
if (controlsTimeoutRef.current) {
clearTimeout(controlsTimeoutRef.current);
}
controlsTimeoutRef.current = setTimeout(() => {
setShowControls(false);
}, 3000);
}
}
}, [showControls, isPlaying, setShowControls, controlsTimeoutRef]);
const visibilityActions = useMemo(() => ({
handleMouseMove,
handleTouchToggleControls,
startSpeedMenuTimeout,
clearSpeedMenuTimeout
}), [handleMouseMove, startSpeedMenuTimeout, clearSpeedMenuTimeout]);
}), [handleMouseMove, handleTouchToggleControls, startSpeedMenuTimeout, clearSpeedMenuTimeout]);
return visibilityActions;
}
@@ -154,6 +154,7 @@ export function useDesktopPlayerLogic({
return useMemo(() => ({
handleMouseMove: controlsVisibility.handleMouseMove,
handleTouchToggleControls: controlsVisibility.handleTouchToggleControls,
togglePlay: playbackControls.togglePlay,
handlePlay: playbackControls.handlePlay,
handlePause: playbackControls.handlePause,
+2254 -5
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kvideo",
"version": "4.1.5",
"version": "4.1.6",
"private": true,
"scripts": {
"dev": "next dev",
@@ -30,6 +30,7 @@
"eslint": "^10",
"eslint-config-next": "16.1.6",
"postcss": "^8.5.6",
"postcss-preset-env": "^11.1.3",
"tailwindcss": "^4",
"typescript": "^5"
}
+7
View File
@@ -1,6 +1,13 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
"postcss-preset-env": {
features: {
"cascade-layers": true,
"color-mix": true,
},
browsers: "Safari >= 15, iOS >= 15",
},
},
};