鼠标连续点击事件--toggle()学习例子

本文介绍了一个使用jQuery实现的鼠标连续点击事件示例。通过toggle方法,每次点击div元素时,其内部显示的标题级别会发生变化,从h1至h6循环显示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>鼠标连续点击事件--toggle()</title>
<script type="text/javascript" src="js/jquery-1.8.3.min.js"></script>
<script type="text/javascript">
$(function() {
$("div")
.css("width", "100px")
.css("height", "100px")
.css("background-color", "cornflowerblue")
.toggle( //可以绑定多个点击事件,多个事件之间循环调用
function() {
$("div").html("<h1>你好</h1>");//第一次点击时触发
},
function() {
$("div").html("<h2>你好</h2>");//第2次点击时触发
},
function() {
$("div").html("<h3>你好</h3>");//第3次点击时触发
},
function() {
$("div").html("<h4>你好</h4>");//第4次点击时触发
},
function() {
$("div").html("<h5>你好</h5>");//第5次点击时触发
},
function() {
$("div").html("<h6>你好</h6>");//第6次点击时触发
}
);
});
</script>
</head>
<body>
<div></div>
</body>

转载于:https://www.cnblogs.com/tian-xin/p/8204015.html

import React, { useEffect, useRef, useState, useCallback, forwardRef, useImperativeHandle } from 'react'; import videojs from 'video.js'; import 'video.js/dist/video-js.css'; import 'videojs-playlist'; import './PlaylistButton'; import './index.scss'; // 扩展videojs类型 declare global { interface Window { videojs: any; } } // 循环播放模式枚举 export enum LoopMode { NONE = 'none', // 不循环 SINGLE = 'single', // 单视频循环 SEQUENCE = 'sequence', // 顺序循环整个播放列表 SHUFFLE = 'shuffle', // 随机循环整个播放列表 } // 视频项接口 export interface VideoItem { sources: Array<{ src: string; type: string; }>; poster?: string; title?: string; summary?: string; } // 组件属性接口 interface MifcVideoProps { videoList: VideoItem[]; autoplay?: boolean; loop?: boolean | LoopMode; // 支持布尔值或循环模式 width?: string | number; height?: string | number; onVideoChange?: (index: number, isAutoPlay?: boolean) => void; onPlay?: () => void; onPause?: () => void; onEnded?: () => void; onRef?: (ref: MifcVideoRef) => void; children?: React.ReactNode; } // 组件引用接口 export interface MifcVideoRef { playByUrl: (videoUrl: string) => void; } const MifcVideo = forwardRef<MifcVideoRef, MifcVideoProps>( ( { videoList, autoplay = false, loop = LoopMode.SEQUENCE, // 默认顺序循环 width = '100%', height = '100%', onVideoChange, onPlay, onPause, onEnded, onRef, children, }, ref, ) => { const videoRef = useRef<HTMLVideoElement>(null); const playlistRef = useRef<HTMLDivElement>(null); const playerRef = useRef<any>(null); const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null); const originalPlaylistRef = useRef<VideoItem[]>([]); const [isPlaylistVisible, setIsPlaylistVisible] = useState(false); const isPlaylistVisibleRef = useRef(isPlaylistVisible); const [currentVideoIndex, setCurrentVideoIndex] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const [autoplayBlocked, setAutoplayBlocked] = useState(false); const [isPlaylistScrollable, setIsPlaylistScrollable] = useState(false); const [isMouseNearLeftEdge, setIsMouseNearLeftEdge] = useState(false); const [showControls, setShowControls] = useState(false); const controlsTimeoutRef = useRef<NodeJS.Timeout | null>(null); const [isMobile, setIsMobile] = useState(false); const isAutoPlayingRef = useRef(false); const touchStartRef = useRef({ x: 0, y: 0 }); const lastCallTimeRef = useRef(0); const [isFullscreen, setIsFullscreen] = useState(false); // 新增:跟踪全屏状态 // 暴露方法给父组件 useImperativeHandle(ref, () => ({ playByUrl: (videoUrl: string) => { if (playerRef.current) { // 设置自动播放标记 isAutoPlayingRef.current = true; // 创建临时视频项 const tempVideo: VideoItem = { sources: [{ src: videoUrl, type: 'video/mp4' }], title: 'AI推荐视频', summary: '正在播放AI推荐的视频', }; // 将临时视频添加到播放列表并播放 const currentPlaylist = (playerRef.current as any).playlist(); const videoIndex = currentPlaylist.findIndex((video: VideoItem) => video.sources.some((source) => source.src === videoUrl), ); if (videoIndex >= 0) { // 如果视频已在播放列表中,直接切换到该视频 switchVideo(videoIndex); // onVideoChange 会通过 playlistitem 事件自动触发 } else { // 如果视频不在播放列表中,添加到列表并播放 const newPlaylist = [...currentPlaylist, tempVideo]; (playerRef.current as any).playlist(newPlaylist, newPlaylist.length - 1); // onVideoChange 会通过 playlistitem 事件自动触发 } // 尝试播放 const playPromise = playerRef.current.play(); if (playPromise !== undefined) { playPromise.catch((error: any) => { console.warn('播放视频失败:', error); }); } } }, })); // 检测是否为移动端 const checkMobile = () => { const userAgent = navigator.userAgent.toLowerCase(); const isMobileDevice = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent); const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; const isMobileResult = isMobileDevice || isTouchDevice; setIsMobile(isMobileResult); return isMobileResult; }; useEffect(() => { checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); // 显示控制器 const showControlsWithTimeout = useCallback(() => { setShowControls(true); // 清除之前的定时器 if (controlsTimeoutRef.current) { clearTimeout(controlsTimeoutRef.current); } // 3秒后自动隐藏控制器 controlsTimeoutRef.current = setTimeout(() => { setShowControls(false); }, 3000); }, []); // 隐藏控制器 const hideControls = useCallback(() => { setShowControls(false); if (controlsTimeoutRef.current) { clearTimeout(controlsTimeoutRef.current); controlsTimeoutRef.current = null; } }, []); // 自定义setter函数,同时更新状态和ref const setIsPlaylistVisibleWithRef = useCallback((value: boolean) => { setIsPlaylistVisible(value); isPlaylistVisibleRef.current = value; // 立即更新ref }, []); // 显示播放列表 //showPlaylist`函数不能添加isMobile为依赖项,因为当前函数被用作useEffect的依赖项,而该useEffect用于初始化播放器。当`isMobile`状态改变时,`showPlaylist`函数会重新创建,导致`useEffect`依赖项变化,从而重新初始化播放器。在移动端,可能由于重新初始化播放器时自动播放被阻止,导致黑屏 const showPlaylist = useCallback(() => { // 防止快速连续调用 const now = Date.now(); if (now - lastCallTimeRef.current < 300) return; lastCallTimeRef.current = now; const currentVisible = isPlaylistVisibleRef.current; const mobileIsNo = checkMobile(); if (currentVisible) { setIsPlaylistVisibleWithRef(false); } else { setIsPlaylistVisibleWithRef(true); } // 清除之前的定时器 if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); } if (!mobileIsNo) { // 1.5秒后自动隐藏 hideTimeoutRef.current = setTimeout(() => { setIsPlaylistVisibleWithRef(false); }, 1500); } }, []); // 隐藏播放列表 const hidePlaylist = useCallback((e?: React.MouseEvent | React.TouchEvent) => { // 阻止事件冒泡到父元素,避免触发视频播放/暂停 if (e) { e.stopPropagation(); e.preventDefault(); // 阻止默认行为 } setIsPlaylistVisibleWithRef(false); if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); hideTimeoutRef.current = null; } }, []); // 切换视频 const switchVideo = useCallback((index: number) => { if (playerRef.current && playerRef.current.playlist) { playerRef.current.playlist.currentItem(index); // setCurrentVideoIndex 和 onVideoChange 会通过 playlistitem 事件自动触发 // 不需要在这里重复调用 // 重置隐藏定时器 if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); } hideTimeoutRef.current = setTimeout(() => { setIsPlaylistVisibleWithRef(false); }, 1000); } }, []); // 设置循环播放模式 const setLoopMode = useCallback((mode: LoopMode) => { if (!playerRef.current) return; switch (mode) { case LoopMode.NONE: // 不循环播放 (playerRef.current as any).playlist.repeat(false); (playerRef.current as any).playlist.autoadvance(0); break; case LoopMode.SINGLE: // 单视频循环 (playerRef.current as any).playlist.repeat(false); (playerRef.current as any).playlist.autoadvance(0); // 设置videojs的loop属性 playerRef.current.loop(true); break; case LoopMode.SEQUENCE: // 顺序循环整个播放列表 (playerRef.current as any).playlist.repeat(true); (playerRef.current as any).playlist.autoadvance(0); playerRef.current.loop(false); break; case LoopMode.SHUFFLE: // 随机循环整个播放列表 (playerRef.current as any).playlist.repeat(false); (playerRef.current as any).playlist.autoadvance(0); playerRef.current.loop(false); // 启用随机播放 (playerRef.current as any).playlist.shuffle({ rest: false }); break; } }, []); // 初始化播放器 useEffect(() => { if (!videoRef.current || !videoList.length) return; // 保存原始播放列表 originalPlaylistRef.current = [...videoList]; // 初始化videojs播放器 const player = videojs(videoRef.current, { controls: true, autoplay, loop: false, // 初始不设置循环,由循环模式控制 fluid: true, responsive: true, playbackRates: [0.5, 1, 1.25, 1.5, 2], playsinline: true, // 防止移动端自动全屏 controlBar: { children: [ 'playToggle', { name: 'volumePanel', inline: false, }, 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'playbackRateMenuButton', 'playlistButton', 'fullscreenToggle', ], }, }); playerRef.current = player; // 添加播放列表组件 playerRef.current.addChild('Component', { el: playlistRef.current, }); // 等待播放器准备就绪后初始化播放列表 player.ready(() => { // 使用正确的播放列表API初始化 (player as any).playlist(videoList, 0); // 设置循环播放模式 const loopMode = typeof loop === 'boolean' ? (loop ? LoopMode.SEQUENCE : LoopMode.NONE) : loop; setLoopMode(loopMode); // 设置自动播放 if (autoplay) { // 确保播放列表已加载 setTimeout(() => { try { (player as any).playlist.first(); const playPromise = player.play(); // 处理自动播放可能被浏览器阻止的情况 if (playPromise !== undefined) { playPromise.catch((error: any) => { console.warn('自动播放被阻止:', error); setAutoplayBlocked(true); }); } } catch (error) { console.warn('自动播放失败:', error); setAutoplayBlocked(true); } }, 100); } }); // 设置事件监听器 player.on('play', () => { setIsPlaying(true); // showPlaylist(); onPlay?.(); }); player.on('pause', () => { setIsPlaying(false); onPause?.(); }); player.on('ended', () => { setIsPlaying(false); onEnded?.(); isAutoPlayingRef.current = false; }); player.on('timeupdate', () => { const currentTime = player.currentTime(); const duration = player.duration(); // 在视频即将结束时显示播放列表(剩余3秒) if (duration && currentTime && currentTime > 0 && duration - currentTime <= 3) { // 注释掉这里,因为会触发多次,直接播放完下一个播放就行不需要展开播放列表 // showPlaylist(); } }); // 播放列表项变化事件 player.on('playlistitem', (event: any, data: any) => { const currentIndex = (player as any).playlist.currentItem(); setCurrentVideoIndex(currentIndex); onVideoChange?.(currentIndex, isAutoPlayingRef.current); // 根据自动播放标记传递参数 // // 重置自动播放标记 // isAutoPlayingRef.current = false; }); // 播放列表按钮点击事件 player.on('playlistButtonClick', (e?: React.MouseEvent | React.TouchEvent) => { // 彻底阻止事件传播 if (e) { e.stopPropagation(); e.preventDefault(); } showPlaylist(); }); // 监听全屏变化事件 player.on('fullscreenchange', () => { setIsFullscreen(player?.isFullscreen() || false); }); // 清理函数 return () => { if (player) { player.dispose(); } if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); } if (playerRef.current) { playerRef.current.dispose(); playerRef.current = null; } }; }, [videoList, autoplay, loop, showPlaylist, onVideoChange, onPlay, onPause, onEnded, setLoopMode]); // 播放列表点击事件处理 const handlePlaylistItemClick = useCallback( (index: number, e?: React.MouseEvent | React.TouchEvent) => { if (e) { e.stopPropagation(); // 阻止事件冒泡 e.preventDefault(); // 阻止默认行为 } switchVideo(index); isAutoPlayingRef.current = false; }, [switchVideo], ); // 播放列表项的触摸事件处理 const handlePlaylistItemTouchStart = useCallback((e: React.TouchEvent, index: number) => { // 记录触摸起始位置 const touch = e.touches[0]; touchStartRef.current = { x: touch.clientX, y: touch.clientY, }; e.stopPropagation(); }, []); // 播放列表项的触结束事件处理 const handlePlaylistItemTouchEnd = useCallback( (e: React.TouchEvent, index: number) => { // 计算触摸移动距离 const touch = e.changedTouches[0]; const deltaX = Math.abs(touch.clientX - touchStartRef.current.x); const deltaY = Math.abs(touch.clientY - touchStartRef.current.y); // 只有在移动距离小于阈值时才触发点击事件 // 这样可以防止滑动时误触发点击 if (deltaX < 10 && deltaY < 10) { handlePlaylistItemClick(index, e); } e.stopPropagation(); }, [handlePlaylistItemClick], ); // 播放列表浮层的点击处理 const handlePlaylistOverlayClick = useCallback((e: React.MouseEvent | React.TouchEvent) => { e.stopPropagation(); }, []); // 播放列表鼠标进入事件 const handlePlaylistMouseEnter = useCallback(() => { if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); hideTimeoutRef.current = null; } // 在播放列表区域时保持控制器显示 showControlsWithTimeout(); }, [showControlsWithTimeout]); // 播放列表鼠标离开事件 const handlePlaylistMouseLeave = useCallback(() => { // 只有在鼠标不在左侧边缘时才启动自动隐藏定时器 if (!isMouseNearLeftEdge) { hideTimeoutRef.current = setTimeout(() => { setIsPlaylistVisibleWithRef(false); }, 1000); } }, [isMouseNearLeftEdge]); // 处理用户交互开始播放 const handleUserInteraction = useCallback(() => { if (autoplayBlocked && playerRef.current) { setAutoplayBlocked(false); const playPromise = playerRef.current.play(); if (playPromise !== undefined) { playPromise.catch((error: any) => { console.warn('播放失败:', error); }); } } }, [autoplayBlocked]); // 检测播放列表内容是否可滚动 const checkPlaylistScrollable = useCallback(() => { const playlistContent = document.querySelector('.playlist-content'); if (playlistContent) { const isScrollable = playlistContent.scrollHeight > playlistContent.clientHeight; setIsPlaylistScrollable(isScrollable); } }, []); // 处理播放器点击事件 const handlePlayerClick = useCallback( (event?: React.MouseEvent | React.TouchEvent) => { // 如果是移动端且是触摸事件,不处理点击(由触摸事件处理) if (isMobile && event && 'touches' in event) { return; } // 检查触摸目标是否是播放列表按钮或相关元素 const target = event?.target as Element; const isPlaylistButton = target?.closest('.vjs-playlist-button'); if (playerRef.current && !isPlaylistButton) { if (isPlaying) { playerRef.current.pause(); } else { playerRef.current.play(); } } }, [isPlaying, isMobile], ); // 处理触摸开始事件(移动端) const handleTouchStart = useCallback( (event: React.TouchEvent) => { // 显示控制器 showControlsWithTimeout(); // 检查触摸目标是否是播放列表按钮或相关元素 const target = event.target as Element; const isPlaylistButton = target.closest('.vjs-playlist-button') || target.closest('.playlist-toggle-btn') || target.closest('.playlist-overlay'); // 在移动端触摸播放视频时关闭侧边栏,但排除播放列表相关元素 if (isMobile && !isPlaylistButton) { hidePlaylist(); } }, [showControlsWithTimeout, isMobile], ); // 处理触摸结束事件(移动端) const handleTouchEnd = useCallback( (event: React.TouchEvent) => { // 检查是否在播放器区域点击(非控制栏区域) const touch = event.changedTouches[0]; const container = event.currentTarget; const rect = container.getBoundingClientRect(); const touchY = touch.clientY - rect.top; // 检查是否点击在控制栏区域(底部20%区域) const isInControlBarArea = touchY > rect.height * 0.8; // 只有在非控制栏区域点击时才触发播放/暂停 if (!isInControlBarArea) { handlePlayerClick(); } }, [handlePlayerClick], ); // 处理触摸移动事件(移动端) const handleTouchMove = useCallback((event: React.TouchEvent) => { // 移动端不需要处理触摸移动事件 }, []); // 处理鼠标移动到左侧边缘(仅桌面端) const handleMouseMove = useCallback( (event: React.MouseEvent) => { const container = event.currentTarget; const rect = container.getBoundingClientRect(); const mouseX = event.clientX - rect.left; const edgeThreshold = 50; // 左侧边缘检测区域宽度 // 显示控制器 showControlsWithTimeout(); // 仅在桌面端处理左侧边缘检测 if (!isMobile) { if (mouseX <= edgeThreshold) { setIsMouseNearLeftEdge(true); // 只有在播放列表未显示且不在播放列表区域内时才显示 if (!isPlaylistVisible) { showPlaylist(); } } else { setIsMouseNearLeftEdge(false); } } }, [isPlaylistVisible, showPlaylist, showControlsWithTimeout, isMobile], ); // 处理鼠标离开容器(仅桌面端) const handleMouseLeave = useCallback( (event: React.MouseEvent) => { // 仅在桌面端处理鼠标离开事件 if (!isMobile) { setIsMouseNearLeftEdge(false); hideControls(); } }, [hideControls, isMobile], ); // 检测播放列表可滚动状态 useEffect(() => { if (isPlaylistVisible) { // 延迟检测,确保DOM已更新 setTimeout(checkPlaylistScrollable, 100); // 监听窗口大小变化 window.addEventListener('resize', checkPlaylistScrollable); return () => window.removeEventListener('resize', checkPlaylistScrollable); } }, [isPlaylistVisible, videoList, checkPlaylistScrollable]); // 控制videojs控制器的显示/隐藏 useEffect(() => { if (playerRef.current) { const controlBar = playerRef.current.controlBar; if (controlBar) { if (showControls) { controlBar.el().classList.add('vjs-control-bar-visible'); } else { controlBar.el().classList.remove('vjs-control-bar-visible'); } } } }, [showControls]); // 组件卸载时清理定时器 useEffect(() => { return () => { if (controlsTimeoutRef.current) { clearTimeout(controlsTimeoutRef.current); } }; }, []); return ( <div className={`mifc-video-container ${isFullscreen ? 'fullscreen-mode' : ''}`} style={{ width, height }} onMouseMove={handleMouseMove} onMouseLeave={handleMouseLeave} onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd} onTouchMove={handleTouchMove} onContextMenu={(e) => e.preventDefault()}> {/* 视频播放器 */} <div className={`video-player-wrapper ${!isMobile && isMouseNearLeftEdge ? 'edge-hover' : ''}`} onClick={handlePlayerClick} style={{ touchAction: 'manipulation' }}> <video ref={videoRef} className="video-js vjs-default-skin" data-setup="{}" muted={autoplay} playsInline onContextMenu={(e) => e.preventDefault()} style={{ pointerEvents: 'auto' }} /> {/* 自动播放被阻止时的提示 */} {autoplayBlocked && ( <div className="autoplay-blocked-overlay" onClick={handleUserInteraction}> <div className="autoplay-blocked-content"> <div className="autoplay-icon">▶️</div> <div className="autoplay-text"> <h3>点击开始播放</h3> <p>浏览器阻止了自动播放,请点击此处开始播放</p> </div> </div> </div> )} {/* 暂停时显示的播放按钮 {!isPlaying && !autoplayBlocked && ( <div className="play-button-overlay" onClick={handlePlayerClick}> <div className="play-button"> <svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="40" cy="40" r="40" fill="rgba(0, 0, 0, 0.7)"/> <path d="M32 24L56 40L32 56V24Z" fill="white"/> </svg> </div> </div> )} */} </div> {/* 播放列表浮层 */} <div ref={playlistRef} className={`playlist-overlay ${isPlaylistVisible && !isFullscreen ? 'visible' : ''} ${isFullscreen ? 'fullscreen' : ''} ${isPlaylistVisible && isFullscreen ? 'fullscreen-visible' : ''}`} onMouseEnter={handlePlaylistMouseEnter} onMouseLeave={handlePlaylistMouseLeave} onClick={handlePlaylistOverlayClick} // 阻止播放列表浮层的点击事件冒泡 onTouchStart={handlePlaylistOverlayClick} // 阻止触摸事件冒泡 onTouchEnd={handlePlaylistOverlayClick} // 添加触摸结束处理 > <div className={`playlist-content ${isPlaylistScrollable ? 'scrollable' : ''}`} onClick={handlePlaylistOverlayClick} // 内容区域也阻止点击 onTouchStart={handlePlaylistOverlayClick} // 内容区域也阻止触摸 > {videoList.map((video, index) => ( <div key={index} className={`playlist-item ${index === currentVideoIndex ? 'active' : ''}`} onClick={(e) => handlePlaylistItemClick(index, e)} onTouchStart={(e) => handlePlaylistItemTouchStart(e, index)} // 添加触摸开始处理 onTouchEnd={(e) => handlePlaylistItemTouchEnd(e, index)} // 添加触摸结束处理 > <div className="playlist-item-content"> <div className="playlist-item-title">{video.title || `视频 ${index + 1}`}</div> <div className="playlist-item-summary">{video.summary || '点击播放视频'}</div> </div> </div> ))} </div> {/* 右侧收起按钮 */} <div className="playlist-toggle-btn" onClick={hidePlaylist} onTouchEnd={hidePlaylist} // 添加触摸事件处理 ></div> </div> {children} </div> ); }, ); export default MifcVideo; 这里的children 为外部传入的组件我要怎么在vido中注册
最新发布
08-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值