Automatically scrolls to a specified text within a YouTube grid. Search box in masthead, search on button click only, and stop button.
当前为
// ==UserScript==
// @name YouTube Grid Auto-Scroll & Search (Ultra Optimized - Instant)
// @match https://www.youtube.com/*
// @grant GM_addStyle
// @version 1.9
// @description Automatically scrolls to a specified text within a YouTube grid. Search box in masthead, search on button click only, and stop button.
// @author You (with further optimization)
// @license MIT
// @run-at document-end
// @namespace https://greasyfork.org/users/1435316
// ==/UserScript==
(function() {
'use strict';
let targetText = "";
let searchBox;
let isScrolling = false;
let searchInput;
let searchButton;
let stopButton; // Added stop button
let observer; // Store the observer so we can disconnect it.
// --- Styling (CSS) ---
GM_addStyle(`
#floating-search-box {
background-color: #222;
padding: 5px;
border: 1px solid #444;
border-radius: 5px;
display: flex;
align-items: center;
margin-left: 10px;
}
#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 { /* Style for the stop button */
background-color: #aa0000; /* Red color */
}
#stop-search-button:hover {
background-color: #800000;
}
.highlighted-text {
background-color: yellow;
}
`);
// --- Create the Search Box ---
function createSearchBox() {
searchBox = document.createElement('div');
searchBox.id = 'floating-search-box';
searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Search to scroll...';
// NO input event listener here. We only search on button click.
searchButton = document.createElement('button');
searchButton.textContent = 'Search';
searchButton.addEventListener('click', searchAndScroll);
stopButton = document.createElement('button'); // Create the stop button
stopButton.textContent = 'Stop';
stopButton.id = 'stop-search-button'; // Give it an ID for styling
stopButton.addEventListener('click', stopSearch); // Add the stop function
searchBox.appendChild(searchInput);
searchBox.appendChild(searchButton);
searchBox.appendChild(stopButton); // Add stop button to the box
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.");
document.body.appendChild(searchBox); // Fallback
}
}
// --- Stop Search Function ---
function stopSearch() {
if (observer) {
observer.disconnect(); // Stop observing
}
isScrolling = false;
const prevHighlighted = document.querySelector('.highlighted-text');
if (prevHighlighted) {
prevHighlighted.classList.remove('highlighted-text');
}
}
// --- Optimized Search and Scroll Function ---
function searchAndScroll() {
if (isScrolling) return;
isScrolling = true;
//disconnect any prior observer, to avoid multiple observers
if(observer) {
observer.disconnect();
}
targetText = searchInput.value.trim().toLowerCase();
if (!targetText) {
isScrolling = false;
return;
}
// Remove previous highlights
const prevHighlighted = document.querySelector('.highlighted-text');
if (prevHighlighted) {
prevHighlighted.classList.remove('highlighted-text');
}
// Intersection Observer for optimized searching
observer = new IntersectionObserver((entries) => { // Store the observer
for (const entry of entries) {
if (entry.isIntersecting) {
const titleElement = entry.target.querySelector('#video-title'); // Target title directly
if (titleElement && titleElement.textContent.toLowerCase().includes(targetText)) {
entry.target.scrollIntoView({ behavior: 'auto', block: 'center' }); // Instant scroll
entry.target.classList.add('highlighted-text');
observer.disconnect(); // Stop observing once found. VERY IMPORTANT.
isScrolling = false; // Reset flag
return; // Exit early
}
}
}
});
// Observe initial set of grid items
document.querySelectorAll('ytd-rich-grid-media').forEach(item => {
observer.observe(item);
});
// Immediate scroll to bottom, but with a *very* short delay.
setTimeout(() => {
if (!document.querySelector('.highlighted-text')) {
window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'auto' }); //instant scroll
//shorter delay, before searching again
setTimeout(() => {
if(!isScrolling) return; // Check again, in case stop was pressed
isScrolling = false;
searchAndScroll();
}, 750);
}else{
isScrolling = false; // Reset the flag even if found by observer.
}
}, 100);
}
// --- Initialization ---
createSearchBox();
})();