Enhanced Button Info Display
// ==UserScript==
// @name Enhanced Button Info Display
// @namespace http://tampermonkey.net/
// @version 1.4
// @description Show button and custom component info in a draggable floating window with enhanced naming accuracy and mouse position coordinates.
// @author You
// @match *://*/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
let lastClickInfo = {
elementOuterHTML: '???',
customComponentHTML: '???',
elementType: '???',
elementClassName: '???',
elementTitle: '???',
elementID: '???',
isLatest: false,
customComponentName: '???'
};
let hoverInfo = {
elementClassName: '???',
elementTitle: '???',
isHovering: false,
customComponentName: '???',
mouseX: 0,
mouseY: 0
};
window.addEventListener('load', function() {
const floatingWindow = document.createElement('div');
floatingWindow.id = 'button-info-display';
floatingWindow.style.cssText = `
position: fixed;
top: 10%;
left: 10%;
width: 80%;
height: 80%;
background: #ADD8E6;
border: 1px solid #000;
padding: 10px;
overflow: auto;
z-index: 10000;
display: none; /* 初始时隐藏悬浮窗 */
`;
document.body.appendChild(floatingWindow);
const titleBar = document.createElement('div');
titleBar.style.cssText = `
background: #000;
color: #fff;
padding: 5px;
cursor: move;
`;
titleBar.textContent = 'Button Info Logger';
floatingWindow.appendChild(titleBar);
let isDragging = false;
let offsetX, offsetY;
titleBar.addEventListener('mousedown', (e) => {
isDragging = true;
offsetX = e.clientX - floatingWindow.getBoundingClientRect().left;
offsetY = e.clientY - floatingWindow.getBoundingClientRect().top;
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
floatingWindow.style.left = (e.clientX - offsetX) + 'px';
floatingWindow.style.top = (e.clientY - offsetY) + 'px';
}
// 更新鼠标坐标
hoverInfo.mouseX = e.clientX;
hoverInfo.mouseY = e.clientY;
updateFloatingWindow();
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
const toggleButton = document.createElement('button');
toggleButton.textContent = 'Toggle Button Info';
toggleButton.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
z-index: 10001;
`;
toggleButton.addEventListener('click', () => {
floatingWindow.style.display = (floatingWindow.style.display === 'none')? 'block' : 'none';
});
document.body.appendChild(toggleButton);
// 鼠标悬停时更新信息
document.body.addEventListener('mouseover', (event) => {
const target = event.target;
if (target) {
updateHoverInfo(target);
hoverInfo.isHovering = true;
updateFloatingWindow();
}
});
// 鼠标离开时清除悬停信息
document.body.addEventListener('mouseout', (event) => {
const target = event.target;
if (target) {
hoverInfo.elementClassName = '???';
hoverInfo.elementTitle = '???';
hoverInfo.customComponentName = '???';
hoverInfo.isHovering = false;
updateFloatingWindow();
}
});
// 处理点击事件
document.body.addEventListener('click', (event) => {
const target = event.target;
if (target) {
lastClickInfo.elementOuterHTML = target.outerHTML || '???';
lastClickInfo.elementClassName = target.getAttribute('class') || '???';
lastClickInfo.elementTitle = target.getAttribute('title') || '???';
lastClickInfo.elementID = target.id || '???';
lastClickInfo.elementType = target.tagName + (target.className? '.' + target.className.split(' ').join('.') : '???');
// 分析类名并确定自定义组件类型
analyzeButtonClassName(target);
lastClickInfo.isLatest = true; // 标记为最新信息
updateFloatingWindow();
}
});
// 分析类名并确定按钮类型
function analyzeButtonClassName(target) {
const className = target.getAttribute('class');
const type = target.getAttribute('type');
const ariaLabel = target.getAttribute('aria-label');
const title = target.getAttribute('title');
if (className) {
const classList = className.split(' ');
if (classList.includes('button')) {
lastClickInfo.customComponentName = '基础按钮';
} else if (classList.includes('btn')) {
lastClickInfo.customComponentName = '框架通用按钮(如 Bootstrap 中的)';
} else if (classList.includes('ui button')) {
lastClickInfo.customComponentName = 'Semantic UI 中的按钮';
} else if (classList.includes('button is-primary')) {
lastClickInfo.customComponentName = 'Bulma 中的主要按钮';
} else if (classList.includes('active')) {
lastClickInfo.customComponentName = '处于激活状态的按钮';
} else if (classList.includes('focus')) {
lastClickInfo.customComponentName = '获得焦点的按钮';
} else if (classList.includes('hover')) {
lastClickInfo.customComponentName = '鼠标悬停的按钮';
} else if (classList.includes('visited')) {
lastClickInfo.customComponentName = '已访问过的按钮';
} else if (classList.includes('small')) {
lastClickInfo.customComponentName = '小尺寸按钮';
} else if (classList.includes('medium')) {
lastClickInfo.customComponentName = '中尺寸按钮';
} else if (classList.includes('large')) {
lastClickInfo.customComponentName = '大尺寸按钮';
} else if (classList.includes('btn-sm')) {
lastClickInfo.customComponentName = 'Bootstrap 中的小号按钮';
} else if (classList.includes('btn-lg')) {
lastClickInfo.customComponentName = 'Bootstrap 中的大号按钮';
} else if (classList.includes('outline')) {
lastClickInfo.customComponentName = '边框样式按钮';
} else if (classList.includes('rounded')) {
lastClickInfo.customComponentName = '圆角按钮';
} else if (classList.includes('circle')) {
lastClickInfo.customComponentName = '圆形按钮';
} else if (classList.includes('block')) {
lastClickInfo.customComponentName = '占据整个父容器宽度的按钮';
} else if (classList.includes('ghost')) {
lastClickInfo.customComponentName = '半透明或无背景色的按钮';
} else if (classList.includes('fade-in')) {
lastClickInfo.customComponentName = '具有淡入动画效果的按钮';
} else if (classList.includes('slide-up')) {
lastClickInfo.customComponentName = '具有向上滑动动画效果的按钮';
} else if (classList.includes('animated')) {
lastClickInfo.customComponentName = '结合动画库的动画按钮';
} else {
for (const cls of classList) {
if (cls.startsWith('custom-') || cls.startsWith('my-') || cls.startsWith('action-')) {
lastClickInfo.customComponentName = '自定义按钮';
break;
}
}
if (lastClickInfo.customComponentName === '???') {
lastClickInfo.customComponentName = '未知类型的按钮';
}
}
} else if (type ==='submit') {
lastClickInfo.customComponentName = '用于提交表单的按钮';
} else if (type ==='reset') {
lastClickInfo.customComponentName = '用于重置表单的按钮';
} else if (type === 'button') {
lastClickInfo.customComponentName = '自定义功能的按钮';
} else if (ariaLabel || title) {
lastClickInfo.customComponentName = '具有辅助信息的按钮';
} else {
lastClickInfo.customComponentName = '未知类型的按钮';
}
}
// 更新悬浮窗
function updateFloatingWindow() {
requestAnimationFrame(() => {
floatingWindow.innerHTML = ''; // 清空内容
const infoHeader = document.createElement('h4');
infoHeader.textContent = 'Last Click Info:';
floatingWindow.appendChild(infoHeader);
const details = `
上次触发点击事件时的元素对象:${lastClickInfo.elementOuterHTML}
上次鼠标点击时所触发的自定义组件:${lastClickInfo.customComponentHTML}
上次鼠标点击时的元素对象/自定义组件的名称:${lastClickInfo.elementType} (${lastClickInfo.customComponentName})
上次鼠标点击时的元素对象/自定义组件的相关代码:${lastClickInfo.elementOuterHTML}
上次鼠标点击时的元素对象/自定义组件的类名:${lastClickInfo.elementClassName}
上次鼠标点击时的元素对象/自定义组件的标题:${lastClickInfo.elementTitle}
上次鼠标点击时的元素对象/自定义组件的ID:${lastClickInfo.elementID}
上次鼠标点击信息是否完成显示:${lastClickInfo.isLatest? 'YES' : 'NO'}
`;
const infoElement = document.createElement('pre');
infoElement.textContent = details;
floatingWindow.appendChild(infoElement);
// 显示光标悬停信息
const hoverInfoHeader = document.createElement('h4');
hoverInfoHeader.textContent = 'Hover Info:';
floatingWindow.appendChild(hoverInfoHeader);
const hoverDetails = `
当前悬停按钮的类名:${hoverInfo.elementClassName}
当前悬停按钮的标题:${hoverInfo.elementTitle}
当前悬停组件名称:${hoverInfo.customComponentName}
鼠标悬停所在按钮信息:${hoverInfo.isHovering? 'YES' : 'NO'}
鼠标位置:(${hoverInfo.mouseX}, ${hoverInfo.mouseY}) // 显示鼠标位置
`;
const hoverElement = document.createElement('pre');
hoverElement.textContent = hoverDetails;
floatingWindow.appendChild(hoverElement);
// 状态与进度条
const statusDetails = `
上次鼠标点击时信息:${lastClickInfo.isLatest? 'YES' : 'NO'}
鼠标悬停所在按钮信息:${hoverInfo.isHovering? 'YES' : 'NO'}
`;
const statusElement = document.createElement('pre');
statusElement.textContent = statusDetails;
floatingWindow.appendChild(statusElement);
// 进度信息
let clickProgress = lastClickInfo.isLatest? '100%' : '50%';
const hoverProgress = hoverInfo.isHovering? '100%' : '未悬停在可触发点击事件的区域';
const progressDetails = `
上次点击信息:${clickProgress}
鼠标悬停信息:${hoverProgress}
`;
const progressElement = document.createElement('pre');
progressElement.textContent = progressDetails;
floatingWindow.appendChild(progressElement);
});
}
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.tagName === 'BUTTON' || node.getAttribute('role') === 'button') {
node.addEventListener('click', function () {
lastClickInfo.elementOuterHTML = this.outerHTML || '???';
lastClickInfo.elementClassName = this.getAttribute('class') || '???';
lastClickInfo.elementTitle = this.getAttribute('title') || '???';
lastClickInfo.elementID = this.id || '???';
lastClickInfo.elementType = this.tagName + (this.className? '.' + this.className.split(' ').join('.') : '???');
// 分析类名并确定自定义组件信息
analyzeButtonClassName(this);
lastClickInfo.isLatest = true;
updateFloatingWindow();
});
} else if (node.tagName === 'G' && node.classList.contains('Z')) {
lastClickInfo.elementOuterHTML = node.outerHTML || '???';
lastClickInfo.elementClassName = node.getAttribute('class') || '???';
lastClickInfo.elementTitle = node.getAttribute('title') || '???';
lastClickInfo.elementID = node.id || '???';
lastClickInfo.elementType = node.tagName + (node.className? '.' + node.className.split(' ').join('.') : '???');
// 分析类名并确定自定义组件信息
analyzeButtonClassName(node);
lastClickInfo.isLatest = true;
updateFloatingWindow();
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
});
})();