// ==UserScript== // @name OPGG界面优化、交互优化 // @namespace http://tampermonkey.net/ // @version 2.0 // @description OPGG的英雄分析页面和游戏模式页面的元素排版优化、英雄分析页面点击英雄名字链接跳转方式改为'新建标签页打开'(OPGG의 챔피언 분석 페이지와 게임 모드 페이지의 요소 배치를 최적화하고, 챔피언 분석 페이지에서 챔피언 이름을 클릭할 때 링크가‘새 탭에서 열리도록’수정합니다.)(Optimize the layout of elements on OPGG’s Champions page and Game modes page, and change the champion name links on the Champions page so they open in a new tab.) // @license Copyright © 2025 Leon. All rights reserved. // @author Leon // @match https://op.gg/zh-cn* // @match https://op.gg/ko* // @match https://op.gg/* // @icon https://www.google.com/s2/favicons?sz=64&domain=op.gg // @run-at document-start // @grant GM_addStyle // @downloadURL none // ==/UserScript== (function() { 'use strict'; // ========================================== // 1. 静态常量定义 (避免GC压力) // ========================================== const STYLE_ID = 'my-optimized-spa-style'; // 预定义CSS字符串,避免运行时拼接 const CSS_CHAMPIONS = ` @media (min-width: 1080px) { .md\\:w-width-limit { width: 1600px; } .md\\:text-lg { margin-left: 200px; } .md\\:w-\\[740px\\] { width: 830px; } .md\\:w-\\[332px\\] { width: 532px; } } .w-\\[332px\\] { width: 532px; } .flex-row-reverse { flex-direction: row; margin-left: 100px; } .flex { display: flex; justify-content: center; } .md\\:w-auto { justify-content: flex-start !important; } #content-header { display: none; } `; const CSS_MODES = ` .md\\:max-w-\\[332px\\], .md\\:gap-2 { width: 334px !important; } .md\\:max-w-\\[332px\\] { max-width: 405px; } #content-header { display: none; } `; // ========================================== // 2. 状态缓存 (核心性能优化点) // ========================================== // 缓存 DOM 节点引用 let _cachedStyleEl = null; // 缓存当前页面类型,防止重复渲染 let _currentType = null; // 懒加载获取 Style 标签,终身只操作一次 DOM 插入 function getStyleElement() { if (_cachedStyleEl) return _cachedStyleEl; _cachedStyleEl = document.getElementById(STYLE_ID); if (!_cachedStyleEl) { _cachedStyleEl = document.createElement('style'); _cachedStyleEl.id = STYLE_ID; _cachedStyleEl.type = 'text/css'; document.head.appendChild(_cachedStyleEl); } return _cachedStyleEl; } // ========================================== // 3. 样式应用逻辑 // ========================================== function updateStyles() { const path = location.pathname; let newType = 'other'; // 快速判断页面类型 (使用 indexOf 比 includes 微快,且兼容性更好) if (/\/champions\/?$/.test(path)) { newType = 'champions'; } else if (path.indexOf('modes') !== -1) { newType = 'modes'; } // 【性能关键】如果页面类型没变,直接退出! // 避免浏览器执行耗时的 Recalculate Style if (newType === _currentType) return; // 更新状态 _currentType = newType; const styleEl = getStyleElement(); // 只有状态改变时才写入 DOM if (newType === 'champions') { styleEl.textContent = CSS_CHAMPIONS; } else if (newType === 'modes') { styleEl.textContent = CSS_MODES; } else { styleEl.textContent = ''; // 清理样式 } } // ========================================== // 4. 点击拦截逻辑 (Event Hot Path) // ========================================== function handleClick(e) { // 【性能关键】快速检查:利用缓存变量判断。 // 如果当前不是 champions 页面,直接结束函数,不进行任何 DOM 遍历。 if (_currentType !== 'champions') return; // 只有确定在 champions 页面,才开始查找 DOM // closest 是原生 C++ 实现,速度很快,但能省则省 const link = e.target.closest('a'); if (!link || !link.href) return; // 检查父级容器:精准匹配 class const targetContainer = link.closest('.flex.flex-row-reverse.gap-2'); if (targetContainer) { link.target = "_blank"; e.stopPropagation(); // 阻止 SPA 路由接管 } } // ========================================== // 5. 初始化与事件绑定 // ========================================== // 绑定点击事件 (捕获阶段) document.addEventListener('click', handleClick, true); // 劫持 History API const _historyWrap = function(type) { const orig = history[type]; return function() { const rv = orig.apply(this, arguments); const e = new Event(type); e.arguments = arguments; window.dispatchEvent(e); return rv; }; }; history.pushState = _historyWrap('pushState'); history.replaceState = _historyWrap('replaceState'); // 绑定路由事件 // 使用同一个处理函数,减少内存占用 window.addEventListener('pushState', updateStyles); window.addEventListener('replaceState', updateStyles); window.addEventListener('popstate', updateStyles); // 首次执行 updateStyles(); })();