您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在B站动态页(t.bilibili.com)鼠标悬停标题/简介一小段时间后,显示完整标题/简介,不用点进视频就能知道主要内容。具有高优先级样式以避免冲突。
// ==UserScript== // @name B站动态首页鼠标悬停完整标题和简介显示 // @name:en Bilibili Dynamic Title Tooltip // @namespace http://tampermonkey.net/ // @version 2.0.0 // @description 在B站动态页(t.bilibili.com)鼠标悬停标题/简介一小段时间后,显示完整标题/简介,不用点进视频就能知道主要内容。具有高优先级样式以避免冲突。 // @description:en Shows the full video title in a tooltip after hovering on a truncated title in Bilibili's dynamic feed (t.bilibili.com). Features high-priority styling to avoid conflicts. // @author Smetona // @match https://t.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; // --- 全局变量与配置 --- /** @type {HTMLElement | null} 用于存储当前显示的Tooltip DOM元素 */ let tooltip = null; /** @type {number | undefined} 用于存储 setTimeout 的 ID,以便清除计时器 */ let tooltipTimer; let latestMouseEvent; // <--- 新增一个变量,用于存储最新的鼠标事件对象 /** * @constant {number} 鼠标悬停后延迟显示Tooltip的时间(单位:毫秒) * 用户可根据个人喜好修改此值 */ const HOVER_DELAY = 500; // --- 核心功能函数 --- /** * 创建并显示Tooltip * @param {string} text - 要在Tooltip中显示的完整标题文本 * @param {MouseEvent} e - 触发创建的 'mouseenter' 事件对象,用于获取初始鼠标位置 */ function createTooltip(text, e) { // 防御性检查:如果因为未知原因已有tooltip,先移除,确保页面上只有一个实例 const existingTooltip = document.getElementById('bili-dyn-tooltip-final'); if (existingTooltip) { existingTooltip.remove(); } tooltip = document.createElement('div'); tooltip.id = 'bili-dyn-tooltip-final'; // 设置唯一ID,便于识别和避免样式冲突 tooltip.textContent = text; // 应用高优先级的“堡垒”样式,确保在各种环境下都能正确显示 Object.assign(tooltip.style, { // 定位与布局 position: 'fixed', left: e.clientX + 10 + 'px', // 初始位置在鼠标右下方 top: e.clientY + 10 + 'px', maxWidth: '450px', // 外观样式 background: 'white', color: 'black', border: '1px solid #ccc', borderRadius: '4px', padding: '5px 10px', fontSize: '14px', lineHeight: '1.5', whiteSpace: 'normal', // 允许长文本自动换行 boxShadow: '0 2px 8px rgba(0,0,0,0.15)', // “铠甲”:确保最高优先级和可见性,对抗外部CSS冲突 zIndex: '2147483647', // 使用CSS理论上的最大值,确保永远在最顶层 pointerEvents: 'none', // 让鼠标可以“穿透”Tooltip,避免事件干扰 display: 'block', // 强制设为块级元素 visibility: 'visible', // 强制可见 opacity: '1' // 强制不透明 }); document.body.appendChild(tooltip); } /** * 从页面上移除当前的Tooltip */ function removeTooltip() { if (tooltip) { tooltip.remove(); tooltip = null; // 清理变量,方便垃圾回收 } } /** * 当鼠标在标题上移动时,更新Tooltip的位置 * @param {MouseEvent} e - 'mousemove' 事件对象 */ function moveTooltip(e) { latestMouseEvent = e; // <--- 在移动时,总是更新这个变量 if (tooltip) { tooltip.style.left = e.clientX + 10 + 'px'; tooltip.style.top = e.clientY + 10 + 'px'; } } /** * @description 这是本脚本的核心绑定逻辑函数。 * 它接收一个选择器,并为所有匹配该选择器的元素绑定悬停事件。 * @param {string} selector - 用于查询目标元素的CSS选择器 (例如 '.bili-dyn-card-video__title') * @param {string} boundFlag - 用于防止重复绑定的 dataset 标记名 (例如 'tipBoundTitle') */ function bindHoverForSelector(selector, boundFlag) { document.querySelectorAll(selector).forEach(el => { // 如果该元素已经绑定过事件,则跳过,提高效率 if (el.dataset[boundFlag]) return; el.dataset[boundFlag] = '1'; // 打上“已绑定”的标记 // 鼠标进入事件:启动延迟计时器 el.addEventListener('mouseenter', e => { const text = e.currentTarget.innerText.trim(); if (!text) return; // 如果标题为空,则不执行任何操作 latestMouseEvent = e; // <--- 进入时,先记录一次 tooltipTimer = setTimeout(() => { createTooltip(text, latestMouseEvent); }, HOVER_DELAY); }); // 鼠标移动事件:实时更新Tooltip位置 el.addEventListener('mousemove', moveTooltip); // 鼠标离开事件:清除计时器并移除Tooltip el.addEventListener('mouseleave', () => { clearTimeout(tooltipTimer); removeTooltip(); }); }); } /** * @description 一个统一的入口函数,用于调用所有需要绑定的元素的逻辑。 * 这样可以方便地在未来添加更多需要悬停提示的元素。 */ function initializeAllHovers() { // 为视频标题绑定悬停事件 bindHoverForSelector('.bili-dyn-card-video__title', 'tipBoundTitle'); // 为视频简介绑定悬停事件 bindHoverForSelector('.bili-dyn-card-video__desc', 'tipBoundDesc'); } // --- 脚本启动入口 --- // 1. 页面初次加载时,立即执行一次绑定 initializeAllHovers(); // 2. 创建一个 MutationObserver 来监听页面的动态变化(滚动加载) // 当页面内容变化时,再次调用绑定函数,确保新加载的内容也能生效 new MutationObserver(initializeAllHovers).observe(document.body, { childList: true, subtree: true }); })();