Luogu 题目难度隐藏插件 V1.3

· · 科技·工程

插件介绍:

写题或 VP 模拟赛时我们经常会不小心看到题目难度,从而大致猜出题目所使用的算法,导致直接跳过或者不屑于做 很不利于提升我们的判题水平。于是乎做了一个小插件,能够一键隐藏题目难度,切换悬停显示/显示/隐藏三种模式。希望能够帮到同志们。

有了这个插件妈妈再也不用担心我不会对着黑题傻想俩小时了

使用说明:

此为篡改猴脚本,需先安装篡改猴方可使用。具体安装方式与安装一般的篡改猴脚本相同(如是 MSEdge 用户请在扩展中打开“开发人员模式”)。网上教程诸多,这里不再赘述。

更新日志:

【2025.8.21】正式发布 V1.2!

【2025.8.21】发布 V1.3,修复了手残把按钮拖到页面外弄不回来的问题,修复了题单无法隐藏难度的 bug.

插件源码:

// ==UserScript==
// @name         Luogu 难度隐藏开关
// @namespace    http://tampermonkey.net/
// @version      V1.3
// @description  Luogu 题目难度标签隐藏/悬停显示/始终显示,浮动按钮可拖动、记忆位置、防止拖出屏幕、拖动时半透明。
// @author       LTTXiaochuan
// @match        https://www.luogu.com.cn/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-idle
// ==/UserScript==

(function () {
  'use strict';

  const DEFAULT_MODE = GM_getValue('lg_diff_mode', 'hover');

  const selectors = [
    '.l-flex-info-row a[href*="problem/list?difficulty"] span',
    '.difficulty span span',
    '.difficulty span.lfe-caption'
  ];

  const styleId = 'tm-luogu-diff-style';
  const css = `
  .tm-diff-hidden, .tm-diff-hover { transition: opacity .15s ease !important; }
  .tm-diff-hidden { opacity: 0 !important; }
  .tm-diff-hover { opacity: 0 !important; }
  .tm-diff-hover:hover { opacity: 1 !important; }
  #tm-luogu-diff-toggle {
    position: fixed;
    top: 12px;
    right: 12px;
    z-index: 999999;
    background: rgba(0,0,0,0.6);
    color: #fff !important;
    padding: 6px 8px;
    font-size: 12px;
    border-radius: 6px;
    cursor: move;
    user-select: none;
    box-shadow: 0 2px 6px rgba(0,0,0,.4);
  }
  #tm-luogu-diff-toggle small { opacity: .8; display:block; font-size:11px; margin-top:2px; color: #fff !important; }
  `;

  function injectStyle() {
    if (document.getElementById(styleId)) return;
    const s = document.createElement('style');
    s.id = styleId;
    s.textContent = css;
    document.head.appendChild(s);
  }

  let currentMode = DEFAULT_MODE;

  function applyToElement(el, mode) {
    el.classList.remove('tm-diff-hidden', 'tm-diff-hover');
    if (mode === 'hidden') el.classList.add('tm-diff-hidden');
    else if (mode === 'hover') el.classList.add('tm-diff-hover');
  }

  function applyModeToAll(mode) {
    const combined = selectors.join(',');
    document.querySelectorAll(combined).forEach(el => applyToElement(el, mode));
  }

  let mo = null;
  function startObserver() {
    if (mo) return;
    mo = new MutationObserver(muts => {
      if (muts.some(m => m.addedNodes && m.addedNodes.length)) {
        applyModeToAll(currentMode);
      }
    });
    mo.observe(document.body, { childList: true, subtree: true });
  }

  function saveMode(mode) {
    currentMode = mode;
    GM_setValue('lg_diff_mode', mode);
    updateToggleText();
    applyModeToAll(mode);
  }

  function nextMode(m) {
    if (m === 'show') return 'hover';
    if (m === 'hover') return 'hidden';
    return 'show';
  }

  function createToggle() {
    if (document.getElementById('tm-luogu-diff-toggle')) return;
    const btn = document.createElement('div');
    btn.id = 'tm-luogu-diff-toggle';
    btn.title = '点击切换难度显示模式';
    btn.style.minWidth = '120px';
    btn.style.textAlign = 'center';
    btn.style.fontFamily = 'Segoe UI,微软雅黑,sans-serif';

    const savedX = GM_getValue('lg_diff_x', null);
    const savedY = GM_getValue('lg_diff_y', null);
    if (savedX !== null && savedY !== null) {
      btn.style.left = savedX + 'px';
      btn.style.top = savedY + 'px';
      btn.style.right = 'auto';
    }

    btn.addEventListener('click', (e) => {
      if (btn._dragging) return;
      saveMode(nextMode(currentMode));
    });

    makeDraggable(btn);
    document.body.appendChild(btn);
    updateToggleText();
  }

  function updateToggleText() {
    const btn = document.getElementById('tm-luogu-diff-toggle');
    if (!btn) return;
    const label = (currentMode === 'show') ? '显示难度' : (currentMode === 'hover') ? '悬停显示难度' : '隐藏难度';
    btn.innerHTML = ` <strong> ${label} </strong> <small> (点击切换) </small> `;
  }

  // 拖动逻辑 + 防止拖出屏幕 + 拖动时半透明
  function makeDraggable(el) {
    let offsetX, offsetY, isDown = false;

    el.addEventListener('mousedown', (e) => {
      isDown = true;
      el._dragging = false;
      offsetX = e.clientX - el.offsetLeft;
      offsetY = e.clientY - el.offsetTop;
      el.style.opacity = "0.6"; // 拖动时半透明
      document.addEventListener('mousemove', move);
      document.addEventListener('mouseup', up);
    });

    function move(e) {
      if (!isDown) return;
      el._dragging = true;
      let newLeft = e.clientX - offsetX;
      let newTop = e.clientY - offsetY;

      // 限制在可视区域内
      const maxLeft = window.innerWidth - el.offsetWidth;
      const maxTop = window.innerHeight - el.offsetHeight;
      newLeft = Math.max(0, Math.min(maxLeft, newLeft));
      newTop = Math.max(0, Math.min(maxTop, newTop));

      el.style.left = newLeft + 'px';
      el.style.top = newTop + 'px';
      el.style.right = 'auto';
    }

    function up() {
      if (!isDown) return;
      isDown = false;
      el.style.opacity = "1"; // 拖动结束恢复
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseup', up);
      GM_setValue('lg_diff_x', el.offsetLeft);
      GM_setValue('lg_diff_y', el.offsetTop);
      setTimeout(() => { el._dragging = false; }, 50);
    }
  }

  function init() {
    injectStyle();
    createToggle();
    applyModeToAll(currentMode);
    startObserver();
    window.addEventListener('load', () => setTimeout(() => applyModeToAll(currentMode), 300));
  }

  init();
})();