Greasy Fork is available in English.
Activate, click to remove, Alt+Click (keep only this section), Shift+Click (make full width), BACKSPACE to undo, ESC to deactivate (keeps changes). Inspired by The Printliminator by CSS-Tricks.
// ==UserScript==
// @name The Printliminator (Lean)
// @namespace https://greasyfork.org/en/users/1462137-piknockyou
// @version 1.0
// @author Piknockyou (vibe-coded)
// @license AGPL-3.0
// @description Activate, click to remove, Alt+Click (keep only this section), Shift+Click (make full width), BACKSPACE to undo, ESC to deactivate (keeps changes). Inspired by The Printliminator by CSS-Tricks.
// @match *://*/*
// @match file:///*
// @icon https://css-tricks.github.io/The-Printliminator/src/icons/favicon.ico
// @grant GM.registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
// Prevents the script from running multiple times on the same page if re-injected.
if (window.isPrintliminatorCoreLoaded) {
return;
}
window.isPrintliminatorCoreLoaded = true;
// This is the main object that holds all the logic for The Printliminator.
const Printliminator = {
isActive: false, // Is the tool currently active and listening for clicks/keys?
actionsHistory: [], // Keeps a list of changes made, so we can "undo" them.
// Configuration settings for the tool
config: {
highlightClass: "_pe_highlight", // CSS class for the red highlight border on mouseover
hiddenClass: "_pe_hidden", // CSS class used to hide elements
fullWidthClass: "_pe_full_width", // CSS class for making an element take full page width
stylesheetId: "_pe_styles", // ID for the custom stylesheet added to the page
// Keyboard shortcut settings
keys: {
undo: 8, // Backspace key code for "undo"
deactivate: 27, // Escape key code for "deactivate"
keepOnlyModifier: "altKey", // Modifier key (Alt) for "keep only this element's section"
fullWidthModifier: "shiftKey" // Modifier key (Shift) for "make element full width"
},
// HTML tag types that the tool should ignore (e.g., script tags, invisible elements)
ignoredElementTagNames: /^(SCRIPT|STYLE|LINK|META|HEAD|BR|HR)$/i
},
/**
* Activates The Printliminator.
* Sets up event listeners for mouse clicks, movements, and key presses.
*/
activate: function() {
if (Printliminator.isActive) {
return; // Already active, do nothing.
}
Printliminator.actionsHistory = []; // Clear any previous history for a fresh start.
Printliminator.injectStyleSheet(); // Add custom styles to the page.
// Start listening for user interactions on the page.
const bodyElement = document.body;
Printliminator.listenTo(bodyElement, "click", Printliminator.handleClickOnPageElement, true); // `true` for capture phase
Printliminator.listenTo(bodyElement, "mouseover", Printliminator.highlightElementOnMouseOver);
Printliminator.listenTo(bodyElement, "mouseout", Printliminator.removeHighlightOnMouseOut);
Printliminator.listenTo(document, "keydown", Printliminator.handleKeyboardShortcut);
Printliminator.isActive = true;
},
/**
* Deactivates The Printliminator.
* Removes event listeners, but keeps any changes made (like hidden elements).
*/
deactivate: function() {
if (!Printliminator.isActive) {
return; // Already inactive.
}
Printliminator.removeAllHighlights(); // Clean up any leftover highlights.
// Stop listening to user interactions.
const bodyElement = document.body;
Printliminator.stopListeningTo(bodyElement, "click", Printliminator.handleClickOnPageElement, true);
Printliminator.stopListeningTo(bodyElement, "mouseover", Printliminator.highlightElementOnMouseOver);
Printliminator.stopListeningTo(bodyElement, "mouseout", Printliminator.removeHighlightOnMouseOut);
Printliminator.stopListeningTo(document, "keydown", Printliminator.handleKeyboardShortcut);
// Note: The stylesheet is intentionally NOT removed here, so hidden elements remain hidden.
// The history for the current "session" is cleared.
Printliminator.actionsHistory = [];
Printliminator.isActive = false;
},
/**
* Handles what happens when an element on the page is clicked.
* @param {MouseEvent} event - The mouse click event object.
*/
handleClickOnPageElement: function(event) {
if (!Printliminator.isActive || event.target === document.body) {
return; // Tool not active or click was on the page background.
}
event.preventDefault(); // Stop the browser's default action for the click (e.g., following a link).
event.stopImmediatePropagation(); // Stop other click listeners on this element from running.
const clickedElement = event.target;
Printliminator.removeHighlightFromElement(clickedElement); // Remove highlight before action.
if (event[Printliminator.config.keys.fullWidthModifier]) { // Shift+Click
// Toggle full-width for the element.
if (!Printliminator.isElementStyledWith(clickedElement, Printliminator.config.fullWidthClass)) {
Printliminator.applyCssClass(clickedElement, Printliminator.config.fullWidthClass);
Printliminator.actionsHistory.push(function() { // Action to undo: remove full width
Printliminator.removeCssClass(clickedElement, Printliminator.config.fullWidthClass);
});
} else {
Printliminator.removeCssClass(clickedElement, Printliminator.config.fullWidthClass);
Printliminator.actionsHistory.push(function() { // Action to undo: re-apply full width
Printliminator.applyCssClass(clickedElement, Printliminator.config.fullWidthClass);
});
}
} else if (event[Printliminator.config.keys.keepOnlyModifier]) { // Alt+Click
// Hide elements *around* the clicked one to "keep only" its section.
const elementsToHide = Printliminator.getSurroundingElements(clickedElement);
if (elementsToHide.length > 0) {
Printliminator.hideElements(elementsToHide);
Printliminator.actionsHistory.push({ type: 'show', elements: elementsToHide }); // Action to undo: show these elements
}
} else { // Normal Click
// Hide the clicked element.
Printliminator.hideElements(clickedElement);
Printliminator.actionsHistory.push({ type: 'show', elements: clickedElement }); // Action to undo: show this element
}
},
/**
* Highlights an element when the mouse moves over it.
* @param {MouseEvent} event - The mouse event object.
*/
highlightElementOnMouseOver: function(event) {
if (!Printliminator.isActive || event.target === document.body || Printliminator.isElementStyledWith(event.target, Printliminator.config.hiddenClass)) {
return; // Tool not active, or hovering body, or element is already hidden.
}
Printliminator.applyCssClass(event.target, Printliminator.config.highlightClass);
},
/**
* Removes the highlight from an element when the mouse moves out of it.
* @param {MouseEvent} event - The mouse event object.
*/
removeHighlightOnMouseOut: function(event) {
// No need to check isActive; removing a non-existent class is harmless.
Printliminator.removeHighlightFromElement(event.target);
},
/**
* Handles keyboard shortcuts like Backspace (undo) and Escape (deactivate).
* @param {KeyboardEvent} event - The keyboard event object.
*/
handleKeyboardShortcut: function(event) {
if (!Printliminator.isActive) {
return;
}
const keyPressed = event.which || event.keyCode;
switch (keyPressed) {
case Printliminator.config.keys.undo: // Backspace
event.preventDefault(); // Stop browser from going back a page.
Printliminator.undoLastAction();
break;
case Printliminator.config.keys.deactivate: // Escape
event.preventDefault(); // Stop other Escape key actions.
Printliminator.deactivate();
break;
}
},
/**
* Undoes the last action performed (hiding, making full-width).
*/
undoLastAction: function() {
if (!Printliminator.isActive || Printliminator.actionsHistory.length === 0) {
return; // Nothing to undo or tool not active.
}
const actionToUndo = Printliminator.actionsHistory.pop(); // Get the last action from our list.
if (actionToUndo) {
if (typeof actionToUndo === 'function') {
// This was a full-width toggle, so call the stored undo function.
actionToUndo();
} else if (actionToUndo.type === 'show') {
// This was hiding elements, so now show them again.
Printliminator.showElements(actionToUndo.elements);
}
}
},
/**
* Creates and adds the custom CSS stylesheet to the page.
* This stylesheet contains rules for highlighting, hiding, and full-width elements.
*/
injectStyleSheet: function() {
if (document.getElementById(Printliminator.config.stylesheetId)) {
return; // Stylesheet already exists.
}
const cssRules = `
.${Printliminator.config.hiddenClass} { display: none !important; }
.${Printliminator.config.highlightClass} {
outline: 2px solid red !important;
cursor: default !important;
box-shadow: 0 0 8px rgba(255, 0, 0, 0.7) !important;
position: relative;
z-index: 2147483647 !important; /* Max z-index */
}
.${Printliminator.config.fullWidthClass} {
width: 100vw !important; /* Viewport width */
min-width: 100vw !important;
max-width: 100vw !important;
position: relative !important;
left: 50% !important;
transform: translateX(-50%) !important; /* Center the element */
margin-left: 0 !important; margin-right: 0 !important;
padding-left: 0 !important; padding-right: 0 !important;
float: none !important;
box-sizing: border-box !important;
z-index: 2147483646 !important; /* High, but below highlight */
}
.${Printliminator.config.highlightClass}.${Printliminator.config.fullWidthClass} {
outline-color: blue !important;
box-shadow: 0 0 8px rgba(0, 0, 255, 0.7) !important;
}
`;
const styleElement = document.createElement("style");
styleElement.id = Printliminator.config.stylesheetId;
styleElement.textContent = cssRules;
document.head.insertBefore(styleElement, document.head.firstChild);
},
/**
* Checks if an element is a valid target for editing (e.g., not a script tag, not already hidden).
* @param {HTMLElement} element - The HTML element to check.
* @returns {boolean} True if the element is valid, false otherwise.
*/
isEditableElement: function(element) {
return element &&
element.nodeType === 1 && // Is an actual element node
!Printliminator.config.ignoredElementTagNames.test(element.nodeName) &&
!Printliminator.isElementStyledWith(element, Printliminator.config.hiddenClass);
},
/**
* Finds all sibling elements of a given element.
* @param {HTMLElement} element - The element whose siblings to find.
* @returns {Array<HTMLElement>} An array of sibling elements.
*/
getSiblingElements: function(element) {
const siblings = [];
if (!element || !element.parentNode) return siblings;
let sibling = element.parentNode.firstChild;
while (sibling) {
if (sibling !== element && Printliminator.isEditableElement(sibling)) {
siblings.push(sibling);
}
sibling = sibling.nextSibling;
}
return siblings;
},
/**
* Finds elements surrounding a given element by looking at its siblings,
* then its parent's siblings, and so on, up the page structure.
* Used for the "Alt+Click to keep only" feature.
* @param {HTMLElement} element - The central element.
* @returns {Array<HTMLElement>} An array of surrounding elements to potentially hide.
*/
getSurroundingElements: function(element) {
let elementsToConsiderHiding = [];
let currentElement = element;
while (currentElement && currentElement.parentNode && currentElement.nodeName.toUpperCase() !== 'BODY') {
elementsToConsiderHiding = elementsToConsiderHiding.concat(Printliminator.getSiblingElements(currentElement));
currentElement = currentElement.parentNode;
if (!currentElement || currentElement === document.documentElement) break;
}
// Filter out any elements that might themselves contain the original target element.
return elementsToConsiderHiding.filter(el => el && !el.contains(element) && Printliminator.isEditableElement(el));
},
/**
* Hides one or more elements by applying the hidden CSS class.
* @param {HTMLElement|Array<HTMLElement>} elements - A single element or an array of elements to hide.
*/
hideElements: function(elements) {
if (!elements) return;
const elementsArray = Array.isArray(elements) ? elements : [elements];
elementsArray.forEach(el => {
if (Printliminator.isEditableElement(el)) { // Double check before hiding
Printliminator.applyCssClass(el, Printliminator.config.hiddenClass);
}
});
},
/**
* Shows one or more elements by removing the hidden CSS class.
* @param {HTMLElement|Array<HTMLElement>} elements - A single element or an array of elements to show.
*/
showElements: function(elements) {
if (!elements) return;
const elementsArray = Array.isArray(elements) ? elements : [elements];
elementsArray.forEach(el => Printliminator.removeCssClass(el, Printliminator.config.hiddenClass));
},
/** Helper: Adds a CSS class to an element. Uses modern `classList`. */
applyCssClass: function(element, cssClass) {
if (element && element.classList) {
element.classList.add(cssClass);
}
},
/** Helper: Removes a CSS class from an element. Uses modern `classList`. */
removeCssClass: function(element, cssClass) {
if (element && element.classList) {
element.classList.remove(cssClass);
}
},
/** Helper: Checks if an element has a specific CSS class. Uses modern `classList`. */
isElementStyledWith: function(element, cssClass) {
return element && element.classList && element.classList.contains(cssClass);
},
/** Helper: Removes the highlight class from a specific element. */
removeHighlightFromElement: function(element) {
if (element && Printliminator.isElementStyledWith(element, Printliminator.config.highlightClass)) {
Printliminator.removeCssClass(element, Printliminator.config.highlightClass);
}
},
/** Helper: Removes highlight from all currently highlighted elements on the page. */
removeAllHighlights: function() {
const highlightedElements = document.querySelectorAll('.' + Printliminator.config.highlightClass);
highlightedElements.forEach(el => Printliminator.removeHighlightFromElement(el));
},
/**
* Helper: Attaches an event listener to an element.
* @param {EventTarget} element - The element to listen on (e.g., document.body).
* @param {string} eventType - The type of event (e.g., "click", "mouseover").
* @param {Function} callbackFunction - The function to call when the event occurs.
* @param {boolean} [useCapturePhase=false] - Whether to listen during the capture phase.
*/
listenTo: function(element, eventType, callbackFunction, useCapturePhase = false) {
if (element) {
element.addEventListener(eventType, callbackFunction, useCapturePhase);
}
},
/**
* Helper: Removes an event listener from an element.
* @param {EventTarget} element - The element to stop listening on.
* @param {string} eventType - The type of event.
* @param {Function} callbackFunction - The function that was originally attached.
* @param {boolean} [useCapturePhase=false] - Must match the useCapture value from addEventListener.
*/
stopListeningTo: function(element, eventType, callbackFunction, useCapturePhase = false) {
if (element) {
element.removeEventListener(eventType, callbackFunction, useCapturePhase);
}
}
};
// Register menu commands for the userscript manager (e.g., Violentmonkey)
if (typeof GM !== 'undefined' && typeof GM.registerMenuCommand === 'function') {
GM.registerMenuCommand("Activate The Printliminator", () => {
Printliminator.activate();
}, "a"); // "a" is an example access key
GM.registerMenuCommand("Deactivate The Printliminator (ESC)", () => {
Printliminator.deactivate();
}, "d"); // "d" is an example access key
}
})();