您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically scrolls to a specified text within a YouTube grid. Search box in masthead, search on button click only, stop button, animated border highlighting, and no results message. Improved robustness, accessibility, and user experience.
当前为
// ==UserScript== // @name YouTube Grid Auto-Scroll & Search (Ultra Optimized - Instant) // @match https://www.youtube.com/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @version 2.3 // @description Automatically scrolls to a specified text within a YouTube grid. Search box in masthead, search on button click only, stop button, animated border highlighting, and no results message. Improved robustness, accessibility, and user experience. // @author Your Name (with further optimization) // @license MIT // @namespace https://gf.zukizuki.org/users/1435316 // ==/UserScript== (function() { 'use strict'; let targetText = ""; let searchBox; let isSearching = false; let searchInput; let searchButton; let stopButton; let observer; let searchTimeout; const SEARCH_TIMEOUT_MS = 20000; // 20 seconds const SCROLL_DELAY_MS = 750; const MAX_SEARCH_LENGTH = 255; // Prevent excessively long search strings GM_addStyle(` /* Existing CSS (with additions) */ #floating-search-box { background-color: #222; padding: 5px; border: 1px solid #444; border-radius: 5px; display: flex; align-items: center; margin-left: 10px; } /* Responsive width for smaller screens */ @media (max-width: 768px) { #floating-search-box input[type="text"] { width: 150px; /* Smaller width on smaller screens */ } } #floating-search-box input[type="text"] { background-color: #333; color: #fff; border: 1px solid #555; padding: 3px 5px; border-radius: 3px; margin-right: 5px; width: 200px; height: 30px; } #floating-search-box input[type="text"]:focus { outline: none; border-color: #065fd4; } #floating-search-box button { background-color: #065fd4; color: white; border: none; padding: 3px 8px; border-radius: 3px; cursor: pointer; height: 30px; } #floating-search-box button:hover { background-color: #0549a8; } #floating-search-box button:focus { outline: none; } #stop-search-button { background-color: #aa0000; /* Red color */ } #stop-search-button:hover { background-color: #800000; } .highlighted-text { position: relative; /* Needed for the border to be positioned correctly */ z-index: 1; /* Ensure the border is on top of other elements */ } /* Creates the animated border effect */ .highlighted-text::before { content: ''; position: absolute; top: -2px; left: -2px; right: -2px; bottom: -2px; border: 2px solid transparent; border-radius: 8px; background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet); background-size: 400% 400%; animation: gradientAnimation 5s ease infinite; z-index: -1; } /* Keyframes for the gradient animation */ @keyframes gradientAnimation { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } /* Style for the error message */ #search-error-message { color: red; font-weight: bold; padding: 5px; position: fixed; /* Fixed position */ top: 50px; /* Position below the masthead (adjust as needed)*/ left: 50%; transform: translateX(-50%); /* Center horizontally */ background-color: rgba(0, 0, 0, 0.8); /* Semi-transparent black */ color: white; border-radius: 5px; z-index: 10000; /* Ensure it's on top */ display: none; /* Initially hidden */ } /* Style for the no results message */ #search-no-results-message { color: #aaa; /* Light gray */ padding: 5px; position: fixed; top: 50px; /* Same position as error message */ left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.8); border-radius: 5px; z-index: 10000; display: none; /* Initially hidden */ } `); // --- Create the Search Box --- function createSearchBox() { searchBox = document.createElement('div'); searchBox.id = 'floating-search-box'; searchBox.setAttribute('role', 'search'); // Add ARIA role for accessibility searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.placeholder = 'Search to scroll...'; searchInput.value = GM_getValue('lastSearchTerm', ''); searchInput.setAttribute('aria-label', 'Search within YouTube grid'); // ARIA label searchInput.maxLength = MAX_SEARCH_LENGTH; // Limit input length searchButton = document.createElement('button'); searchButton.textContent = 'Search'; searchButton.addEventListener('click', searchAndScroll); searchButton.setAttribute('aria-label', 'Start search'); // ARIA label stopButton = document.createElement('button'); stopButton.textContent = 'Stop'; stopButton.id = 'stop-search-button'; stopButton.addEventListener('click', stopSearch); stopButton.setAttribute('aria-label', 'Stop search'); // ARIA label searchBox.appendChild(searchInput); searchBox.appendChild(searchButton); searchBox.appendChild(stopButton); const mastheadEnd = document.querySelector('#end.ytd-masthead'); const buttonsContainer = document.querySelector('#end #buttons'); if (mastheadEnd) { if(buttonsContainer){ mastheadEnd.insertBefore(searchBox, buttonsContainer); } else{ mastheadEnd.appendChild(searchBox); } } else { console.error("Could not find the YouTube masthead's end element."); showErrorMessage("Could not find the YouTube masthead. Search box placed at top of page."); document.body.insertBefore(searchBox, document.body.firstChild); } // Trigger search on load if text is present and the URL indicates a channel page if (searchInput.value.trim() !== "" && window.location.href.includes("/videos")) { searchAndScroll(); } } // --- Show Error Message --- function showErrorMessage(message) { let errorDiv = document.getElementById('search-error-message'); if (!errorDiv) { errorDiv = document.createElement('div'); errorDiv.id = 'search-error-message'; document.body.appendChild(errorDiv); } errorDiv.textContent = message; errorDiv.style.display = 'block'; setTimeout(() => { errorDiv.style.display = 'none'; }, 5000); // Hide after 5 seconds } // --- Show "No Results" Message --- function showNoResultsMessage() { let noResultsDiv = document.getElementById('search-no-results-message'); if (!noResultsDiv) { noResultsDiv = document.createElement('div'); noResultsDiv.id = 'search-no-results-message'; noResultsDiv.textContent = "No matching results found."; // Set the text document.body.appendChild(noResultsDiv); } noResultsDiv.style.display = 'block'; // Hide the message after a few seconds setTimeout(() => { noResultsDiv.style.display = 'none'; }, 5000); } // --- Stop Search Function --- function stopSearch() { if (observer) { observer.disconnect(); } isSearching = false; clearTimeout(searchTimeout); const prevHighlighted = document.querySelector('.highlighted-text'); if (prevHighlighted) { prevHighlighted.classList.remove('highlighted-text'); } } // --- Optimized Search and Scroll Function --- function searchAndScroll() { if (isSearching) return; isSearching = true; clearTimeout(searchTimeout); if (observer) { observer.disconnect(); } targetText = searchInput.value.trim().toLowerCase(); if (!targetText) { isSearching = false; return; } GM_setValue('lastSearchTerm', targetText); // Remove previous highlights const prevHighlighted = document.querySelector('.highlighted-text'); if (prevHighlighted) { prevHighlighted.classList.remove('highlighted-text'); prevHighlighted.style.position = ''; //remove inline styles, if any } let foundMatch = false; // Keep track of whether a match was found observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { const titleElement = entry.target.querySelector('#video-title'); // More reliable selector if (titleElement && titleElement.textContent.toLowerCase().includes(targetText)) { foundMatch = true; entry.target.scrollIntoView({ behavior: 'auto', block: 'center' }); entry.target.classList.add('highlighted-text'); observer.disconnect(); isSearching = false; clearTimeout(searchTimeout); return; } } } }); //Observe all grid items. document.querySelectorAll('ytd-rich-grid-media').forEach(item => { observer.observe(item); }); searchTimeout = setTimeout(() => { stopSearch(); if (!foundMatch) { showNoResultsMessage(); //show no results message. } }, SEARCH_TIMEOUT_MS); setTimeout(() => { if (!document.querySelector('.highlighted-text')) { window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'auto' }); setTimeout(() => { if (!isSearching) return; // Check again in case stop was pressed isSearching = false; searchAndScroll(); // Recursive call }, SCROLL_DELAY_MS); } else { isSearching = false; } }, 100); } // --- Initialization --- createSearchBox(); })();