最近又在折腾几个 WordPress Page,AI 大模型变强以后,各种折腾都更顺畅了。我不是专业前端开发者,更多是靠浏览器开发者工具一点点试、改、刷新。问题也很明显:每次刷新样式就没了,改到一半忘记复制保存是常态,尤其是频繁调整 HTML 和 CSS 的时候,整个流程非常碎片化,也很容易打断思路。
2026.06.22,进一步增强。现在支持在 footer 中注入 JavaScript 脚本,而且自动刷新,调试网页更方便了!并且支持保存上一次弹窗的位置和大小,支持 Ctrl + Shift + F 一键还原~
昨夜我突发奇想,能否把临时的 CSS 持久化固定在浏览器,给 ChatGPT 聊了聊这个事情,最终形成了这个脚本。从我的角度来看目前这版已经是兼顾性能和功能的完成态了:
- CSS 持久化存储机制
每个页面(基于 origin + pathname + query)都有独立的 CSS 存储空间,修改后自动写入 localStorage,刷新不丢失 - 页面元素点击选取 + selector 自动生成
点击页面任意元素即可生成尽可能稳定的 CSS selector,同时尽量过滤 WordPress / 页面构建器的干扰 class - DOM 高亮辅助定位
点击元素后会自动生成高亮框,实时跟随元素位置,方便快速确认修改目标。 - 可拖拽、可缩放的调试面板
面板支持拖动位置、尺寸调整,并会记住上一次的位置状态 - 实时 CSS 应用与防抖保存机制
输入 CSS 后不会立即频繁写入,而是通过 debounce 方式延迟保存,避免性能浪费 - 轻量级“开发者工具替代层”
它并不试图替代 DevTools,而是补足 DevTools 在“持续编辑体验”上的缺口 - 支持快捷键打开和关闭
目前绑定的是Ctrl + Shift + F打开面板,再按一次(或者按Esc、右上角)关闭,关闭时不消耗任何资源

