页面刷新后嵌入式视频批量播放/暂停按钮失效及显示异常问题
问题描述
我开发了一个可展示多个YouTube视频的网站,想通过JavaScript实现批量播放、暂停所有视频的按钮功能。网站能正常运行,但存在两个问题:
- 页面刷新后,点击「显示视频」按钮,前两次只会生成空div,第三次点击才会加载视频
- 播放/暂停按钮只有在点击显示按钮后才会生效
原代码
$(document).ready(function() { let input = document.getElementById("video-url"); let displayButton = document.getElementById("display-video"); let containerDiv = document.getElementById("video-container"); let stateList = []; let player; let players = []; // Load the YT library var tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); displayButton.addEventListener("click", function() { let videoID = input.value.split("watch?v=")[1]; let innerDiv = document.createElement("div"); innerDiv.className = "inner"; innerDiv.id = "video-" + players.length; let outerDiv = document.createElement("div"); outerDiv.className = "window draggable resizable"; outerDiv.style.transform = "translate3d(100px, 100px, 0)"; outerDiv.appendChild(innerDiv); init(); containerDiv.appendChild(outerDiv); player = new YT.Player("video-" + players.length, { height: "100%", width: "100%", videoId: videoID, events: { 'onReady': onPlayerReady } }); players.push(player); init(); saveState(); }); function onPlayerReady(event) { let playButton = document.getElementById("play-button"); playButton.addEventListener("click", function() { event.target.playVideo(); }); let pauseButton = document.getElementById("pause-button"); pauseButton.addEventListener("click", function() { event.target.pauseVideo(); }); let stopButton = document.getElementById("stop-button"); stopButton.addEventListener("click", function() { event.target.stopVideo(); }); } function init() { // the ui-resizable-handles are added here $('.resizable').resizable(); // makes GSAP Draggable avoid clicks on the resize handles $('.ui-resizable-handle').attr('data-clickable', true); // make the element draggable Draggable.create('.draggable', { onPress: function () { $(this.target).addClass('ui-resizable-resizing'); }, onRelease: function() { $(this.target).removeClass('ui-resizable-resizing'); saveState(); } }); $('.resizable').dblclick(function () { var win = $(this); toggleSize(win); saveState(); }); } function createHandleBar() { // create handlebar let handleBar = document.createElement('div'); handleBar.className = 'handleBar'; $(handleBar).css({ position: 'absolute', top: '0px', width: '100%', height: '43px', padding: '4px', boxSizing: 'border-box' }); saveState(); return handleBar; } function saveState() { let state = { id: stateList.length, state: containerDiv.innerHTML }; stateList.push(state); window.localStorage.setItem('videoState', JSON.stringify(stateList)); window.history.pushState({}, '', '#' + btoa(JSON.stringify(stateList))); } if (window.location.hash) { let stateListFromStorage = JSON.parse(atob(window.location.hash.substring(1))); let stateToRestore = stateListFromStorage[stateListFromStorage.length - 1]; containerDiv.innerHTML = stateToRestore.state; init(); } });
问题原因
YouTube API加载时机错误:
在$(document).ready里直接插入API脚本,但脚本加载是异步的,刷新后第一次点击显示按钮时,YT对象可能还未初始化,导致创建播放器失败,视频无法渲染。状态恢复逻辑缺失:
页面刷新后从hash恢复DOM结构,但这些恢复的视频div没有重新创建播放器,players数组依然为空,新创建的视频ID用players.length作为后缀,会和恢复的div ID重复,导致播放器绑定失败。批量按钮事件绑定时机错误:
播放/暂停按钮的事件绑定写在onPlayerReady里,只有当第一个播放器初始化完成后,按钮才会有事件响应,且每次创建新播放器都会重复绑定事件,逻辑只能控制单个播放器,无法实现批量操作。
修复方案
核心修改点
1. 遵守YT API加载规范
使用API规定的全局回调onYouTubeIframeAPIReady,确保API加载完成后再执行播放器创建、事件绑定等逻辑。
2. 提前绑定批量控制按钮事件
直接在API加载完成后绑定全局播放/暂停/停止按钮的事件,遍历players数组实现批量操作,无需等待单个播放器ready。
3. 优化状态存储与恢复
修改saveState存储每个视频的ID、位置信息,刷新恢复时重新创建播放器并加入players数组,避免ID冲突。
4. 健壮的视频ID提取逻辑
支持标准链接和短链接(youtu.be),同时过滤链接中的额外参数。
修复后的完整代码
// 全局变量声明 let input, displayButton, containerDiv, stateList, players = []; $(document).ready(function() { input = document.getElementById("video-url"); displayButton = document.getElementById("display-video"); containerDiv = document.getElementById("video-container"); stateList = []; // 加载YouTube IFrame API const tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; const firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); // 页面刷新后恢复状态 if (window.location.hash) { try { const stateListFromStorage = JSON.parse(atob(window.location.hash.substring(1))); const latestState = stateListFromStorage[stateListFromStorage.length - 1]; containerDiv.innerHTML = latestState.containerHTML; stateList = stateListFromStorage; // 重新创建播放器 latestState.videos.forEach((videoInfo, index) => { const player = new YT.Player(videoInfo.id, { height: "100%", width: "100%", videoId: videoInfo.videoId, events: { 'onReady': onPlayerReady } }); players.push(player); // 恢复窗口位置 const windowEl = containerDiv.querySelectorAll('.window')[index]; windowEl.style.transform = videoInfo.transform; }); init(); } catch (e) { console.error('状态恢复失败:', e); } } }); // YouTube API加载完成后自动触发的全局回调 function onYouTubeIframeAPIReady() { // 绑定显示视频按钮事件 displayButton.addEventListener("click", function() { const videoURL = input.value.trim(); if (!videoURL) { alert('请输入有效的YouTube视频链接'); return; } // 提取视频ID(兼容标准链接和短链接) let videoID; if (videoURL.includes("watch?v=")) { videoID = videoURL.split("watch?v=")[1].split('&')[0]; } else if (videoURL.includes("youtu.be/")) { videoID = videoURL.split("youtu.be/")[1].split('?')[0]; } else { alert('请输入有效的YouTube视频链接'); return; } // 创建视频容器DOM const innerDiv = document.createElement("div"); innerDiv.className = "inner"; innerDiv.id = `video-${players.length}`; const outerDiv = document.createElement("div"); outerDiv.className = "window draggable resizable"; outerDiv.style.transform = "translate3d(100px, 100px, 0)"; outerDiv.appendChild(innerDiv); containerDiv.appendChild(outerDiv); // 创建YouTube播放器 const player = new YT.Player(`video-${players.length}`, { height: "100%", width: "100%", videoId: videoID, events: { 'onReady': onPlayerReady } }); players.push(player); init(); saveState(); }); // 绑定批量控制按钮事件 const playButton = document.getElementById("play-button"); playButton.addEventListener("click", () => { players.forEach(player => { if (player?.playVideo) player.playVideo(); }); }); const pauseButton = document.getElementById("pause-button"); pauseButton.addEventListener("click", () => { players.forEach(player => { if (player?.pauseVideo) player.pauseVideo(); }); }); const stopButton = document.getElementById("stop-button"); stopButton.addEventListener("click", () => { players.forEach(player => { if (player?.stopVideo) player.stopVideo(); }); }); } // 播放器就绪回调(可用于单个播放器的状态监听) function onPlayerReady(event) { // 这里可以添加单个播放器的逻辑,比如自动播放等 } // 初始化拖拽、缩放功能 function init() { $('.resizable').resizable(); $('.ui-resizable-handle').attr('data-clickable', true); Draggable.create('.draggable', { onPress: function () { $(this.target).addClass('ui-resizable-resizing'); }, onRelease: function() { $(this.target).removeClass('ui-resizable-resizing'); saveState(); } }); $('.resizable').dblclick(function () { const win = $(this); toggleSize(win); saveState(); }); } // 创建标题栏(原逻辑保留) function createHandleBar() { const handleBar = document.createElement('div'); handleBar.className = 'handleBar'; $(handleBar).css({ position: 'absolute', top: '0px', width: '100%', height: '43px', padding: '4px', boxSizing: 'border-box' }); saveState(); return handleBar; } // 保存状态(新增视频信息存储) function saveState() { const videosInfo = []; containerDiv.querySelectorAll('.window').forEach((win, index) => { const innerDiv = win.querySelector('.inner'); videosInfo.push({ videoId: players[index].getVideoData().video_id, id: innerDiv.id, transform: win.style.transform }); }); const state = { id: stateList.length, videos: videosInfo, containerHTML: containerDiv.innerHTML }; stateList.push(state); window.localStorage.setItem('videoState', JSON.stringify(stateList)); window.history.pushState({}, '', `#${btoa(JSON.stringify(stateList))}`); }
内容的提问来源于stack exchange,提问作者Ryan Vaughan
相关产品推荐
相关产品推荐

