Chrome扩展:如何监听YouTube滚动加载事件以重新标记视频
Hey there! Let's sort out that infinite scroll issue for your YouTube tagging extension—this is a super common problem with dynamic content sites like YouTube, so I’ve got a couple of proven solutions for you.
The Best Approach: Use MutationObserver
YouTube loads new videos by dynamically inserting elements into the DOM, so the most reliable way to catch these new videos is to listen for changes to the page's DOM using MutationObserver. This is the standard, high-performance method for tracking dynamic content updates.
Here's how to implement it:
Step 1: Refine Your Tagging Function
First, update your existing tagging function to handle both initial and new videos efficiently, adding a marker to avoid redundant API calls:
function markVideos(videoElements) { videoElements.forEach(video => { // Skip if video was already processed if (video.dataset.tagged) return; // Extract video ID (adjust selector if YouTube's DOM shifts) const videoLink = video.querySelector('#video-title-link'); const videoId = videoLink.href.split('v=')[1].split('&')[0]; // Call YouTube Data API to fetch category (critical: move this to background script for API key security!) fetch(`https://www.googleapis.com/youtube/v3/videos?id=${videoId}&part=snippet&key=YOUR_API_KEY`) .then(res => res.json()) .then(data => { const categoryId = data.items[0].snippet.categoryId; // Map category ID to your color scheme if (categoryId === '27') { // Education category ID video.style.backgroundColor = '#e8f0fe'; } // Mark as processed to prevent re-runs video.dataset.tagged = 'true'; }); }); }
Step 2: Set Up the Mutation Observer
This will watch the YouTube video container for new elements being added:
// Run initial tagging when DOM is ready document.addEventListener('DOMContentLoaded', () => { const initialVideos = document.querySelectorAll('ytd-video-renderer'); markVideos(initialVideos); // Target the container where YouTube inserts new videos (adjust if needed) const videoContainer = document.querySelector('ytd-item-section-renderer #contents') || document.querySelector('#contents'); if (!videoContainer) return; // Create observer instance const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { // Filter only new video renderer elements const newVideos = Array.from(mutation.addedNodes).filter(node => node.tagName === 'YTD-VIDEO-RENDERER' && node.isConnected ); // Tag new videos if any were added if (newVideos.length > 0) { markVideos(newVideos); } }); }); // Start observing the container for child node changes observer.observe(videoContainer, { childList: true, subtree: true // Needed in case videos are nested in child elements }); });
Critical Tips to Avoid Issues
- YouTube DOM Stability: YouTube occasionally updates its DOM structure. If selectors break, inspect the page to find the latest container or video element.
ytd-video-rendereris fairly stable, but parent containers might shift. - API Key Security: Never hardcode your YouTube Data API key in a content script—anyone can inspect it! Move the API call to your extension's background script, then use
chrome.runtime.sendMessageto send the video ID to the background, andchrome.runtime.onMessage.addListenerto receive category data back. - Manifest Configuration: Ensure your content script runs at the right time by setting
run_at: "document_end"in yourmanifest.json:
"content_scripts": [ { "matches": ["https://www.youtube.com/*"], "js": ["content.js"], "run_at": "document_end" } ]
Fallback: Scroll Event with Debouncing (Less Recommended)
If MutationObserver doesn’t work for edge cases, use a scroll event with debouncing as a backup. It’s less efficient but functional:
let debounceTimer; window.addEventListener('scroll', () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { // Find all unprocessed videos const unmarkedVideos = document.querySelectorAll('ytd-video-renderer:not([data-tagged])'); if (unmarkedVideos.length > 0) { markVideos(unmarkedVideos); } }, 300); // Wait 300ms after last scroll to avoid constant checks });
内容的提问来源于stack exchange,提问作者SagiZiv

