您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Click: Main, Alt+Click: Addition, Ctrl+Click: Excluded. Supports multiple selections per type & strict relationship rules.
// ==UserScript== // @name Element Selector Inspector - Advanced (Multiple Selections & Strict Relationships) // @namespace http://tampermonkey.net/ // @version 3.8 // @description Click: Main, Alt+Click: Addition, Ctrl+Click: Excluded. Supports multiple selections per type & strict relationship rules. // @author lmdw // @match *://*/* // @grant none // ==/UserScript== (function() { 'use strict'; // --- Tùy chỉnh --- let activated = false; const toggleKey = { ctrl: true, alt: true, key: '1' }; // Phím tắt để bật/tắt (Ctrl+Alt+1) const overlayPadding = 4; // Khoảng cách giữa overlay và phần tử const hoverColor = 'rgba(255,223,0,0.8)'; // Màu khi di chuột const mainColor = 'rgba(255,165,0,1)'; // Màu phần tử chính (vàng cam) const additionColor = 'rgba(0,255,0,1)'; // Màu phần tử bổ sung (xanh lá) const excludeColor = 'rgba(255,0,0,1)'; // Màu phần tử loại trừ (đỏ) const infoLabelBackground = 'rgba(255,223,0,0.95)'; // Màu nền của info label // --- Biến toàn cục (Lưu trữ các Set phần tử đã chọn) --- let hoverOverlay, infoLabel; let mainElements = new Set(); // Các phần tử chính let additionElements = new Set(); // Các phần tử bổ sung let excludedElements = new Set(); // Các phần tử loại trừ // --- Ánh xạ phần tử sang overlay --- // Sử dụng WeakMap để tránh rò rỉ bộ nhớ nếu phần tử bị xóa khỏi DOM const elementOverlayMap = new WeakMap(); const elementInfoMap = new WeakMap(); // Có thể không cần thiết nếu infoLabel chỉ hiển thị cho hover // --- Hàm tạo overlay chung --- function createOverlays() { if (!hoverOverlay) { hoverOverlay = document.createElement('div'); // Style chung cho hover overlay Object.assign(hoverOverlay.style, { position: 'absolute', borderRadius: '4px', pointerEvents: 'none', zIndex: 2147483646, display: 'none', boxSizing: 'border-box', border: `2px solid ${hoverColor}`, background: hoverColor.replace(/[^,]+(?=\))/, '0.1') }); document.body.appendChild(hoverOverlay); } if (!infoLabel) { infoLabel = document.createElement('div'); // Style cho info label Object.assign(infoLabel.style, { position: 'absolute', padding: '2px 6px', background: infoLabelBackground, color: '#000', fontSize: '12px', fontFamily: 'Arial', borderRadius: '4px', pointerEvents: 'none', zIndex: 2147483647, whiteSpace: 'nowrap', display: 'none' }); document.body.appendChild(infoLabel); } } // --- Hàm cập nhật vị trí và kích thước overlay --- function updateOverlay(overlay, el) { try { const rect = el.getBoundingClientRect(); // Kiểm tra nếu phần tử không còn trong viewport hoặc có kích thước không hợp lệ if (rect.width === 0 || rect.height === 0 || rect.right < 0 || rect.bottom < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight) { overlay.style.display = 'none'; // Ẩn overlay nếu phần tử không hiển thị return false; // Báo hiệu không cập nhật thành công } overlay.style.top = `${rect.top + window.scrollY - overlayPadding}px`; overlay.style.left = `${rect.left + window.scrollX - overlayPadding}px`; overlay.style.width = `${rect.width + overlayPadding * 2}px`; overlay.style.height = `${rect.height + overlayPadding * 2}px`; overlay.style.display = 'block'; return true; // Báo hiệu cập nhật thành công } catch (e) { // Xử lý lỗi nếu getBoundingClientRect thất bại (ví dụ: phần tử bị display: none đột ngột) overlay.style.display = 'none'; console.warn("Error updating overlay for element:", el, e); return false; } } // --- Hàm cập nhật thông tin trên label --- function updateInfoLabel(el, x, y) { const tag = el.tagName.toLowerCase(); const id = el.id ? `#${el.id}` : ''; // Lấy các class và lọc bỏ khoảng trắng thừa, sau đó nối lại const classes = el.className ? `.${el.className.trim().split(/\s+/).filter(cls => cls.length > 0).join('.')}` : ''; infoLabel.textContent = `${tag}${id}${classes}`; infoLabel.style.top = `${y + 15 + window.scrollY}px`; infoLabel.style.left = `${x + 15 + window.scrollX}px`; infoLabel.style.display = 'block'; } // --- Hàm xử lý mouseover --- function mouseOver(e) { // Bỏ qua nếu không activated, hoặc di chuột vào chính overlay/label, hoặc root document if (!activated || e.target === hoverOverlay || e.target === infoLabel || e.target === document.documentElement || e.target === document.body) return; // Tránh cập nhật nếu di chuột qua một phần tử đã có overlay (tránh nhấp nháy) // if (elementOverlayMap.has(e.target)) return; // Commented out - want hover overlay even on selected updateOverlay(hoverOverlay, e.target); updateInfoLabel(e.target, e.clientX, e.clientY); } // --- Hàm xử lý mouseout/mouseleave --- function mouseOut(e) { if (!activated) return; // Ẩn label và hover overlay khi chuột rời khỏi bất kỳ phần tử nào (hoặc trang) hoverOverlay.style.display = 'none'; infoLabel.style.display = 'none'; } // --- Hàm tạo và trả về overlay với style và zIndex --- function createAndStoreOverlay(el, color, zIndex, hatched = false) { // Kiểm tra nếu overlay đã tồn tại cho phần tử này, xóa nó trước if (elementOverlayMap.has(el)) { removeOverlay(el); } const overlay = document.createElement('div'); Object.assign(overlay.style, { position: 'absolute', borderRadius: '4px', pointerEvents: 'none', boxSizing: 'border-box', border: `2px solid ${color}`, background: color.replace(/[^,]+(?=\))/, '0.2'), display: 'block', zIndex: zIndex, opacity: 1, // Mặc định là 1 transition: 'opacity 0.2s ease' // Thêm transition cho hiệu ứng mờ dần khi xóa }); if (hatched) { overlay.style.backgroundImage = 'linear-gradient(45deg, rgba(0,0,0,0.1) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0.1) 75%, transparent 75%, transparent)'; overlay.style.backgroundSize = '20px 20px'; } // Thử cập nhật vị trí, nếu thất bại (phần tử không hiển thị), không thêm overlay if (!updateOverlay(overlay, el)) { return null; // Không thêm overlay nếu phần tử không hiển thị } document.body.appendChild(overlay); elementOverlayMap.set(el, overlay); // Lưu trữ tham chiếu overlay return overlay; } // --- Hàm xóa overlay của một phần tử --- function removeOverlay(el) { const overlay = elementOverlayMap.get(el); if (overlay) { // Sử dụng transition để mờ dần trước khi xóa overlay.style.opacity = 0; overlay.addEventListener('transitionend', function() { if (overlay.parentNode) { overlay.parentNode.removeChild(overlay); } }); elementOverlayMap.delete(el); } } // --- Helper: Kiểm tra mối quan hệ (chứa, bị chứa, hoặc giống nhau) --- function isRelated(el1, el2) { if (!el1 || !el2) return false; // Kiểm tra nếu el1 hoặc el2 không còn trong DOM if (!el1.isConnected || !el2.isConnected) return false; return el1 === el2 || el1.contains(el2) || el2.contains(el1); } // --- Helper: Kiểm tra nếu element là tổ tiên của bất kỳ phần tử nào trong set --- function isAncestorOfAnyInSet(element, set) { if (!element || !set || set.size === 0) return false; for (const el of set) { if (element !== el && element.contains(el)) { return true; } } return false; } // --- Helper: Kiểm tra nếu element là con cháu của bất kỳ phần tử nào trong set --- function isDescendantOfAnyInSet(element, set) { if (!element || !set || set.size === 0) return false; for (const el of set) { if (element !== el && el.contains(element)) { return true; } } return false; } // --- Helper: Kiểm tra nếu element là con cháu của bất kỳ phần tử nào trong Main hoặc Addition --- function isDescendantOfAnyMainOrAddition(element) { return isDescendantOfAnyInSet(element, mainElements) || isDescendantOfAnyInSet(element, additionElements); } // --- Hàm xóa phần tử và overlay của nó theo loại --- function removeElement(el, type) { let targetSet; let overlayProp; let zIndex; switch (type) { case 'main': targetSet = mainElements; overlayProp = 'mainOverlay'; zIndex = 2147483645; break; case 'addition': targetSet = additionElements; overlayProp = 'additionOverlay'; zIndex = 2147483646; break; case 'exclude': targetSet = excludedElements; overlayProp = 'excludeOverlay'; zIndex = 2147483647; break; default: console.error("Invalid removal type:", type); return; } if (targetSet.has(el)) { targetSet.delete(el); removeOverlay(el); // Xóa overlay qua WeakMap // Xử lý dọn dẹp các phần tử Excluded là con cháu của phần tử vừa xóa if (type === 'main' || type === 'addition') { const excludedToRemove = []; excludedElements.forEach(ex => { // Nếu Excluded này là con cháu của `el` VÀ không phải con cháu của bất kỳ Main/Addition còn lại nào if (el.contains(ex) && !isDescendantOfAnyMainOrAddition(ex)) { excludedToRemove.push(ex); } }); excludedToRemove.forEach(ex => removeElement(ex, 'exclude')); if (excludedToRemove.length > 0) { console.log(`Removed ${excludedToRemove.length} Excluded elements dependent on the removed ${type} element.`); } } console.log(`${type.charAt(0).toUpperCase() + type.slice(1)} Element Removed:`, el); } else { // console.warn(`Attempted to remove a ${type} element that was not in the set:`, el); } } // --- Hàm xử lý sự kiện click --- function clickHandler(e) { // Bỏ qua nếu không activated, hoặc click vào chính overlay/label if (!activated || e.target === hoverOverlay || e.target === infoLabel) return; e.preventDefault(); e.stopPropagation(); const target = e.target; let mode = null; let targetSet = null; let overlayColor = null; let overlayZIndex = null; let isHatched = false; // Xác định chế độ click if (!e.ctrlKey && !e.altKey) { mode = 'main'; targetSet = mainElements; overlayColor = mainColor; overlayZIndex = 2147483645; } else if (e.altKey && !e.ctrlKey) { mode = 'addition'; targetSet = additionElements; overlayColor = additionColor; overlayZIndex = 2147483646; } else if (e.ctrlKey && !e.altKey) { mode = 'exclude'; targetSet = excludedElements; overlayColor = excludeColor; overlayZIndex = 2147483647; isHatched = true; } else { // Tổ hợp phím không xác định, bỏ qua console.log("Unknown key combination clicked."); return; } // --- Bước 1: Xử lý Bỏ chọn (nếu click vào phần tử đã chọn cùng loại) --- if (targetSet.has(target)) { removeElement(target, mode); return; // Đã xử lý, thoát } // --- Bước 2: Xử lý Logic Tổ tiên/Con cháu trong CÙNG loại --- // Nếu target là con cháu của phần tử đã chọn cùng loại, bỏ qua click này cho loại đó if (isDescendantOfAnyInSet(target, targetSet)) { console.log(`Clicked element is a descendant of an already selected ${mode} element. Ignoring.`); return; // Đã xử lý, thoát } // Nếu target là tổ tiên của phần tử đã chọn cùng loại, xóa các con cháu đó const descendantsToRemoveInSameMode = []; targetSet.forEach(el => { if (target.contains(el)) { descendantsToRemoveInSameMode.push(el); } }); descendantsToRemoveInSameMode.forEach(el => removeElement(el, mode)); if (descendantsToRemoveInSameMode.length > 0) { console.log(`Clicked element is an ancestor of selected ${mode} elements. Replaced descendants with ancestor.`); } // --- Bước 3: Xử lý Logic Quan hệ giữa CÁC loại --- // Chụp lại trạng thái hiện tại trước khi xử lý xung đột const initiallySelectedAsMain = mainElements.has(target); const initiallySelectedAsAddition = additionElements.has(target); const initiallySelectedAsExcluded = excludedElements.has(target); let allowAdd = true; // Cờ để quyết định có thêm target vào targetSet cuối cùng không if (mode === 'main') { // Nếu click Main, xóa mọi phần tử Addition có quan hệ với target const additionsToRemove = []; additionElements.forEach(addEl => { if (isRelated(target, addEl)) { additionsToRemove.push(addEl); } }); additionsToRemove.forEach(addEl => removeElement(addEl, 'addition')); if (additionsToRemove.length > 0) { console.log(`Conflict with Addition elements on Main click. Removed ${additionsToRemove.length} conflicting Additions.`); } // Nếu target là Excluded, xóa nó khỏi Excluded if (initiallySelectedAsExcluded) { removeElement(target, 'exclude'); console.log(`Element was Excluded. Removed exclusion on Main click.`); } } else if (mode === 'addition') { // Nếu click Addition, kiểm tra xem có quan hệ với Main không const mainsRelated = Array.from(mainElements).filter(mainEl => isRelated(target, mainEl)); if (mainsRelated.length > 0) { // Yêu cầu: ưu tiên Main, vô hiệu hóa click Addition nếu xung đột console.log('Invalid Addition element: Related to a Main element. Ignoring Addition click.'); allowAdd = false; // Không cho phép thêm vào Addition } else { // Nếu không xung đột với Main, kiểm tra và xóa khỏi Excluded nếu cần if (initiallySelectedAsExcluded) { removeElement(target, 'exclude'); console.log(`Element was Excluded. Removed exclusion on Addition click.`); } // Không cần kiểm tra xung đột giữa Addition với Addition khác theo yêu cầu } } else if (mode === 'exclude') { // Nếu click Excluded, kiểm tra xem có phải là con cháu của Main hoặc Addition không const isValidExclude = isDescendantOfAnyMainOrAddition(target); if (!isValidExclude) { console.log('Invalid Excluded element: Not a child/descendant of any Main or Addition element. Ignoring click.'); allowAdd = false; // Không cho phép thêm vào Excluded } else { // Nếu hợp lệ làm Excluded, kiểm tra và xóa khỏi Main hoặc Addition nếu cần if (initiallySelectedAsMain) { removeElement(target, 'main'); console.log(`Element was Main. Removed Main selection on Excluded click.`); } if (initiallySelectedAsAddition) { removeElement(target, 'addition'); console.log(`Element was Addition. Removed Addition selection on Excluded click.`); } } } // --- Bước 4: Thêm phần tử (nếu được phép) và tạo Overlay --- if (allowAdd) { // Kiểm tra lại lần cuối xem phần tử có còn hợp lệ để thêm vào set hiện tại sau khi xử lý xung đột không // (Ví dụ: bấm Ctrl+Click vào 1 phần tử, nó xóa Main/Addition chứa nó, nhưng nó lại không phải con của bất kỳ Main/Addition còn lại nào) if (mode === 'exclude') { // Riêng với Exclude, cần kiểm tra lại điều kiện con cháu sau khi potentially xóa Main/Addition cha if (!isDescendantOfAnyMainOrAddition(target)) { console.log('Element became invalid for Excluded after removing its parent Main/Addition. Ignoring add.'); allowAdd = false; // Không thêm nếu không còn cha hợp lệ } } } if (allowAdd) { targetSet.add(target); // Tạo/cập nhật overlay const overlay = createAndStoreOverlay(target, overlayColor, overlayZIndex, isHatched); if (overlay) { console.log(`${mode.charAt(0).toUpperCase() + mode.slice(1)} Element Added:`, target); } else { // Xóa khỏi set nếu không tạo được overlay (ví dụ: display: none) targetSet.delete(target); console.log(`Could not add ${mode} element (not visible):`, target); } } // --- Bước 5: Dọn dẹp cuối cùng cho Excluded sau mọi thay đổi --- // Đảm bảo tất cả Excluded còn lại đều là con cháu của ít nhất 1 Main hoặc 1 Addition const excludedStillValid = new Set(); excludedElements.forEach(ex => { if (isDescendantOfAnyMainOrAddition(ex)) { excludedStillValid.add(ex); } else { removeElement(ex, 'exclude'); // Xóa các Excluded không còn hợp lệ console.log('Removed Excluded element that lost its Main/Addition parent:', ex); } }); // Cập nhật lại set excludedElements excludedElements = excludedStillValid; // Note: Có thể cần một logic để cập nhật overlay nếu phần tử thay đổi kích thước/vị trí (cuộn trang, resize) // Điều này phức tạp hơn và không nằm trong yêu cầu hiện tại. UpdateOverlay chỉ chạy 1 lần khi tạo. // Cần thêm listeners cho scroll/resize để update các overlays đang hiển thị. } // --- Hàm kích hoạt inspector --- function activateInspector() { if (!hoverOverlay || !infoLabel) createOverlays(); document.addEventListener('mouseover', mouseOver, true); document.addEventListener('mouseout', mouseOut, true); document.addEventListener('mouseleave', mouseOut, true); document.addEventListener('click', clickHandler, true); // Thêm listener cho scroll/resize để cập nhật vị trí overlays window.addEventListener('scroll', updateAllOverlays, true); window.addEventListener('resize', updateAllOverlays, true); activated = true; console.log('Inspector Activated. Use Ctrl+Alt+1 to toggle. Click modes: Main, Alt+Click: Addition, Ctrl+Click: Excluded.'); } // --- Hàm hủy kích hoạt inspector --- function deactivateInspector() { document.removeEventListener('mouseover', mouseOver, true); document.removeEventListener('mouseout', mouseOut, true); document.removeEventListener('mouseleave', mouseOut, true); document.removeEventListener('click', clickHandler, true); window.removeEventListener('scroll', updateAllOverlays, true); window.removeEventListener('resize', updateAllOverlays, true); // Dọn dẹp tất cả phần tử và overlays removeAllElementsAndOverlays(); hoverOverlay.style.display = 'none'; infoLabel.style.display = 'none'; activated = false; console.log('Inspector Deactivated.'); } // --- Hàm xóa tất cả phần tử đã chọn và overlays --- function removeAllElementsAndOverlays() { [...mainElements].forEach(el => removeElement(el, 'main')); [...additionElements].forEach(el => removeElement(el, 'addition')); [...excludedElements].forEach(el => removeElement(el, 'exclude')); // Excluded nên được xóa cuối cùng hoặc độc lập // Đảm bảo tất cả overlays từ WeakMap bị xóa for(let [el, overlay] of elementOverlayMap) { if (overlay && overlay.parentNode) { overlay.parentNode.removeChild(overlay); } } elementOverlayMap = new WeakMap(); // Reset WeakMap mainElements.clear(); additionElements.clear(); excludedElements.clear(); } // --- Hàm cập nhật vị trí của tất cả overlays đang hiển thị --- function updateAllOverlays() { if (!activated) return; // Chỉ cập nhật khi inspector đang hoạt động // Tạo danh sách các phần tử cần cập nhật overlay const elementsToUpdate = new Set([...mainElements, ...additionElements, ...excludedElements]); elementsToUpdate.forEach(el => { const overlay = elementOverlayMap.get(el); if (overlay) { // Nếu không thể cập nhật overlay (phần tử không hiển thị), xóa phần tử khỏi set tương ứng if (!updateOverlay(overlay, el)) { if (mainElements.has(el)) removeElement(el, 'main'); else if (additionElements.has(el)) removeElement(el, 'addition'); else if (excludedElements.has(el)) removeElement(el, 'exclude'); } } else { // Nếu phần tử có trong set nhưng không có overlay (lỗi?), thử tạo lại hoặc xóa khỏi set console.warn("Element in set but no overlay found. Removing:", el); if (mainElements.has(el)) removeElement(el, 'main'); else if (additionElements.has(el)) removeElement(el, 'addition'); else if (excludedElements.has(el)) removeElement(el, 'addition'); } }); // Sau khi cập nhật/xóa các overlay, cần chạy lại bước dọn dẹp Excluded cuối cùng // để loại bỏ những Excluded mà cha/mẹ của nó vừa bị xóa do không hiển thị const excludedStillValid = new Set(); excludedElements.forEach(ex => { if (isDescendantOfAnyMainOrAddition(ex)) { excludedStillValid.add(ex); } else { removeElement(ex, 'exclude'); console.log('Removed Excluded element that lost its Main/Addition parent (due to visibility change):', ex); } }); excludedElements = excludedStillValid; // Cập nhật lại set } // --- Hàm bật/tắt inspector --- function toggleInspector(e) { // Kiểm tra nếu đang gõ vào input/textarea, bỏ qua if (e.target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target.tagName)) { return; } if (e.ctrlKey === toggleKey.ctrl && e.altKey === toggleKey.alt && e.key === toggleKey.key) { if (activated) { deactivateInspector(); } else { // Tạo overlays ban đầu khi bật createOverlays(); activateInspector(); } e.preventDefault(); // Ngăn chặn hành vi mặc định của phím (ví dụ: Alt+1 có thể làm gì đó trên trình duyệt) } } // --- Lắng nghe sự kiện phím tắt --- document.addEventListener('keydown', toggleInspector, false); // --- Khởi tạo overlays ẩn khi script chạy lần đầu --- // Điều này giúp các biến hoverOverlay và infoLabel có giá trị ngay cả trước khi inspector được kích hoạt lần đầu createOverlays(); hoverOverlay.style.display = 'none'; infoLabel.style.display = 'none'; })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址