mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
feat: Implement mobile double-tap gestures for video player controls and apply CSS optimizations.
This commit is contained in:
@@ -23,13 +23,17 @@
|
||||
.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 */
|
||||
.video-card-image {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@supports (content-visibility: auto) {
|
||||
img {
|
||||
content-visibility: auto;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Generated
+2254
-5
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -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"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
"postcss-preset-env": {
|
||||
features: {
|
||||
"cascade-layers": true,
|
||||
"color-mix": true,
|
||||
},
|
||||
browsers: "Safari >= 15, iOS >= 15",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user