UNPKG

web-element-inspector

Version:

Web Element Inspector - 网页元素检查器,支持鼠标悬停高亮和实时查看元素信息

2 lines (1 loc) 16.1 kB
!function(){"use strict";class t{constructor(){this.state={isActive:!1,currentElement:null,floatingPanel:null,overlay:null,isDragging:!1,dragOffset:{x:0,y:0}},this.onDrag=t=>{if(!this.state.isDragging||!this.state.floatingPanel)return;const e=t.clientX-this.state.dragOffset.x,n=t.clientY-this.state.dragOffset.y,o=window.innerWidth-this.state.floatingPanel.offsetWidth,s=window.innerHeight-this.state.floatingPanel.offsetHeight,i=Math.max(0,Math.min(e,o)),a=Math.max(0,Math.min(n,s));this.state.floatingPanel.style.left=`${i}px`,this.state.floatingPanel.style.top=`${a}px`,this.state.floatingPanel.style.right="auto"},this.stopDrag=()=>{this.state.isDragging=!1,document.removeEventListener("mousemove",this.onDrag),document.removeEventListener("mouseup",this.stopDrag),this.state.floatingPanel&&(this.state.floatingPanel.style.cursor="default")},this.init()}init(){this.state.isActive?console.log("调试工具已激活"):(this.createOverlay(),this.createFloatingPanel(),this.attachEventListeners(),this.state.isActive=!0,console.log("✅ 调试工具已启动"))}createOverlay(){const t=document.createElement("div");t.id="debug-tool-overlay",t.style.cssText="\n position: absolute;\n pointer-events: none;\n z-index: 999998;\n background: rgba(66, 153, 225, 0.3);\n border: 2px solid #4299e1;\n transition: all 0.1s ease;\n display: none;\n ",document.body.appendChild(t),this.state.overlay=t}createFloatingPanel(){const t=document.createElement("div");t.id="debug-tool-panel",t.style.cssText="\n position: fixed;\n top: 10px;\n right: 10px;\n width: 340px;\n max-height: calc(100vh - 20px);\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n padding: 0;\n border-radius: 12px;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 13px;\n line-height: 1.6;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n ",t.innerHTML='\n <div id="debug-tool-header" style="\n padding: 12px 16px;\n cursor: move;\n background: rgba(255, 255, 255, 0.1);\n border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n display: flex;\n justify-content: space-between;\n align-items: center;\n ">\n <div style="font-weight: 600; font-size: 14px;">🔍 元素调试工具</div>\n <button id="debug-tool-close" style="\n background: rgba(255, 255, 255, 0.2);\n border: none;\n color: white;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n cursor: pointer;\n font-size: 18px;\n line-height: 24px;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n ">×</button>\n </div>\n <div id="debug-tool-content" style="padding: 16px; overflow-y: auto; flex: 1;">\n <div style="text-align: center; color: rgba(255, 255, 255, 0.7); padding: 20px 0;">\n 移动鼠标到元素上查看信息\n </div>\n </div>\n ',document.body.appendChild(t),this.state.floatingPanel=t;const e=t.querySelector("#debug-tool-close");e&&(e.addEventListener("mouseenter",()=>{e.style.background="rgba(255, 255, 255, 0.3)"}),e.addEventListener("mouseleave",()=>{e.style.background="rgba(255, 255, 255, 0.2)"}),e.addEventListener("click",t=>{t.stopPropagation(),this.destroy()}));const n=t.querySelector("#debug-tool-header");n&&n.addEventListener("mousedown",t=>this.startDrag(t))}startDrag(t){if(!this.state.floatingPanel)return;this.state.isDragging=!0;const e=this.state.floatingPanel.getBoundingClientRect();this.state.dragOffset={x:t.clientX-e.left,y:t.clientY-e.top},document.addEventListener("mousemove",this.onDrag),document.addEventListener("mouseup",this.stopDrag),this.state.floatingPanel&&(this.state.floatingPanel.style.cursor="grabbing")}attachEventListeners(){document.addEventListener("mousemove",t=>this.onMouseMove(t)),document.addEventListener("scroll",()=>this.updateOverlay(),!0),window.addEventListener("resize",()=>this.updateOverlay())}onMouseMove(t){if(this.state.isDragging)return;let e=t.target;if(t.composedPath&&t.composedPath().length>0){const n=t.composedPath()[0];n&&n.nodeType===Node.ELEMENT_NODE&&(e=n)}this.isDebugToolElement(e)?this.hideOverlay():(this.state.currentElement=e,this.updateOverlay(),this.updatePanelContent(e,t))}isDebugToolElement(t){return!!t&&("debug-tool-panel"===t.id||"debug-tool-overlay"===t.id||null!==t.closest("#debug-tool-panel"))}safeGetProperty(t,e){try{return t()}catch(n){return e}}isWebComponent(t){return this.safeGetProperty(()=>{var e;return null==(e=t.tagName)?void 0:e.toLowerCase()},"").includes("-")}isInsideShadowDOM(t){return this.safeGetProperty(()=>{var e;let n=t.parentNode;for(;n;){if(n.nodeType===Node.DOCUMENT_FRAGMENT_NODE){const t=n.host;return{inside:!0,host:t,hostTagName:(null==(e=t.tagName)?void 0:e.toLowerCase())||"unknown"}}n=n.parentNode}return{inside:!1}},{inside:!1})}getWebComponentInfo(t){const e=this.safeGetProperty(()=>null!==t.shadowRoot&&void 0!==t.shadowRoot,!1),n=this.safeGetProperty(()=>{if(e)return t.shadowRoot.mode||"unknown"},void 0),o=[];return this.safeGetProperty(()=>(t.attributes&&Array.from(t.attributes).forEach(t=>{(t.name.startsWith("data-")||t.name.startsWith("config-")||t.name.includes("-"))&&o.push({name:t.name,value:t.value.length>50?t.value.substring(0,50)+"...":t.value})}),!0),!1),{hasShadowRoot:e,shadowMode:n,customAttributes:o}}updateOverlay(){if(this.state.overlay&&this.state.currentElement)try{const t=this.state.currentElement.getBoundingClientRect(),e=window.pageYOffset||document.documentElement.scrollTop,n=window.pageXOffset||document.documentElement.scrollLeft;this.state.overlay.style.display="block",this.state.overlay.style.top=`${t.top+e}px`,this.state.overlay.style.left=`${t.left+n}px`,this.state.overlay.style.width=`${t.width}px`,this.state.overlay.style.height=`${t.height}px`}catch(t){this.state.overlay.style.display="none"}}hideOverlay(){this.state.overlay&&(this.state.overlay.style.display="none")}updatePanelContent(t,e){if(!this.state.floatingPanel)return;const n=this.state.floatingPanel.querySelector("#debug-tool-content");if(!n)return;const o=this.safeGetProperty(()=>{var e;return null==(e=t.tagName)?void 0:e.toLowerCase()},"unknown"),s=this.safeGetProperty(()=>t.getBoundingClientRect(),{width:0,height:0,left:0,top:0}),i=this.safeGetProperty(()=>window.getComputedStyle(t),null),a=this.getElementPath(t),r=this.safeGetProperty(()=>Array.from(t.classList).join(", ")||"无","无法读取"),d=this.safeGetProperty(()=>t.id||"无","无法读取"),l=this.safeGetProperty(()=>{var e;return(null==(e=t.textContent)?void 0:e.trim())||""},""),p=l.length>0,g=l.length>200?l.substring(0,200)+"...":l,h=this.safeGetProperty(()=>null==i?void 0:i.color,"无法读取"),c=this.safeGetProperty(()=>null==i?void 0:i.backgroundColor,"无法读取"),f=this.safeGetProperty(()=>null==i?void 0:i.fontSize,"无法读取"),y=this.safeGetProperty(()=>null==i?void 0:i.display,"无法读取"),u=this.isWebComponent(t),v=u?this.getWebComponentInfo(t):null,m=this.isInsideShadowDOM(t);n.innerHTML=`\n <div style="margin-bottom: 12px;">\n <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px; display: flex; align-items: center;">\n <span style="margin-right: 8px;">📍</span>\n <span>鼠标位置</span>\n </div>\n <div style="background: rgba(255, 255, 255, 0.1); padding: 8px 12px; border-radius: 6px; font-family: 'Courier New', monospace;">\n <div>X: <span style="color: #ffd700;">${e.clientX}px</span></div>\n <div>Y: <span style="color: #ffd700;">${e.clientY}px</span></div>\n </div>\n </div>\n\n <div style="margin-bottom: 12px;">\n <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px; display: flex; align-items: center;">\n <span style="margin-right: 8px;">🏷️</span>\n <span>元素标签</span>\n </div>\n <div style="background: rgba(255, 255, 255, 0.1); padding: 8px 12px; border-radius: 6px;">\n <code style="color: #ffd700; font-weight: 600;">&lt;${o}&gt;</code>\n ${u?'<span style="margin-left: 8px; padding: 2px 8px; background: rgba(255, 165, 0, 0.3); border-radius: 4px; font-size: 10px; color: #ffa500;">Web Component</span>':""}\n ${m.inside?'<span style="margin-left: 8px; padding: 2px 8px; background: rgba(138, 43, 226, 0.3); border-radius: 4px; font-size: 10px; color: #ba55d3;">在 Shadow DOM 内</span>':""}\n </div>\n ${m.inside&&m.hostTagName?`\n <div style="margin-top: 6px; padding: 6px 12px; background: rgba(138, 43, 226, 0.1); border-radius: 4px; font-size: 11px;">\n <span style="opacity: 0.8;">Shadow Host:</span> \n <code style="color: #ba55d3; margin-left: 4px;">&lt;${m.hostTagName}&gt;</code>\n </div>\n `:""}\n </div>\n\n ${p?`\n <div style="margin-bottom: 12px;">\n <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px; display: flex; align-items: center;">\n <span style="margin-right: 8px;">📝</span>\n <span>文本内容</span>\n </div>\n <div style="background: rgba(255, 255, 255, 0.1); padding: 8px 12px; border-radius: 6px; word-break: break-word; max-height: 120px; overflow-y: auto;">\n <div style="color: #ffeaa7; line-height: 1.6;">${g.replace(/</g,"&lt;").replace(/>/g,"&gt;")}</div>\n </div>\n </div>\n `:""}\n\n ${"无"!==d?`\n <div style="margin-bottom: 12px;">\n <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px; display: flex; align-items: center;">\n <span style="margin-right: 8px;">🆔</span>\n <span>ID</span>\n </div>\n <div style="background: rgba(255, 255, 255, 0.1); padding: 8px 12px; border-radius: 6px;">\n <code style="color: #98fb98;">#${d}</code>\n </div>\n </div>\n `:""}\n\n ${"无"!==r?`\n <div style="margin-bottom: 12px;">\n <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px; display: flex; align-items: center;">\n <span style="margin-right: 8px;">🎨</span>\n <span>类名</span>\n </div>\n <div style="background: rgba(255, 255, 255, 0.1); padding: 8px 12px; border-radius: 6px; word-break: break-all;">\n <code style="color: #87ceeb;">${r}</code>\n </div>\n </div>\n `:""}\n\n <div style="margin-bottom: 12px;">\n <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px; display: flex; align-items: center;">\n <span style="margin-right: 8px;">📐</span>\n <span>尺寸 & 位置</span>\n </div>\n <div style="background: rgba(255, 255, 255, 0.1); padding: 8px 12px; border-radius: 6px; font-family: 'Courier New', monospace; font-size: 12px;">\n <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px;">\n <div>宽度: <span style="color: #ffd700;">${Math.round(s.width)}px</span></div>\n <div>高度: <span style="color: #ffd700;">${Math.round(s.height)}px</span></div>\n <div>左: <span style="color: #ffd700;">${Math.round(s.left)}px</span></div>\n <div>上: <span style="color: #ffd700;">${Math.round(s.top)}px</span></div>\n </div>\n </div>\n </div>\n\n <div style="margin-bottom: 12px;">\n <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px; display: flex; align-items: center;">\n <span style="margin-right: 8px;">🎭</span>\n <span>样式</span>\n </div>\n <div style="background: rgba(255, 255, 255, 0.1); padding: 8px 12px; border-radius: 6px; font-family: 'Courier New', monospace; font-size: 11px;">\n <div>颜色: <span style="color: #ffd700;">${h}</span></div>\n <div>背景: <span style="color: #ffd700;">${c}</span></div>\n <div>字体: <span style="color: #ffd700;">${f}</span></div>\n <div>显示: <span style="color: #ffd700;">${y}</span></div>\n </div>\n </div>\n\n ${u&&v?`\n <div style="margin-bottom: 12px;">\n <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px; display: flex; align-items: center;">\n <span style="margin-right: 8px;">🔧</span>\n <span>Web Component 信息</span>\n </div>\n <div style="background: rgba(255, 165, 0, 0.1); padding: 8px 12px; border-radius: 6px; font-family: 'Courier New', monospace; font-size: 11px;">\n <div>Shadow DOM: <span style="color: #ffd700;">${v.hasShadowRoot?"✓ 存在":"✗ 不存在"}</span></div>\n ${v.hasShadowRoot&&v.shadowMode?`<div>Shadow Mode: <span style="color: #ffd700;">${v.shadowMode}</span></div>`:""}\n ${v.customAttributes.length>0?`\n <div style="margin-top: 6px; padding-top: 6px; border-top: 1px solid rgba(255,255,255,0.1);">\n <div style="margin-bottom: 4px; opacity: 0.8;">自定义属性:</div>\n ${v.customAttributes.slice(0,5).map(t=>`<div style="margin-left: 8px; font-size: 10px; line-height: 1.6;">\n <span style="color: #87ceeb;">${t.name}</span>: \n <span style="color: #98fb98;">"${t.value}"</span>\n </div>`).join("")}\n ${v.customAttributes.length>5?`<div style="margin-left: 8px; font-size: 10px; opacity: 0.6;">...还有 ${v.customAttributes.length-5} 个属性</div>`:""}\n </div>\n `:""}\n </div>\n </div>\n `:""}\n\n <div>\n <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px; display: flex; align-items: center;">\n <span style="margin-right: 8px;">🗂️</span>\n <span>DOM 路径</span>\n </div>\n <div style="background: rgba(255, 255, 255, 0.1); padding: 8px 12px; border-radius: 6px; font-family: 'Courier New', monospace; font-size: 11px; word-break: break-all; line-height: 1.8;">\n ${a}\n </div>\n </div>\n `}getElementPath(t){const e=[];let n=t;try{let t=0;const o=30;for(;n&&t<o;){let o=this.safeGetProperty(()=>{var t;return null==(t=n.tagName)?void 0:t.toLowerCase()},"unknown");const s=this.safeGetProperty(()=>n.id,"");if(s)o+=`<span style="color: #98fb98;">#${s}</span>`;else{const t=this.safeGetProperty(()=>n.classList?Array.from(n.classList).slice(0,2).join("."):"","");t&&(o+=`<span style="color: #87ceeb;">.${t}</span>`)}e.unshift(o);const i=this.safeGetProperty(()=>n.parentNode,null);if(i&&i.nodeType===Node.DOCUMENT_FRAGMENT_NODE)e.unshift('<span style="color: #ffa500; font-weight: bold;">#shadow-root</span>'),n=this.safeGetProperty(()=>i.host,null);else{if(!i||i.nodeType!==Node.ELEMENT_NODE){if(n===document.body||n===document.documentElement)break;break}n=i}t++}return 0===e.length?'<span style="color: rgba(255,255,255,0.5);">无法获取路径</span>':e.join(' <span style="color: rgba(255,255,255,0.5);">›</span> ')}catch(o){return'<span style="color: rgba(255,255,255,0.5);">路径获取异常</span>'}}destroy(){this.state.overlay&&this.state.overlay.remove(),this.state.floatingPanel&&this.state.floatingPanel.remove(),document.removeEventListener("mousemove",this.onDrag),document.removeEventListener("mouseup",this.stopDrag),this.state.isActive=!1,console.log("❌ 调试工具已关闭")}}(()=>{window.__elementInspector&&window.__elementInspector.destroy();const e=new t;window.__elementInspector=e,console.log("💡 提示:使用 __elementInspector.destroy() 可以关闭元素检查器")})()}();