您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
JS实现获取CSS选择器,方便开发者使用
当前为
// ==UserScript== // @name 获取CSS选择器 // @description JS实现获取CSS选择器,方便开发者使用 // @version 1.0 // @namespace https://space.bilibili.com/482343 // @author 古海沉舟 // @license 古海沉舟 // @include ** // @noframes // @grant GM_setClipboard // ==/UserScript== (function () { 'use strict'; var ancestor; const state = { active: false, elementA: null, elementB: null, masks: [], mousePos: { x: -1, y: -1 } }; function init() { injectStyles(); createMasks(); setupEventListeners(); } function setupEventListeners() { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('keydown', handleKeyPress); document.addEventListener('click', handleActivationClick, true); } function handleMouseMove(e) { state.mousePos = { x: e.clientX, y: e.clientY }; if (state.active) updateMasks(); } function handleKeyPress(e) { if (parseKeyCombo(e) === 'C-A-s') { if (!state.active) { startSelection(); } else { completeSelectionWithCurrent(); } e.preventDefault(); } } function handleActivationClick(e) { if (state.active) { e.preventDefault(); e.stopImmediatePropagation(); completeSelectionWithCurrent(); } } function generateSelector(elem, iss = 0) { const { tagName, id, className, parentNode } = elem; if (tagName === 'HTML') return 'html'; let str = tagName.toLowerCase(); const isDescendant = (!ancestor) || (!ancestor.contains(elem)) || ancestor==elem; if (id) { if (iss == 0) { str += `#${id}`; return str; } else if (iss == 1 && isDescendant) { str += `#${id}`; return str; } } if (className) { const classes = className.split(/\s+/).filter(c => c); if (classes.length > 0) { // 初始包含所有类名 str += classes.map(c => `.${c}`).join(''); // 尝试精简类名 let canOptimize = true; while (canOptimize) { canOptimize = false; // 遍历每个现有类名 for (let i = 0; i < classes.length; i++) { if (!classes[i]) continue; // 跳过已删除项 // 构建测试选择器(移除当前类) const testSelector = `${tagName.toLowerCase()}${classes .filter((_, idx) => idx != i) .map(c => `.${c}`) .join('')}`; // 检查唯一性 let matchCount = 0; for (const child of parentNode.children) { if (child.matches(testSelector)) matchCount++; } // 如果移除后仍唯一 if (matchCount == 1) { classes.splice(i, 1); // 永久删除该类 str = testSelector; // 更新当前选择器 canOptimize = true; // 允许继续优化 break; // 重新遍历修改后的列表 } } } } } // 检查处理后的选择器在父元素下的匹配数量 let matchCount = 0; for (const child of parentNode.children) { if (child.matches(str)) { matchCount++; } } // 如果仍然多个匹配,添加:nth-child if (matchCount > 1) { let childIndex = 1; for (let e = elem; e.previousElementSibling; e = e.previousElementSibling) { childIndex++; } str += `:nth-child(${childIndex})`; } return `${generateSelector(parentNode,iss)} > ${str}`; } function startSelection() { const initialElement = getCurrentElement(); if (initialElement) { state.active = true; state.elementA = initialElement; showMasks(); } } function completeSelectionWithCurrent() { const currentElement = getCurrentElement(); if (currentElement) { if (!state.elementA) { state.elementA = currentElement; log('设置元素A:', currentElement); } else { state.elementB = currentElement; processResult(); cleanup(); } } } function createMasks() { state.masks = Array(2).fill().map((_, i) => { const mask = document.createElement('div'); mask.className = `ancestor-mask ${i ? 'active' : ''}`; document.body.appendChild(mask); return mask; }); } function updateMasks() { state.masks.forEach((mask, i) => { const target = i === 0 ? state.elementA : getCurrentElement(); updateMask(mask, target); }); } function updateMask(mask, element) { if (!element || element === document.body) { mask.style.display = 'none'; return; } const rect = getVisibleRect(element); Object.assign(mask.style, { top: `${rect.top}px`, left: `${rect.left}px`, width: `${rect.width}px`, height: `${rect.height}px`, display: 'block' }); } function getCurrentElement() { let element; try { element = document.elementFromPoint( state.mousePos.x, state.mousePos.y ); while (element && element.classList.contains('ancestor-mask')) { element = document.elementFromPoint( state.mousePos.x, state.mousePos.y ); } } catch (e) { return null; } return element && element !== document.body ? element : null; } function processResult() { ancestor = findCommonAncestor(state.elementA, state.elementB); if (!ancestor || ancestor === document.body) { log('错误:未找到有效公共祖先'); return; } //const selector = generateSelector(ancestor); const selectorA = generateSelector(state.elementA, 1); const selectorB = generateSelector(state.elementB, 1); const [diffA, diffB,selector] = compareSelectors(selectorA, selectorB); const validation = validateSelector(selector, ancestor); const diffAnalysis = [ "选择器:", `祖: ${selector || '<无相同>'}`, `A: ${diffA || '<无差异>'}`, `B: ${diffB || '<无差异>'}` ].join('\n'); console.group('🔍 分析结果'); log('元素A:', state.elementA); log('A 选择器:', selectorA); log('元素B:', state.elementB); log('B 选择器:', selectorB); log('公共祖先:', ancestor); log(diffAnalysis); log('验证结果:', validation.message); if (validation.success) { GM_setClipboard(selector, { type: 'text', mimetype: 'text/plain' }); } console.groupEnd(); } function findCommonAncestor(a, b) { const getPath = el => { const path = []; while (el && el !== document.body) { path.push(el); el = el.parentElement; } return path; }; const pathA = getPath(a); return pathA.find(node => node.contains(b)) || document.body; } function compareSelectors(selectorA, selectorB) { const partsA = selectorA.split('>').map(p => p.trim()); const partsB = selectorB.split('>').map(p => p.trim()); let maxCommonLength = 0; const minLength = Math.min(partsA.length, partsB.length); // 计算最大公共前缀长度 while (maxCommonLength < minLength && partsA[maxCommonLength] === partsB[maxCommonLength]) { maxCommonLength++; } // 特殊处理完全匹配的多级选择器 if (maxCommonLength === partsA.length && maxCommonLength === partsB.length && maxCommonLength > 0) { maxCommonLength--; } // 提取公共部分和差异部分 const commonPart = partsA.slice(0, maxCommonLength).join(' > '); const splitIndex = maxCommonLength; const getDiff = (arr) => { return splitIndex < arr.length ? arr.slice(splitIndex).join(' > ') : ''; }; const diffA = getDiff(partsA); const diffB = getDiff(partsB); // 特殊处理单级完全匹配 if (partsA.length === 1 && partsB.length === 1 && diffA === diffB) { return [diffA, diffB, '']; } return [diffA, diffB, commonPart]; } function validateSelector(selector, expected) { try { const found = document.querySelector(selector); return { success: found === expected, element: found, message: found === expected ? '✅ 选择器验证通过' : `❌ 匹配到其他元素: ${found?.outerHTML?.slice(0, 100)}...` }; } catch (e) { return { success: false, message: `❌ 无效选择器: ${e.message}` }; } } function getVisibleRect(el) { const rect = el.getBoundingClientRect(); return { top: rect.top, left: rect.left, width: rect.width, height: rect.height }; } function injectStyles() { const style = document.createElement('style'); style.textContent = ` .ancestor-mask { position: fixed; pointer-events: none; background: rgba(110, 180, 255, 0.2); border: 2px solid #1a73e8; z-index: 2147483647; transition: all 0.15s ease-out; display: none; box-shadow: 0 0 8px rgba(0,0,0,0.1); } .ancestor-mask.active { background: rgba(255, 80, 80, 0.2); border-color: #e53935; } .ancestor-mask.visible { display: block !important; } `; document.head.appendChild(style); } function parseKeyCombo(e) { return [ e.ctrlKey ? 'C-' : '', e.altKey ? 'A-' : '', e.shiftKey ? 'S-' : '', e.key.toLowerCase() ].join(''); } function showMasks() { state.masks.forEach(mask => mask.classList.add('visible')); updateMasks(); } function cleanup() { state.active = false; state.elementA = null; state.elementB = null; hideMasks(); } function hideMasks() { state.masks.forEach(mask => { mask.style.display = 'none'; mask.classList.remove('visible'); }); } function log(...args) { if (true) console.log(...args); } init(); })();