您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在用户页面显示收藏状态分布彩色条
// ==UserScript== // @name Bangumi 不同类型收藏状态比例条图 // @namespace https://bgm.tv/group/topic/422194 // @version 1.3 // @description 在用户页面显示收藏状态分布彩色条 // @author owho // @match http*://bgm.tv/user/* // @match http*://bangumi.tv/user/* // @match http*://chii.in/user/* // @grant none // @license MIT // @gf https://greasyfork.org/zh-CN/scripts/534247 // @gadget https://bgm.tv/dev/app/3773 // ==/UserScript== (function () { 'use strict'; const categoryMap = { anime: '动画', book: '书籍', game: '游戏', music: '音乐', real: '三次元' } // 添加样式(包含tooltip样式) const style = document.createElement('style'); style.textContent = /* css */` html { --bar-bg-color: rgba(0, 0, 0, 0.05); --bar-color: #555; } html[data-theme="dark"] { --bar-bg-color: rgba(255, 255, 255, 0.05); --bar-color: #dcdcdc; } .status-bars-container { display: flex; flex-direction: column; gap: 2px; margin-inline: 8px; margin-block: 5px; } .category-container { display: flex; flex-direction: column; cursor: pointer; border-radius: 5px; padding: 5px; transition: background-color 0.3s; } .category-container:hover, .category-container:active, .category-container:focus { background-color: var(--bar-bg-color); } .category-container:hover .status-bar, .category-container:active .status-bar, .category-container:focus .status-bar { width: 100%!important; } .category-title { color: var(--bar-color); } .status-bar { display: flex; height: 10px; border-radius: 3px; overflow: hidden; transition: width 0.3s; min-width: 10px; /* 设置最小宽度,确保圆角显示 */ } .status-segment { height: 100%; transition: width 0.3s; position: relative; border: 1px solid transparent; box-shadow: 0px 2px 5px rgba(0,0,0,0.1); } /* 状态颜色定义 - 带渐变效果 */ .status-wish { background: linear-gradient(120deg, rgba(255, 183, 77, 0.8) 15%, rgba(255, 183, 77, 0.9) 47%, #FFB74D 73%); border-color: #FFB74D; box-shadow: 0px 2px 5px rgba(255, 183, 77, 0.5); } .status-doing { background: linear-gradient(120deg, rgba(76, 175, 80, 0.8) 15%, rgba(76, 175, 80, 0.9) 47%, #4CAF50 73%); border-color: #4CAF50; box-shadow: 0px 2px 5px rgba(76, 175, 80, 0.5); } .status-done { background: linear-gradient(120deg, rgba(33, 150, 243, 0.8) 15%, rgba(33, 150, 243, 0.9) 47%, #2196F3 73%); border-color: #2196F3; box-shadow: 0px 2px 5px rgba(33, 150, 243, 0.5); } .status-onhold { background: linear-gradient(120deg, rgba(158, 158, 158, 0.8) 15%, rgba(158, 158, 158, 0.9) 47%, #9E9E9E 73%); border-color: #9E9E9E; box-shadow: 0px 2px 5px rgba(158, 158, 158, 0.5); } .status-dropped { background: linear-gradient(120deg, rgba(244, 67, 54, 0.8) 15%, rgba(244, 67, 54, 0.9) 47%, #F44336 73%); border-color: #F44336; box-shadow: 0px 2px 5px rgba(244, 67, 54, 0.5); } /* 悬停效果增强 */ .status-segment:hover { opacity: 0.9; transform: translateY(-1px); box-shadow: 0px 3px 8px rgba(0,0,0,0.2); } .hidden { display: none; } /* 自定义Tooltip样式 */ .custom-tooltip { position: absolute; background: rgba(0, 0, 0, 0.8); color: white; padding: 4px 8px; border-radius: 3px; font-size: 12px; pointer-events: none; z-index: 1000; opacity: 0; transition: opacity 0.1s ease; /* 缩短过渡时间,减少闪烁感知 */ white-space: nowrap; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); } /* 暗色主题适配 */ html[data-theme="dark"] .custom-tooltip { background: rgba(255, 255, 255, 0.8); color: #333; } `; document.head.appendChild(style); // 自定义Tooltip实现(修复闪烁问题) function initTooltips() { // 1. 全局状态管理:记录当前hover的元素和隐藏定时器,避免重复操作 let currentHoverElement = null; let hideTimer = null; // 2. 创建全局唯一tooltip元素 const tooltip = document.createElement('div'); tooltip.className = 'custom-tooltip'; tooltip.style.display = 'none'; document.body.appendChild(tooltip); // 3. 统一的显示tooltip函数 function showTooltip(element) { // 清除之前的隐藏定时器(关键:避免前一个元素的隐藏操作生效) if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; } // 获取当前元素的提示文本 const titleText = element.getAttribute('data-tooltip'); if (!titleText) return; // 更新tooltip内容和位置 tooltip.textContent = titleText; tooltip.style.display = 'block'; // 计算居中位置(基于元素自身位置) const rect = element.getBoundingClientRect(); const tooltipHeight = tooltip.offsetHeight || 20; // 兼容未渲染完成的情况 tooltip.style.left = `${rect.left + rect.width / 2 - tooltip.offsetWidth / 2}px`; tooltip.style.top = `${rect.top - tooltipHeight - 5}px`; // 立即显示(缩短过渡时间,减少闪烁) setTimeout(() => { tooltip.style.opacity = '1'; }, 10); // 更新当前hover元素 currentHoverElement = element; } // 4. 统一的隐藏tooltip函数(延迟执行,给元素切换留时间) function hideTooltip() { // 延迟30ms隐藏,避免鼠标快速切换时的闪烁 hideTimer = setTimeout(() => { // 确认当前没有hover的元素,再隐藏 if (!currentHoverElement) { tooltip.style.opacity = '0'; setTimeout(() => { tooltip.style.display = 'none'; }, 100); // 匹配opacity过渡时间 } }, 30); } // 5. 为所有状态段绑定事件 document.querySelectorAll('.status-segment.titleTip').forEach(element => { // 存储提示文本到data-tooltip,移除原生title避免冲突 const titleText = element.getAttribute('title'); element.setAttribute('data-tooltip', titleText); element.removeAttribute('title'); // 鼠标进入:显示当前元素的tooltip element.addEventListener('mouseenter', () => { showTooltip(element); }); // 鼠标离开:标记当前元素为空,并触发隐藏(延迟执行) element.addEventListener('mouseleave', () => { // 只有当离开的是当前hover的元素时,才触发隐藏 if (currentHoverElement === element) { currentHoverElement = null; hideTooltip(); } }); // 鼠标移动:调整tooltip位置(避免溢出视口) element.addEventListener('mousemove', (e) => { if (tooltip.style.display === 'none') return; const viewportWidth = window.innerWidth; const tooltipWidth = tooltip.offsetWidth || 80; let left = e.pageX - tooltipWidth / 2; // 边界处理:避免tooltip超出视口左右侧 if (left + tooltipWidth > viewportWidth) { left = viewportWidth - tooltipWidth - 10; } if (left < 10) { left = 10; } tooltip.style.left = `${left}px`; tooltip.style.top = `${e.pageY - (tooltip.offsetHeight || 20) - 10}px`; }); }); } // 等待页面加载完成 $(document).ready(function () { // 获取所有分类数据 const categories = ['anime', 'book', 'game', 'music', 'real']; const container = $('<div class="status-bars-container"></div>'); let maxTotal = 0; const categoryTotals = {}; // 先计算每个分类的总数,并找出最大值 categories.forEach(category => { const selector = `#${category} .num`; const items = $(selector); if (items.length === 0) return; // 跳过没有数据的分类 const total = items.toArray().reduce((sum, num) => { return sum + +num.textContent; }, 0); categoryTotals[category] = total; if (total > maxTotal) { maxTotal = total; } }); categories.forEach(category => { const selector = `#${category} .num`; const items = $(selector); if (items.length === 0) return; // 跳过没有数据的分类 const total = categoryTotals[category]; if (total === 0) return; // 跳过总数为 0 的分类 // 创建分类容器 const categoryContainer = $('<div class="category-container" role="button" tabindex="0"></div>'); // 创建分类标题 const categoryTitle = $('<div class="category-title"></div>').text( categoryMap[category] ); // 创建状态条 const bar = $(`<div class="status-bar ${category}-status-bar"></div>`); const relativeWidth = (total / maxTotal) * 100; bar.css('width', `${relativeWidth}%`); items.each(function () { const count = +$(this).text(); const statusText = $(this).prev().text(); const percentage = (count / total) * 100; // 确定状态类别 let statusClass = ''; if (statusText.includes('想')) statusClass = 'status-wish'; else if (statusText.includes('在')) statusClass = 'status-doing'; else if (statusText.includes('过')) statusClass = 'status-done'; else if (statusText.includes('搁置')) statusClass = 'status-onhold'; else if (statusText.includes('抛弃')) statusClass = 'status-dropped'; if (statusClass && count > 0) { const segment = $(`<div class="status-segment ${statusClass} titleTip" title="${count}${statusText}"></div>`) .css('width', `${percentage}%`); bar.append(segment); } }); // 添加到容器 categoryContainer.append(categoryTitle); categoryContainer.append(bar); container.append(categoryContainer); let longPressTimer; const handleLongPress = function () { bar.toggleClass('hidden'); // 保存隐藏状态到localStorage const hiddenCategories = JSON.parse(localStorage.getItem('hiddenBangumiCategories') || '{}'); hiddenCategories[category] = bar.hasClass('hidden'); localStorage.setItem('hiddenBangumiCategories', JSON.stringify(hiddenCategories)); }; // 为容器添加长按事件,兼容触摸屏和非触摸屏设备 categoryContainer.on('touchstart mousedown', function () { longPressTimer = setTimeout(handleLongPress, 500); }); categoryContainer.on('touchend mouseup', function () { clearTimeout(longPressTimer); }); }); // 将容器插入到页面 $('.userStats').append(container); // 初始化自定义工具提示(修复后版本) initTooltips(); // 从localStorage加载隐藏状态 const hiddenCategories = JSON.parse(localStorage.getItem('hiddenBangumiCategories') || '{}'); Object.keys(hiddenCategories).forEach(category => { const bar = $(`.${category}-status-bar`); if (hiddenCategories[category] && bar.length) { bar.addClass('hidden'); } }); }); })();