// ==UserScript== // @name 【古诗文网】自动展开注释、译文和赏析 // @namespace https://github.com/realSilasYang // @version 2025-4-19 // @description 修改标签页名;自动展开古诗文网的注释、译文和赏析内容,性能优化版本 // @author 阳熙来 // @match https://www.gushiwen.cn/* // @grant none // @icon https://www.google.com/s2/favicons?domain=https://www.gushiwen.cn // @license GNU GPLv3 // @downloadURL https://update.greasyfork.icu/scripts/522487/%E3%80%90%E5%8F%A4%E8%AF%97%E6%96%87%E7%BD%91%E3%80%91%E8%87%AA%E5%8A%A8%E5%B1%95%E5%BC%80%E6%B3%A8%E9%87%8A%E3%80%81%E8%AF%91%E6%96%87%E5%92%8C%E8%B5%8F%E6%9E%90.user.js // @updateURL https://update.greasyfork.icu/scripts/522487/%E3%80%90%E5%8F%A4%E8%AF%97%E6%96%87%E7%BD%91%E3%80%91%E8%87%AA%E5%8A%A8%E5%B1%95%E5%BC%80%E6%B3%A8%E9%87%8A%E3%80%81%E8%AF%91%E6%96%87%E5%92%8C%E8%B5%8F%E6%9E%90.meta.js // ==/UserScript== (function () { 'use strict'; // 修改网页标题 document.title = "古诗文网"; // 配置项 const CONFIG = { CLICK_DELAY: 50, // 每个按钮点击的延迟时间(毫秒) LOAD_MARGIN: '100px', // IntersectionObserver 提前加载的距离(像素) DEBUG: false // 是否开启调试模式 }; // 按钮与诗词容器的选择器 const SELECTORS = { POEM_CONTAINER: '.sons .cont', // 用于标识诗词内容区域的选择器 BUTTONS: { NOTE: 'img[src="https://ziyuan.guwendao.net/siteimg/zhu-pic.png"]', // 注释按钮 TRANSLATION: 'img[src="https://ziyuan.guwendao.net/siteimg/yi-pic.png"]', // 译文按钮 ANALYSIS: 'img[src="https://ziyuan.guwendao.net/siteimg/shang-pic.png"]' // 赏析按钮 } }; /** * 日志工具,用于输出调试信息 */ const logger = { log: (...args) => CONFIG.DEBUG && console.log('[古诗文展开]', ...args), error: (...args) => CONFIG.DEBUG && console.error('[古诗文展开]', ...args) }; /** * 检查元素是否在视口中 * @param {Element} element - 要检查的 HTML 元素 * @returns {boolean} - 返回布尔值,表示元素是否在视口中 */ function isInViewport(element) { const rect = element.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } /** * 快速点击诗词容器内的所有按钮 * @param {Element} container - 诗词容器元素 */ function clickButtonsInContainer(container) { try { // 如果容器已被处理过,则直接返回 if (!container || container.hasAttribute('processed')) { return; } const buttons = []; // 遍历每种按钮的选择器,收集未点击的按钮 Object.entries(SELECTORS.BUTTONS).forEach(([type, selector]) => { const button = container.querySelector(selector); if (button && !button.hasAttribute('clicked') && button.style.display !== 'none') { buttons.push({ type, element: button }); } }); // 按顺序点击按钮,并设置点击的时间间隔 if (buttons.length) { buttons.forEach((button, index) => { setTimeout(() => { try { button.element.setAttribute('clicked', 'true'); // 标记按钮已被点击 button.element.click(); // 执行点击操作 logger.log(`Clicked ${button.type} button`); } catch (err) { logger.error(`Error clicking ${button.type} button:`, err); } }, index * CONFIG.CLICK_DELAY); }); } // 标记容器已处理 container.setAttribute('processed', 'true'); } catch (err) { logger.error('Error in clickButtonsInContainer:', err); } } /** * 使用 IntersectionObserver 优化性能,按需加载诗词容器 */ function setupIntersectionObserver() { try { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { clickButtonsInContainer(entry.target); // 当进入视口时,处理该容器 observer.unobserve(entry.target); // 停止观察该容器 } }); }, { rootMargin: CONFIG.LOAD_MARGIN, // 设置提前加载的距离 threshold: 0 // 当容器完全进入视口时触发 }); // 遍历所有未处理的诗词容器,并开始观察 document.querySelectorAll(`${SELECTORS.POEM_CONTAINER}:not([processed])`).forEach(container => { observer.observe(container); }); } catch (err) { logger.error('Error in setupIntersectionObserver:', err); processVisibleContainers(); // 降级处理:直接处理当前视口中的容器 } } /** * 降级处理:直接处理当前视口中的容器 */ function processVisibleContainers() { try { const containers = document.querySelectorAll(`${SELECTORS.POEM_CONTAINER}:not([processed])`); containers.forEach(container => { if (isInViewport(container)) { clickButtonsInContainer(container); // 处理视口中的容器 } }); } catch (err) { logger.error('Error in processVisibleContainers:', err); } } /** * 监听 DOM 中新增的内容,用于动态加载的场景 */ function observeNewContent() { try { const observer = new MutationObserver((mutations) => { let shouldProcess = false; // 遍历所有变动记录,判断是否有新增的诗词容器 for (const mutation of mutations) { if (mutation.addedNodes.length) { const hasNewPoems = Array.from(mutation.addedNodes).some(node => node.nodeType === Node.ELEMENT_NODE && (node.matches(SELECTORS.POEM_CONTAINER) || node.querySelector(SELECTORS.POEM_CONTAINER)) ); if (hasNewPoems) { shouldProcess = true; break; } } } if (shouldProcess) { setupIntersectionObserver(); // 如果有新增内容,则重新设置观察器 } }); // 监听整个文档的子节点变动 observer.observe(document.body, { childList: true, subtree: true }); } catch (err) { logger.error('Error in observeNewContent:', err); } } /** * 防抖函数:限制高频调用,延迟执行 * @param {Function} func - 要防抖的函数 * @param {number} wait - 延迟时间(毫秒) * @returns {Function} */ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } /** * 初始化脚本 */ function init() { try { logger.log('Initializing...'); setupIntersectionObserver(); // 设置 IntersectionObserver observeNewContent(); // 监听新增内容 } catch (err) { logger.error('Error in init:', err); } } // 使用防抖包装初始化函数 const debouncedInit = debounce(init, 100); // 添加页面事件监听器 window.addEventListener('load', debouncedInit); window.addEventListener('popstate', debouncedInit); window.addEventListener('scroll', debounce(processVisibleContainers, 100)); // 重写 history.pushState 方法,监控前端路由变化 const originalPushState = history.pushState; history.pushState = function () { originalPushState.apply(this, arguments); debouncedInit(); }; // 立即初始化 debouncedInit(); // 如果开启了调试模式,导出调试接口 if (CONFIG.DEBUG) { window._gushiwen_debug = { processVisibleContainers, clickButtonsInContainer, CONFIG, SELECTORS }; } })();