// ==UserScript== // @name 【网页排版】紧凑视图调整小助手 // @namespace https://github.com/realSilasYang // @version 2026-04-17 // @description 实时预览拖拽效果,按域名独立保存,新增域名配置管理面板,优化取消逻辑无刷新恢复。 // @author 阳熙来 // @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9Imljb24iIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIHZlcnNpb249IjEuMSIgZGF0YS1kYXJrcmVhZGVyLWlubGluZS1maWxsPSIiIGRhdGEtc3BtLWFuY2hvci1pZD0iYTMxM3guc2VhcmNoX2luZGV4LjAuaTEuN2M3YjNhODE3VVpZMnUiIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2Ij4KICA8cGF0aCBkPSJNMzg0LjI1NiAzMzUuMTA0YTMyLjgzMiAzMi44MzIgMCAxIDAgNDYuNC00Ni40NjRMMjkxLjMyOCAxNDkuMzc2YTMyLjc2OCAzMi43NjggMCAwIDAtNDYuNCAwTDEwNS42IDI4OC42NGEzMi44OTYgMzIuODk2IDAgMCAwIDQ2LjUyOCA0Ni40NjRsODMuMi04My4ydjUyMC4wNjRsLTgzLjItODMuMzI4YTMyLjg5NiAzMi44OTYgMCAxIDAtNDYuNTI4IDQ2LjUyOGwxMzkuMzI4IDEzOS4zMjhjMTIuOCAxMi44IDMzLjYgMTIuOCA0Ni40IDBsMTM5LjMyOC0xMzkuMzI4YTMyLjgzMiAzMi44MzIgMCAwIDAtNDYuNC00Ni41MjhsLTgzLjIgODMuMlYyNTEuOTY4bDgzLjIgODMuMnogbTIwMS40NzItMTI5LjZoMjkwLjI0YzM0LjU2IDAgNTEuOTA0IDE1LjEwNCA1MS45MDQgNDUuNDRWMjU2YzAgMzAuMzM2LTE3LjI4IDQ1LjQ0LTUxLjg0IDQ1LjQ0SDU4NS42NjRjLTM0LjU2IDAtNTEuODQtMTUuMTA0LTUxLjg0LTQ1LjQ0di01LjA1NmMwLTMwLjMzNiAxNy4yOC00NS40NCA1MS44NC00NS40NHogbTAgNTA5LjQ0aDI5MC4yNGMzNC41NiAwIDUxLjkwNCAxNS4xMDQgNTEuOTA0IDQ1LjQ0djUuMDU2YzAgMzAuMzM2LTE3LjI4IDQ1LjQ0LTUxLjg0IDQ1LjQ0SDU4NS42NjRjLTM0LjU2IDAtNTEuODQtMTUuMTA0LTUxLjg0LTQ1LjQ0di01LjA1NmMwLTMwLjMzNiAxNy4yOC00NS40NCA1MS44NC00NS40NHogbTAtMjU0LjcyaDI5MC4yNGMzNC41NiAwIDUxLjkwNCAxNS4xMDQgNTEuOTA0IDQ1LjUwNHY0Ljk5MmMwIDMwLjMzNi0xNy4yOCA0NS40NC01MS44NCA0NS40NEg1ODUuNjY0Yy0zNC41NiAwLTUxLjg0LTE1LjEwNC01MS44NC00NS40NHYtNC45OTJjMC0zMC4zMzYgMTcuMjgtNDUuNDQgNTEuODQtNDUuNDR2LTAuMTI4eiIgZGF0YS1zcG0tYW5jaG9yLWlkPSJhMzEzeC5zZWFyY2hfaW5kZXguMC5pMi43YzdiM2E4MTdVWlkydSIgY2xhc3M9InNlbGVjdGVkIiBmaWxsPSIjZjc2ZjUzIiBzdHlsZT0iLS1kYXJrcmVhZGVyLWlubGluZS1maWxsOiB2YXIoLS1kYXJrcmVhZGVyLWJhY2tncm91bmQtZjc2ZjUzLCAjOTQyZTE5KTsiIGRhdGEtZGFya3JlYWRlci1pbmxpbmUtZmlsbD0iIj48L3BhdGg+Cjwvc3ZnPgo= // @match *://*/* // @run-at document-end // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @license GNU GPLv3 // @downloadURL https://update.greasyfork.icu/scripts/574259/%E3%80%90%E7%BD%91%E9%A1%B5%E6%8E%92%E7%89%88%E3%80%91%E7%B4%A7%E5%87%91%E8%A7%86%E5%9B%BE%E8%B0%83%E6%95%B4%E5%B0%8F%E5%8A%A9%E6%89%8B.user.js // @updateURL https://update.greasyfork.icu/scripts/574259/%E3%80%90%E7%BD%91%E9%A1%B5%E6%8E%92%E7%89%88%E3%80%91%E7%B4%A7%E5%87%91%E8%A7%86%E5%9B%BE%E8%B0%83%E6%95%B4%E5%B0%8F%E5%8A%A9%E6%89%8B.meta.js // ==/UserScript== (function() { 'use strict'; const CONFIG_KEY = 'compact_layout_configs_v3'; const currentHost = window.location.hostname; // 默认全局参数 const defaultParams = { lh: 1.4, mb: 0.6, sr: 0.5, enabled: false }; let allConfigs = GM_getValue(CONFIG_KEY, {}); let settings = allConfigs[currentHost] || { ...defaultParams }; let observer = null; // === 核心:应用样式的函数 === function applyStyles() { if (!settings.enabled) return; const processElement = (e) => { const ign = new Set(['IMG','BUTTON','INPUT','TEXTAREA','SVG','VIDEO','IFRAME','SCRIPT','STYLE','LINK','CANVAS']); // 免疫隔离区:遇到面板或指定控件直接跳过 if (e.nodeType !== 1 || ign.has(e.tagName) || e.isContentEditable || e.closest('#cpt-panel-mask, #cpt-manage-mask, button, [role="button"], [class*="banner"], [class*="header"]')) { return; } if (!e.dataset.cpt) { e.dataset.cpt = '1'; e.dataset.os = e.getAttribute('style') || ''; const s = window.getComputedStyle(e); e.dataset.ofs = parseFloat(s.fontSize) || 16; let lh = parseFloat(s.lineHeight); e.dataset.olh = isNaN(lh) ? e.dataset.ofs * 1.2 : lh; ['marginTop','marginBottom','paddingTop','paddingBottom','rowGap','columnGap'].forEach(p => { const v = parseFloat(s[p]); if (!isNaN(v) && v > 0) e.dataset['o' + p] = v; }); } const fs = parseFloat(e.dataset.ofs); if (isNaN(fs)) return; let css = ''; const origLh = parseFloat(e.dataset.olh); if (origLh > settings.lh * fs) { css += `line-height: ${settings.lh} !important;`; } ['marginTop','marginBottom','paddingTop','paddingBottom','rowGap','columnGap'].forEach(p => { if (e.tagName === 'P' && p === 'marginBottom') { const origMb = parseFloat(e.dataset.omarginBottom); if (!isNaN(origMb) && origMb > settings.mb * fs) { css += `margin-bottom: ${settings.mb}em !important;`; } } else { const origVal = parseFloat(e.dataset['o' + p]); if (!isNaN(origVal)) { const kebab = p.replace(/[A-Z]/g, m => '-' + m.toLowerCase()); css += `${kebab}: ${origVal * settings.sr}px !important;`; } } }); e.style.cssText = (e.dataset.os ? e.dataset.os + ';' : '') + css; }; document.querySelectorAll('*').forEach(processElement); if (!observer) { observer = new MutationObserver(ms => { ms.forEach(m => m.addedNodes.forEach(n => { if (n.nodeType === 1) { processElement(n); n.querySelectorAll('*').forEach(processElement); } })); }); observer.observe(document.body, { childList: true, subtree: true }); } } function resetStyles() { if (observer) { observer.disconnect(); observer = null; } document.querySelectorAll('[data-cpt]').forEach(e => { if (e.dataset.os !== undefined) e.setAttribute('style', e.dataset.os); else e.removeAttribute('style'); Object.keys(e.dataset).forEach(key => { if (key === 'cpt' || key === 'os' || key.startsWith('o')) { delete e.dataset[key]; } }); }); } if (settings.enabled) applyStyles(); // === UI:当前网站配置面板 === GM_registerMenuCommand('⚙️ 配置当前网站排版参数', showPanel); // === UI:新增全局域名管理面板 === GM_registerMenuCommand('🌐 管理所有域名配置', showManagePanel); function showPanel() { if (document.getElementById('cpt-panel-mask')) return; const mask = document.createElement('div'); mask.id = 'cpt-panel-mask'; mask.style.cssText = ` position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.4); z-index: 2147483647; display: flex; justify-content: center; align-items: center; backdrop-filter: blur(3px); line-height: 1.5 !important; text-align: left !important; color: #333 !important; `; mask.innerHTML = `