我知道各位大佬肯定有更好的办法、有更高的效率,但就我这个连 CSS 属性都要查的人而言,这个小脚本对我来说还是蛮有用的。以前我在页面调样式的时候,总是在“修改 → 刷新 → 丢失 → 再找 → 再改”这个循环里消耗时间,现在这个循环被打断了,变成了“实时修改 → 持久保存 → 可回溯调整”。
而且用上之后,我发现他可能在未来还会用于非页面调试的场景...比如给客户的 Demo 展示,直接浏览器改了就行,随便刷新,随便造...真是机智如我😎😎😎
// ==UserScript==
// @name Dev Panel - CSS & JS Injector
// @namespace https://lyoo.net
// @version 0.2
// @description Ctrl+Shift+F 打开开发注入面板,支持 Head CSS 注入与 Footer JS 注入,关闭或刷新页面不影响已保存内容
// @match *://*/*
// @grant none
// ==/UserScript==
(function () {
const STYLE_ID = 'custom-css-injector';
const SCRIPT_ID = 'custom-js-footer-injector';
const PANEL_ID = 'dev-toolbox-panel';
const PAGE_KEY = location.origin + location.pathname + location.search;
const CSS_STORAGE_KEY = 'stored_custom_css_' + PAGE_KEY;
const JS_STORAGE_KEY = 'stored_custom_js_' + PAGE_KEY;
const PANEL_POS_KEY = 'dev_panel_pos_v1';
const PANEL_SIZE_KEY = 'dev_panel_size_v1';
let panelVisible = false;
let lockMode = false;
let currentMode = 'css';
let dragMoveHandler = null;
let dragUpHandler = null;
let saveTimeout = null;
let clickHandler = null;
let selectorBar = null;
let resizeObserver = null;
let editor = null;
let modeToggleBtn = null;
let titleText = null;
// =========================
// Head CSS 注入层
// =========================
let style = document.getElementById(STYLE_ID);
if (!style) {
style = document.createElement('style');
style.id = STYLE_ID;
document.head.appendChild(style);
}
function loadCSS() {
return localStorage.getItem(CSS_STORAGE_KEY) || '';
}
function safeApplyCSS(css) {
try {
const testSheet = new CSSStyleSheet();
testSheet.replaceSync(css);
requestAnimationFrame(() => {
style.textContent = css;
});
} catch (e) {
console.warn('CSS Syntax Error:', e);
}
}
function saveCSS(css) {
localStorage.setItem(CSS_STORAGE_KEY, css);
safeApplyCSS(css);
}
safeApplyCSS(loadCSS());
// =========================
// Footer JS 注入层
// =========================
function loadJS() {
return localStorage.getItem(JS_STORAGE_KEY) || '';
}
function removeInjectedJS() {
const oldScript = document.getElementById(SCRIPT_ID);
if (oldScript) oldScript.remove();
}
function safeApplyJS(js) {
removeInjectedJS();
if (!js.trim()) return;
try {
new Function(js);
requestAnimationFrame(() => {
const script = document.createElement('script');
script.id = SCRIPT_ID;
script.type = 'text/javascript';
script.textContent = `
;(() => {
${js}
})();
//# sourceURL=dev-footer-injected-${location.host}.js
`.trim();
const footerTarget = document.body || document.documentElement;
footerTarget.appendChild(script);
});
} catch (e) {
console.warn('JS Syntax Error:', e);
}
}
function saveJS(js) {
localStorage.setItem(JS_STORAGE_KEY, js);
safeApplyJS(js);
}
safeApplyJS(loadJS());
// =========================
// DOM 高亮框
// =========================
let highlightBox = null;
let lastTarget = null;
function highlightElement(el) {
if (!el) return;
if (!highlightBox) {
highlightBox = document.createElement('div');
highlightBox.style.position = 'fixed';
highlightBox.style.zIndex = 2147483647;
highlightBox.style.pointerEvents = 'none';
highlightBox.style.border = '2px solid #38bdf8';
highlightBox.style.background = 'rgba(56,189,248,0.08)';
document.body.appendChild(highlightBox);
}
const rect = el.getBoundingClientRect();
highlightBox.style.left = rect.left + 'px';
highlightBox.style.top = rect.top + 'px';
highlightBox.style.width = rect.width + 'px';
highlightBox.style.height = rect.height + 'px';
}
function clearHighlight() {
if (highlightBox) {
highlightBox.remove();
highlightBox = null;
}
lastTarget = null;
}
// =========================
// Selector 生成器
// =========================
function getSelector(el) {
if (!el || el.nodeType !== 1) return '';
if (el.id) {
try {
if (document.querySelectorAll(`#${CSS.escape(el.id)}`).length === 1) {
return `#${el.id}`;
}
} catch (e) {}
}
const badPatterns = ['wpb_', 'vc_', 'container', 'row', 'wp_'];
function cleanClasses(node) {
return Array.from(node.classList || [])
.filter(c => !badPatterns.some(b => c.includes(b)) && c !== PANEL_ID);
}
function getElementSpecificSelector(node, isBottomLayer) {
const tagName = node.tagName.toLowerCase();
if (tagName === 'body' || tagName === 'html') return {
selector: tagName,
classes: []
};
const classes = cleanClasses(node);
let selector = '';
if (classes.length > 0) {
if (isBottomLayer) {
selector = `.${classes.join('.')}`;
} else {
const sortedClasses = [...classes].sort((a, b) => a.length - b.length);
selector = `.${sortedClasses.slice(0, 2).join('.')}`;
}
} else {
selector = tagName;
}
if (isBottomLayer || classes.length === 0) {
const parent = node.parentElement;
if (parent) {
const siblings = Array.from(parent.children).filter(child => {
if (classes.length > 0) {
return classes.every(c => child.classList.contains(c));
}
return child.tagName.toLowerCase() === tagName;
});
if (siblings.length > 1) {
const index = Array.from(parent.children).indexOf(node) + 1;
selector += `:nth-child(${index})`;
}
}
}
return { selector, classes };
}
let rawPathNodes = [];
let current = el;
while (current && current.tagName) {
if (current.id) {
try {
if (document.querySelectorAll(`#${CSS.escape(current.id)}`).length === 1) {
rawPathNodes.unshift({
value: `#${current.id}`,
isUnique: true
});
break;
}
} catch (e) {}
}
const depthFromBottom = rawPathNodes.length;
const isBottomLayer = depthFromBottom <= 1;
const { selector, classes } = getElementSpecificSelector(current, isBottomLayer);
let isUnique = false;
if (classes.length > 0) {
const shortestClass = [...classes].sort((a, b) => a.length - b.length)[0];
try {
if (document.querySelectorAll(`.${CSS.escape(shortestClass)}`).length <= 2) {
isUnique = true;
}
} catch (e) {}
}
rawPathNodes.unshift({ value: selector, isUnique });
current = current.parentElement;
}
let finalPath = [];
if (rawPathNodes.length <= 9) {
finalPath = rawPathNodes.map(i => i.value);
} else {
const top = rawPathNodes.slice(0, 4);
const bottom = rawPathNodes.slice(-5);
const middle = rawPathNodes.slice(4, -5);
const preserved = middle.filter(i => i.isUnique).map(i => i.value);
finalPath = Array.from(new Set([
...top.map(i => i.value),
...preserved,
...bottom.map(i => i.value)
]));
}
return finalPath.join(' ');
}
// =========================
// 面板工具函数
// =========================
function scrollToRight(el) {
setTimeout(() => {
el.scrollLeft = el.scrollWidth;
}, 10);
}
function updateModeUI() {
if (!editor || !modeToggleBtn || !titleText) return;
if (currentMode === 'css') {
titleText.innerText = 'Dev Injector Panel · CSS Head';
modeToggleBtn.innerText = 'CSS in Head';
modeToggleBtn.title = '当前为 CSS 注入模式,点击切换到 JS 页脚注入';
editor.placeholder = 'Write CSS here. It will be injected into <head>.';
} else {
titleText.innerText = 'Dev Injector Panel · JS Footer';
modeToggleBtn.innerText = 'JS in Footer';
modeToggleBtn.title = '当前为 JS 注入模式,点击切换到 CSS 头部注入';
editor.placeholder = 'Write JavaScript here. It will be injected before </body>.';
}
}
function persistEditorValue() {
if (!editor) return;
if (currentMode === 'css') {
saveCSS(editor.value);
} else {
saveJS(editor.value);
}
}
function switchMode() {
if (!editor) return;
if (saveTimeout) {
clearTimeout(saveTimeout);
saveTimeout = null;
}
persistEditorValue();
currentMode = currentMode === 'css' ? 'js' : 'css';
editor.value = currentMode === 'css' ? loadCSS() : loadJS();
updateModeUI();
editor.focus();
}
// =========================
// UI
// =========================
function createPanel() {
if (document.getElementById(PANEL_ID)) return;
panelVisible = true;
currentMode = 'css';
let panelStyle = document.getElementById('dev-panel-scroll-style');
if (!panelStyle) {
panelStyle = document.createElement('style');
panelStyle.id = 'dev-panel-scroll-style';
panelStyle.innerHTML = `
#${PANEL_ID} .custom-selector-bar::-webkit-scrollbar {
height: 4px !important;
background: transparent !important;
}
#${PANEL_ID} .custom-selector-bar::-webkit-scrollbar-thumb {
background: rgba(56,189,248,0.4) !important;
border-radius: 2px !important;
}
#${PANEL_ID} .custom-selector-bar::-webkit-scrollbar-thumb:hover {
background: rgba(56,189,248,0.7) !important;
}
#${PANEL_ID} .custom-selector-bar::-webkit-scrollbar-track {
background: transparent !important;
}
`;
document.head.appendChild(panelStyle);
}
const panel = document.createElement('div');
panel.id = PANEL_ID;
const pos = JSON.parse(localStorage.getItem(PANEL_POS_KEY) || '{}');
const size = JSON.parse(localStorage.getItem(PANEL_SIZE_KEY) || '{}');
panel.style.cssText = `
position: fixed;
top: ${pos.top || '70%'};
left: ${pos.left || '4%'};
width: ${size.width || '400px'};
height: ${size.height || '800px'};
min-width: 400px;
min-height: 180px;
background: #ffffff;
border: 1px solid rgba(0,0,0,0.15);
border-radius: 12px;
box-shadow: 0 25px 70px rgba(0,0,0,0.22), 0 2px 15px rgba(0,0,0,0.08);
z-index: 2147483647;
display: flex;
flex-direction: column;
overflow: hidden;
font-family: "Microsoft YaHei", "Segoe UI", Arial, sans-serif;
resize: both;
`;
const header = document.createElement('div');
header.style.cssText = `
height: 32px;
background: linear-gradient(to right, #f8fafc, #f1f5f9);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 12px 0 16px;
cursor: move;
user-select: none;
font-size: 13px;
font-weight: 600;
color: #1e293b;
border-bottom: 1px solid rgba(0,0,0,0.06);
flex-shrink: 0;
`;
titleText = document.createElement('span');
titleText.innerText = 'Dev Injector Panel · CSS Head';
const headerActions = document.createElement('div');
headerActions.style.cssText = `
display: flex;
align-items: center;
gap: 8px;
cursor: default;
`;
modeToggleBtn = document.createElement('button');
modeToggleBtn.type = 'button';
modeToggleBtn.innerText = 'CSS in Head';
modeToggleBtn.style.cssText = `
height: 22px;
padding: 0 10px;
border: 1px solid rgba(14,165,233,0.35);
border-radius: 999px;
background: #e0f2fe;
color: #0369a1;
font-size: 12px;
font-weight: 600;
line-height: 20px;
cursor: pointer;
outline: none;
font-family: "Microsoft YaHei", "Segoe UI", Arial, sans-serif;
`;
modeToggleBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
switchMode();
};
const closeBtn = document.createElement('span');
closeBtn.innerText = '✕';
closeBtn.style.cssText = `
cursor: pointer;
color: #64748b;
font-weight: bold;
font-size: 14px;
padding: 4px;
line-height: 1;
`;
closeBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
destroyPanel();
};
headerActions.appendChild(modeToggleBtn);
headerActions.appendChild(closeBtn);
header.appendChild(titleText);
header.appendChild(headerActions);
selectorBar = document.createElement('div');
selectorBar.className = 'custom-selector-bar';
selectorBar.style.cssText = `
padding: 6px 16px;
font-family: Consolas, Menlo, Monaco, monospace;
font-size: 14px;
background: #0f172a;
color: #38bdf8;
border-bottom: 1px solid rgba(255,255,255,0.08);
white-space: nowrap;
overflow-x: auto;
flex-shrink: 0;
`;
selectorBar.innerText = 'click element to get selector';
selectorBar.onclick = () => {
navigator.clipboard.writeText(selectorBar.innerText);
};
editor = document.createElement('textarea');
editor.style.cssText = `
flex: 1;
border: none;
outline: none;
padding: 16px;
font-family: Consolas, Menlo, Monaco, monospace;
font-size: 14px;
line-height: 1.6;
color: #334155;
background: #ffffff;
resize: none;
`;
editor.value = loadCSS();
editor.addEventListener('input', () => {
const modeAtInput = currentMode;
const valueAtInput = editor.value;
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
if (modeAtInput === 'css') {
saveCSS(valueAtInput);
} else {
saveJS(valueAtInput);
}
}, 300);
});
panel.appendChild(header);
panel.appendChild(selectorBar);
panel.appendChild(editor);
document.body.appendChild(panel);
updateModeUI();
resizeObserver = new ResizeObserver(() => {
localStorage.setItem(PANEL_SIZE_KEY, JSON.stringify({
width: panel.style.width || panel.offsetWidth + 'px',
height: panel.style.height || panel.offsetHeight + 'px'
}));
});
resizeObserver.observe(panel);
let dragging = false;
let ox = 0;
let oy = 0;
header.addEventListener('mousedown', (e) => {
if (headerActions.contains(e.target)) return;
dragging = true;
ox = e.clientX - panel.offsetLeft;
oy = e.clientY - panel.offsetTop;
});
dragMoveHandler = function (e) {
if (!dragging) return;
panel.style.left = (e.clientX - ox) + 'px';
panel.style.top = (e.clientY - oy) + 'px';
};
dragUpHandler = function () {
dragging = false;
localStorage.setItem(PANEL_POS_KEY, JSON.stringify({
left: panel.style.left,
top: panel.style.top
}));
localStorage.setItem(PANEL_SIZE_KEY, JSON.stringify({
width: panel.offsetWidth + 'px',
height: panel.offsetHeight + 'px'
}));
};
document.addEventListener('mousemove', dragMoveHandler);
document.addEventListener('mouseup', dragUpHandler);
bindClick();
scrollToRight(selectorBar);
editor.focus();
}
function destroyPanel() {
const panel = document.getElementById(PANEL_ID);
if (panel) panel.remove();
panelVisible = false;
unbindClick();
clearHighlight();
if (dragMoveHandler) document.removeEventListener('mousemove', dragMoveHandler);
if (dragUpHandler) document.removeEventListener('mouseup', dragUpHandler);
dragMoveHandler = null;
dragUpHandler = null;
if (saveTimeout) {
clearTimeout(saveTimeout);
saveTimeout = null;
}
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
editor = null;
selectorBar = null;
modeToggleBtn = null;
titleText = null;
}
function bindClick() {
if (clickHandler) return;
clickHandler = function (e) {
if (!panelVisible) return;
const panel = document.getElementById(PANEL_ID);
if (panel && panel.contains(e.target)) return;
if (lockMode) return;
e.preventDefault();
e.stopPropagation();
const target = e.target;
if (target === lastTarget) return;
lastTarget = target;
const run = () => {
const selector = getSelector(target);
if (!selectorBar) return;
selectorBar.innerText = selector;
highlightElement(target);
scrollToRight(selectorBar);
};
if (window.requestIdleCallback) {
requestIdleCallback(run);
} else {
setTimeout(run, 0);
}
};
document.addEventListener('click', clickHandler, true);
}
function unbindClick() {
if (!clickHandler) return;
document.removeEventListener('click', clickHandler, true);
clickHandler = null;
}
// =========================
// 快捷键
// Ctrl + Shift + F:打开 / 关闭面板
// Ctrl + Alt + Shift + F:重置面板位置和尺寸
// Ctrl + Shift + L:锁定 / 解锁页面元素选择
// Esc:关闭面板
// =========================
document.addEventListener('keydown', (e) => {
if (
e.ctrlKey &&
e.altKey &&
e.shiftKey &&
e.key.toLowerCase() === 'f'
) {
e.preventDefault();
localStorage.removeItem(PANEL_POS_KEY);
localStorage.removeItem(PANEL_SIZE_KEY);
const panel = document.getElementById(PANEL_ID);
if (panel) {
panel.style.left = '4%';
panel.style.top = '20%';
panel.style.width = '400px';
panel.style.height = '800px';
}
return;
}
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'f') {
e.preventDefault();
const panel = document.getElementById(PANEL_ID);
if (panel) {
destroyPanel();
} else {
createPanel();
}
}
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'l') {
lockMode = !lockMode;
}
if (e.key === 'Escape') {
if (panelVisible) destroyPanel();
}
});
})();



腻害腻害,太腻害了。