您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Customize fonts for any website through the Tampermonkey menu
// ==UserScript== // @name Font Customizer // @namespace http://tampermonkey.net/ // @version 1.1 // @description Customize fonts for any website through the Tampermonkey menu // @author Cursor, claude-3.7, and me(qooo). // @license MIT // @match *://*/* // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik05LjkzIDEzLjVoNC4xNEwxMiA3Ljk4ek0yMCAySDRjLTEuMSAwLTIgLjktMiAydjE2YzAgMS4xLjkgMiAyIDJoMTZjMS4xIDAgMi0uOSAyLTJWNGMwLTEuMS0uOS0yLTItMm0tNC4wNSAxNi41bC0xLjE0LTNIOS4xN2wtMS4xMiAzSDUuOTZsNS4xMS0xM2gxLjg2bDUuMTEgMTN6Ii8+PC9zdmc+ // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect fonts.googleapis.com // @connect fonts.gstatic.com // ==/UserScript== (function () { "use strict"; // Storage keys const STORAGE_KEY_PREFIX = "fontCustomizer_"; const ENABLED_SUFFIX = "_enabled"; const FONT_SUFFIX = "_font"; const FONT_LIST_KEY = "fontCustomizer_globalFonts"; // Common web fonts from Google Fonts const WEB_FONTS = [ { name: "Roboto", family: "Roboto, sans-serif", url: "https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap", }, { name: "Open Sans", family: "'Open Sans', sans-serif", url: "https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap", }, { name: "Lato", family: "Lato, sans-serif", url: "https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap", }, { name: "Montserrat", family: "Montserrat, sans-serif", url: "https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap", }, { name: "Poppins", family: "Poppins, sans-serif", url: "https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap", }, { name: "Source Sans Pro", family: "'Source Sans Pro', sans-serif", url: "https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;600;700&display=swap", }, { name: "Ubuntu", family: "Ubuntu, sans-serif", url: "https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap", }, { name: "Nunito", family: "Nunito, sans-serif", url: "https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700&display=swap", }, { name: "Playfair Display", family: "'Playfair Display', serif", url: "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&display=swap", }, { name: "Merriweather", family: "Merriweather, serif", url: "https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&display=swap", }, ]; // Load a web font function loadWebFont(fontUrl) { GM_xmlhttpRequest({ method: "GET", url: fontUrl, onload: function (response) { const style = document.createElement("style"); style.textContent = response.responseText; document.head.appendChild(style); }, }); } // Get saved fonts or initialize with empty array function getSavedFonts() { return GM_getValue(FONT_LIST_KEY, []); } // Save a font to the list if it doesn't exist already function saveFontToList(font) { const fonts = getSavedFonts(); if (!fonts.includes(font)) { fonts.push(font); GM_setValue(FONT_LIST_KEY, fonts); } } // Remove a font from the saved list function removeFontFromList(font) { const fonts = getSavedFonts(); const index = fonts.indexOf(font); if (index !== -1) { fonts.splice(index, 1); GM_setValue(FONT_LIST_KEY, fonts); } } // Get current hostname const hostname = window.location.hostname; // Storage helper functions function getStorageKey(suffix) { return STORAGE_KEY_PREFIX + hostname + suffix; } function isEnabledForSite() { return localStorage.getItem(getStorageKey(ENABLED_SUFFIX)) === "true"; } function setEnabledForSite(enabled) { localStorage.setItem(getStorageKey(ENABLED_SUFFIX), enabled.toString()); } function getFontForSite() { return localStorage.getItem(getStorageKey(FONT_SUFFIX)) || ""; } function setFontForSite(font) { localStorage.setItem(getStorageKey(FONT_SUFFIX), font); } // Apply font to the website function applyFont() { if (isEnabledForSite()) { const font = getFontForSite(); GM_addStyle(` * { font-family: ${font} !important; } `); } } // Remove applied font styles function removeAppliedFont() { const styleId = "font-customizer-styles"; const existingStyle = document.getElementById(styleId); if (existingStyle) { existingStyle.remove(); } applyStyles(); } // Apply all necessary styles function applyStyles() { if (isEnabledForSite()) { const font = getFontForSite(); const styleElement = document.createElement("style"); styleElement.id = "font-customizer-styles"; styleElement.textContent = ` * { font-family: ${font} !important; } `; document.head.appendChild(styleElement); } } // Menu command IDs let toggleCommandId = null; let fontCommandId = null; // Register menu commands function registerMenuCommands() { if (toggleCommandId !== null) { GM_unregisterMenuCommand(toggleCommandId); } if (fontCommandId !== null) { GM_unregisterMenuCommand(fontCommandId); } const enabled = isEnabledForSite(); const toggleText = enabled ? "🟢 Enabled on the site" : "🔴 Disabled on the site"; toggleCommandId = GM_registerMenuCommand(toggleText, function () { const newEnabledState = !enabled; setEnabledForSite(newEnabledState); if (newEnabledState) { applyStyles(); } else { removeAppliedFont(); } registerMenuCommands(); }); const currentFont = getFontForSite(); // Truncate the font name if it's too long (more than x characters) const truncatedFont = currentFont && currentFont.length > 10 ? currentFont.substring(0, 10) + "..." : currentFont || "None"; fontCommandId = GM_registerMenuCommand( `✨ Select Font (Current: ${truncatedFont})`, showFontSelector ); } // Create and show font selector popup function showFontSelector() { // Remove existing popup if any const existingPopup = document.getElementById("font-customizer-popup"); if (existingPopup) { existingPopup.remove(); } // Create popup container const popup = document.createElement("div"); popup.id = "font-customizer-popup"; // Add styles for the popup GM_addStyle(` #font-customizer-popup { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: var(--popup-bg, #ffffff); color: var(--popup-text, #000000); border: 1px solid var(--popup-border, #cccccc); border-radius: 12px; padding: 24px; z-index: 9999; min-width: 380px; width: 400px; height: fit-content; overflow-y: auto; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; animation: popup-fade-in 0.2s ease-out; } @keyframes popup-fade-in { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } } #font-customizer-popup h2 { margin-top: 0; margin-bottom: 20px; font-size: 20px; font-weight: 600; text-align: center; color: var(--popup-title, inherit); } #font-customizer-popup .font-input-container { margin-bottom: 16px; } #font-customizer-popup .font-input { width: 100%; padding: 12px; border: 1px solid var(--popup-border, #cccccc); border-radius: 8px; box-sizing: border-box; font-size: 14px; transition: border-color 0.2s; margin-bottom: 8px; } #font-customizer-popup .font-input:focus { border-color: var(--popup-button, #4a86e8); outline: none; box-shadow: 0 0 0 2px rgba(74, 134, 232, 0.2); } #font-customizer-popup .add-font-button { display: block; width: 100%; padding: 8px 16px; background-color: var(--popup-button, #4a86e8); color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 14px; transition: background-color 0.2s, transform 0.1s; } #font-customizer-popup .add-font-button:hover { background-color: var(--popup-button-hover, #3b78e7); } #font-customizer-popup .add-font-button:active { transform: scale(0.98); } #font-customizer-popup .section-title { font-size: 16px; font-weight: 600; margin: 16px 0 8px 0; color: var(--popup-title, inherit); display: flex; align-items: center; justify-content: space-between; } #font-customizer-popup .section-title .title-with-info { display: flex; align-items: center; gap: 8px; } #font-customizer-popup .toggle-section{ cursor: pointer; border: 1px solid var(--popup-border, #cccccc); border-radius: 8px; padding: 4px 8px; font-size: 12px; font-weight: 600; color: var(--popup-text-secondary); } #font-customizer-popup .toggle-section:hover { background-color: var(--popup-hover, #f5f5f5); } #font-customizer-popup .info-icon { cursor: help; color: var(--popup-text-secondary); font-size: 14px; position: relative; } #font-customizer-popup .info-tooltip { visibility: hidden; position: absolute; left: 50%; transform: translateX(-50%); bottom: calc(100% + 8px); color: var(--popup-tooltip-text); padding: 8px 12px; background-color: var(--popup-tooltip-bg); border-radius: 6px; font-size: 12px; font-weight: normal; white-space: normal; text-wrap: wrap; z-index: 10000; opacity: 0; transition: opacity 0.2s, visibility 0.2s; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); width: 250px; line-height: 1.4; } #font-customizer-popup .info-tooltip::after { content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border-width: 5px; border-style: solid; border-color: var(--popup-tooltip-bg) transparent transparent transparent; } #font-customizer-popup .info-icon:hover .info-tooltip { visibility: visible; opacity: 1; } #font-customizer-popup .no-fonts-message { color: var(--popup-text-secondary, #666666); font-style: italic; text-align: center; padding: 16px 0; } #font-customizer-popup ul { list-style: none; padding: 0; margin: 0 0 16px 0; max-height: 200px; overflow-y: auto; border-radius: 8px; border: 1px solid var(--popup-border, #eaeaea); } #font-customizer-popup ul:empty { display: none; } #font-customizer-popup ul::-webkit-scrollbar { width: 8px; } #font-customizer-popup ul::-webkit-scrollbar-track { background: var(--popup-scrollbar-track, #f1f1f1); border-radius: 0 8px 8px 0; } #font-customizer-popup ul::-webkit-scrollbar-thumb { background: var(--popup-scrollbar-thumb, #c1c1c1); border-radius: 4px; } #font-customizer-popup ul::-webkit-scrollbar-thumb:hover { background: var(--popup-scrollbar-thumb-hover, #a1a1a1); } #font-customizer-popup li { padding: 10px 16px; cursor: pointer; transition: all 0.15s ease; border-bottom: 1px solid var(--popup-border, #eaeaea); display: flex; align-items: center; justify-content: space-between; } #font-customizer-popup li:last-child { border-bottom: none; } #font-customizer-popup li:hover { background-color: var(--popup-hover, #f5f5f5); } #font-customizer-popup li.selected { background-color: var(--popup-selected, #e8f0fe); font-weight: 500; } #font-customizer-popup li.selected .font-name::before { content: "✓"; margin-right: 8px; color: var(--popup-check, #4a86e8); font-weight: bold; } #font-customizer-popup li:not(.selected) .font-name { padding-left: 24px; } #font-customizer-popup .font-actions { display: flex; opacity: 0; transition: opacity 0.2s; } #font-customizer-popup li:hover .font-actions { opacity: 1; } #font-customizer-popup .delete-font { color: var(--popup-delete, #e53935); cursor: pointer; font-size: 16px; padding: 4px; border-radius: 4px; transition: background-color 0.2s; } #font-customizer-popup .delete-font:hover { background-color: var(--popup-delete-hover, rgba(229, 57, 53, 0.1)); } #font-customizer-popup .close-button { display: block; width: 100%; margin: 16px auto 0; padding: 12px 16px; background-color: var(--popup-button-secondary, #757575); color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 15px; transition: background-color 0.2s, transform 0.1s; } #font-customizer-popup .close-button:hover { background-color: var(--popup-button-secondary-hover, #616161); } #font-customizer-popup .close-button:active { transform: scale(0.98); } #font-customizer-popup .web-font-item { font-family: inherit; } #font-customizer-popup .web-font-item .font-name { font-family: inherit; } /* Dark mode detection and styles */ @media (prefers-color-scheme: dark) { #font-customizer-popup { --popup-bg: #222222; --popup-text: #ffffff; --popup-text-secondary: #aaaaaa; --popup-title: #ffffff; --popup-border: #444444; --popup-hover: #333333; --popup-selected: #2c3e50; --popup-check: #64b5f6; --popup-button: #4a86e8; --popup-button-hover: #3b78e7; --popup-button-secondary: #616161; --popup-button-secondary-hover: #757575; --popup-delete: #f44336; --popup-delete-hover: rgba(244, 67, 54, 0.2); --popup-scrollbar-track: #333333; --popup-scrollbar-thumb: #555555; --popup-scrollbar-thumb-hover: #666666; --popup-tooltip-bg: #4a4a4a; --popup-tooltip-text: #ffffff; } } /* Light mode styles */ @media (prefers-color-scheme: light) { #font-customizer-popup { --popup-bg: #ffffff; --popup-text: #333333; --popup-text-secondary: #666666; --popup-title: #222222; --popup-border: #eaeaea; --popup-hover: #f5f5f5; --popup-selected: #e8f0fe; --popup-check: #4a86e8; --popup-button: #4a86e8; --popup-button-hover: #3b78e7; --popup-button-secondary: #757575; --popup-button-secondary-hover: #616161; --popup-delete: #e53935; --popup-delete-hover: rgba(229, 57, 53, 0.1); --popup-scrollbar-track: #f1f1f1; --popup-scrollbar-thumb: #c1c1c1; --popup-scrollbar-thumb-hover: #a1a1a1; --popup-tooltip-bg: #ffffff; --popup-tooltip-text: #333333; } } /* Overlay to prevent clicking outside */ #font-customizer-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 9998; animation: overlay-fade-in 0.2s ease-out; } @keyframes overlay-fade-in { from { opacity: 0; } to { opacity: 1; } } `); // Create overlay to prevent clicking outside const overlay = document.createElement("div"); overlay.id = "font-customizer-overlay"; document.body.appendChild(overlay); // Add click event to overlay to close popup overlay.addEventListener("click", closePopup); // Create popup content popup.innerHTML = ` <h2>Font Customizer</h2> <div class="font-input-container"> <input type="text" id="new-font-input" class="font-input" placeholder="Enter font name (e.g., Arial, sans-serif)"> <button id="add-font-button" class="add-font-button">Add & Apply Font</button> </div> <div class="section-title"> <div class="title-with-info"> <span>Your Saved Fonts</span> <span class="info-icon">ℹ️ <span class="info-tooltip">Custom fonts will only work if they are installed on your system. Use exact font names for best results.</span> </span> </div> <span class="toggle-section" id="toggle-saved">Hide</span> </div> <ul id="saved-fonts-list"></ul> <div id="no-saved-fonts" class="no-fonts-message">No saved fonts or Hiden yet. Add one above!</div> <div class="section-title"> <div class="title-with-info"> <span>Web Fonts</span> <span class="info-icon">ℹ️ <span class="info-tooltip">These fonts will be loaded from Google Fonts when selected. They work on any system but require internet connection.</span> </span> </div> <span class="toggle-section" id="toggle-web">Show</span> </div> <ul id="web-fonts-list" style="display: none;"></ul> <button class="close-button" id="close-popup">Close</button> `; document.body.appendChild(popup); // Get current font and saved fonts const currentFont = getFontForSite(); const savedFonts = getSavedFonts(); // Populate saved fonts list const savedFontsList = document.getElementById("saved-fonts-list"); const noSavedFonts = document.getElementById("no-saved-fonts"); // Show/hide no fonts message if (savedFonts.length === 0) { noSavedFonts.style.display = "block"; } else { noSavedFonts.style.display = "none"; } // Add saved fonts to the list savedFonts.forEach((font) => { addFontToList(font, savedFontsList, true); }); // Add web fonts to the list const webFontsList = document.getElementById("web-fonts-list"); WEB_FONTS.forEach((font) => { addFontToList(font.name, webFontsList, false, font); }); // Toggle sections document .getElementById("toggle-saved") .addEventListener("click", function () { const savedList = document.getElementById("saved-fonts-list"); const noSaved = document.getElementById("no-saved-fonts"); const isHidden = savedList.style.display === "none"; savedList.style.display = isHidden ? "block" : "none"; noSaved.style.display = isHidden && getSavedFonts().length === 0 ? "block" : "none"; this.textContent = isHidden ? "Hide" : "Show"; }); document .getElementById("toggle-web") .addEventListener("click", function () { const webList = document.getElementById("web-fonts-list"); const isHidden = webList.style.display === "none"; webList.style.display = isHidden ? "block" : "none"; this.textContent = isHidden ? "Hide" : "Show"; }); // Handle new font input const newFontInput = document.getElementById("new-font-input"); const addFontButton = document.getElementById("add-font-button"); // Focus the input field newFontInput.focus(); // Add font when button is clicked addFontButton.addEventListener("click", addNewFont); // Add font when Enter is pressed newFontInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { addNewFont(); } }); // Function to add a font to the list function addFontToList(font, listElement, isSaved = false, webFont = null) { const li = document.createElement("li"); if (webFont) { li.classList.add("web-font-item"); li.style.fontFamily = webFont.family; } li.innerHTML = ` <span class="font-name">${font}</span> ${ isSaved ? ` <div class="font-actions"> <span class="delete-font" title="Remove font">🗑️</span> </div> ` : "" } `; if (font === currentFont) { li.classList.add("selected"); } // Select font when clicked li.addEventListener("click", (e) => { if (e.target.classList.contains("delete-font")) { return; } // Remove selected class from all items document .querySelectorAll("#font-customizer-popup li") .forEach((item) => { item.classList.remove("selected"); }); // Add selected class to clicked item li.classList.add("selected"); // Set the selected font if (webFont) { setFontForSite(webFont.family); loadWebFont(webFont.url); } else { setFontForSite(font); } // Apply the font if enabled if (isEnabledForSite()) { removeAppliedFont(); applyStyles(); } // Update menu commands registerMenuCommands(); }); // Delete font when delete button is clicked (only for saved fonts) if (isSaved) { const deleteButton = li.querySelector(".delete-font"); deleteButton.addEventListener("click", (e) => { e.stopPropagation(); removeFontFromList(font); li.remove(); if (font === currentFont) { const remainingFonts = getSavedFonts(); if (remainingFonts.length > 0) { setFontForSite(remainingFonts[0]); const firstFont = document.querySelector("#saved-fonts-list li"); if (firstFont) { firstFont.classList.add("selected"); } } else { setFontForSite(""); } if (isEnabledForSite()) { removeAppliedFont(); applyStyles(); } registerMenuCommands(); } if (savedFontsList.children.length === 0) { noSavedFonts.style.display = "block"; } }); } listElement.appendChild(li); } // Function to add a new font function addNewFont() { const fontName = newFontInput.value.trim(); if (fontName) { saveFontToList(fontName); newFontInput.value = ""; noSavedFonts.style.display = "none"; addFontToList(fontName, savedFontsList, true); setFontForSite(fontName); document .querySelectorAll("#font-customizer-popup li") .forEach((item) => { item.classList.remove("selected"); }); const newFontElement = Array.from( document.querySelectorAll("#saved-fonts-list li") ).find((li) => li.querySelector(".font-name").textContent === fontName); if (newFontElement) { newFontElement.classList.add("selected"); } if (isEnabledForSite()) { removeAppliedFont(); applyStyles(); } registerMenuCommands(); } } // Function to close the popup function closePopup() { const popup = document.getElementById("font-customizer-popup"); const overlay = document.getElementById("font-customizer-overlay"); if (popup) popup.remove(); if (overlay) overlay.remove(); } // Close button functionality document .getElementById("close-popup") .addEventListener("click", closePopup); // Prevent closing when clicking on the popup itself popup.addEventListener("click", (e) => { e.stopPropagation(); }); // Allow Escape key to close the popup document.addEventListener("keydown", function handleEscape(e) { if (e.key === "Escape") { closePopup(); document.removeEventListener("keydown", handleEscape); } }); } // Initialize function init() { registerMenuCommands(); applyStyles(); const observer = new MutationObserver(function (mutations) { if (!isEnabledForSite()) return; const styleElement = document.getElementById("font-customizer-styles"); if (!styleElement) { applyStyles(); } }); observer.observe(document.documentElement, { childList: true, subtree: true, }); } // Run the script init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址