您需要先安装一个扩展,例如 篡改猴、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://greasyfork.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();
- })();