📐 网页排版压缩 ${currentHost}

在此域名启用压缩
最大行高倍数 ${settings.lh}
段落底边距 (em) ${settings.mb}
间距压缩比例 ${Math.round(settings.sr*100)}%
`; document.body.appendChild(mask); const updatePreview = () => { if (!settings.enabled) resetStyles(); else applyStyles(); }; const inputs = { lh: document.getElementById('range-lh'), mb: document.getElementById('range-mb'), sr: document.getElementById('range-sr'), toggle: document.getElementById('cpt-main-toggle') }; const labels = { lh: document.getElementById('val-lh'), mb: document.getElementById('val-mb'), sr: document.getElementById('val-sr') }; ['lh', 'mb', 'sr'].forEach(key => { inputs[key].addEventListener('input', (e) => { const val = parseFloat(e.target.value); settings[key] = val; labels[key].innerText = key === 'sr' ? Math.round(val * 100) + '%' : (val % 1 === 0 ? val + '.0' : val); updatePreview(); }); }); inputs.toggle.addEventListener('change', (e) => { settings.enabled = e.target.checked; document.getElementById('cpt-controls').style.opacity = settings.enabled ? 1 : 0.4; document.getElementById('cpt-controls').style.pointerEvents = settings.enabled ? 'auto' : 'none'; updatePreview(); }); // 【关键修改:无刷新恢复状态】 const closePanel = () => { mask.remove(); let oldSettings = allConfigs[currentHost] || { ...defaultParams }; // 如果面板上的当前状态和原保存状态不一致,进行回滚 if (JSON.stringify(settings) !== JSON.stringify(oldSettings)) { settings = { ...oldSettings }; // 恢复内存参数 if (settings.enabled) { applyStyles(); // 重新应用原有压缩 } else { resetStyles(); // 撤销压缩恢复原网页 } } }; document.getElementById('cpt-save').addEventListener('click', () => { allConfigs[currentHost] = settings; GM_setValue(CONFIG_KEY, allConfigs); mask.remove(); }); document.getElementById('cpt-close').addEventListener('click', closePanel); mask.addEventListener('click', (e) => { if (e.target === mask) closePanel(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && document.getElementById('cpt-panel-mask')) closePanel(); }); } // === UI:全局管理面板 === function showManagePanel() { if (document.getElementById('cpt-manage-mask')) return; const mask = document.createElement('div'); mask.id = 'cpt-manage-mask'; mask.style.cssText = ` position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.5); z-index: 2147483647; display: flex; justify-content: center; align-items: center; backdrop-filter: blur(3px); line-height: 1.5 !important; text-align: left !important; color: #333 !important; `; function renderList() { let configs = GM_getValue(CONFIG_KEY, {}); let domains = Object.keys(configs); if (domains.length === 0) { return '
暂无任何域名配置记录
'; } return domains.map(domain => { let conf = configs[domain]; let isCurrent = domain === currentHost; return `
${domain} ${isCurrent ? '(当前)' : ''} ${conf.enabled ? '已启用' : '未启用'} | LH: ${conf.lh} , MB: ${conf.mb} , SR: ${Math.round(conf.sr*100)}%
`; }).join(''); } mask.innerHTML = `

🌐 域名配置管理

${renderList()}
`; document.body.appendChild(mask); // 事件委托:处理删除按钮 mask.addEventListener('click', (e) => { if (e.target.classList.contains('cpt-del-btn')) { const domainToDelete = e.target.getAttribute('data-domain'); if (confirm(`确定要删除 ${domainToDelete} 的配置记录吗?`)) { let currentConfigs = GM_getValue(CONFIG_KEY, {}); delete currentConfigs[domainToDelete]; GM_setValue(CONFIG_KEY, currentConfigs); // 更新本地内存中的 allConfigs allConfigs = currentConfigs; // 如果删除的是当前域名,立即复位排版并更新当前 settings if (domainToDelete === currentHost) { settings = { ...defaultParams }; resetStyles(); } // 重新渲染列表 document.getElementById('cpt-domain-list').innerHTML = renderList(); } } else if (e.target.id === 'cpt-manage-close' || e.target === mask) { mask.remove(); } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && document.getElementById('cpt-manage-mask')) mask.remove(); }, {once: true}); } })();