OPGG 介面優化與互動優化

優化 OPGG 的英雄分析頁面與遊戲模式頁面的元素排版,並將英雄分析頁面中點擊英雄名稱的連結改為『在新分頁中開啟』

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         OPGG界面优化与交互优化
// @name:en      OPGG UI optimization and interaction improvements
// @name:en-GB   OPGG UI optimization and interaction improvements
// @name:ko      OP.GG의 UI 개선 및 인터랙션 최적화
// @name:zh      OPGG界面优化与交互优化
// @name:zh-CN   OPGG界面优化与交互优化
// @name:zh-HK   OPGG 介面優化與互動優化
// @name:zh-MO   OPGG 介面優化與互動優化
// @name:zh-MY   OPGG界面优化与交互优化
// @name:zh-SG   OPGG界面优化与交互优化
// @name:zh-TW   OPGG 介面優化與互動優化
// @namespace    http://tampermonkey.net/
// @version      2.0.1
// @description  OPGG的英雄分析页面和游戏模式页面的元素排版优化、英雄分析页面点击英雄名字链接跳转方式改为'新建标签页打开'
// @description:en     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.
// @description:en-GB  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.
// @description:ko     OPGG의 챔피언 분석 페이지와 게임 모드 페이지의 요소 배치를 최적화하고, 챔피언 분석 페이지에서 챔피언 이름을 클릭할 때 링크가‘새 탭에서 열리도록’수정합니다.
// @description:zh     OPGG的英雄分析页面和游戏模式页面的元素排版优化、英雄分析页面点击英雄名字链接跳转方式改为'新建标签页打开'
// @description:zh-CN  OPGG的英雄分析页面和游戏模式页面的元素排版优化、英雄分析页面点击英雄名字链接跳转方式改为'新建标签页打开'
// @description:zh-HK  優化 OPGG 的英雄分析頁面與遊戲模式頁面的元素排版,並將英雄分析頁面中點擊英雄名稱的連結改為『在新分頁中開啟』
// @description:zh-MO  優化 OPGG 的英雄分析頁面與遊戲模式頁面的元素排版,並將英雄分析頁面中點擊英雄名稱的連結改為『在新分頁中開啟』
// @description:zh-MY  OPGG的英雄分析页面和游戏模式页面的元素排版优化、英雄分析页面点击英雄名字链接跳转方式改为'新建标签页打开'
// @description:zh-SG  OPGG的英雄分析页面和游戏模式页面的元素排版优化、英雄分析页面点击英雄名字链接跳转方式改为'新建标签页打开'
// @description:zh-TW  優化 OPGG 的英雄分析頁面與遊戲模式頁面的元素排版,並將英雄分析頁面中點擊英雄名稱的連結改為『在新分頁中開啟』
// @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
// ==/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();

})();