Last active
April 24, 2025 21:54
-
-
Save glektarssza/4ad84d315260adae6649d46ebbcd7852 to your computer and use it in GitHub Desktop.
YouTube Anti-20th Birthday Changes Greasemonkey Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name YouTube Anti-20th Birthday Limited Item Per Row Script | |
// @version 0.0.3 | |
// @description Increase the number of videos per row on the YouTube home page in opposition to their 20th birthday changes. | |
// @author G'lek Tarssza | |
// @copyright Copyright (c) 2025 G'lek Tarssza | |
// @namespace https://glektarssza.com/ | |
// @source https://gist.github.com/glektarssza/4ad84d315260adae6649d46ebbcd7852 | |
// @updateURL https://gist.github.com/glektarssza/4ad84d315260adae6649d46ebbcd7852/raw/anti-youtube-birthday-gm.js | |
// @downloadURL https://gist.github.com/glektarssza/4ad84d315260adae6649d46ebbcd7852/raw/anti-youtube-birthday-gm.js | |
// @supportURL https://gist.github.com/glektarssza/4ad84d315260adae6649d46ebbcd7852 | |
// @match https://www.youtube.com/* | |
// @icon https://www.google.com/s2/favicons?sz=16&domain=youtube.com | |
// @icon64 https://www.google.com/s2/favicons?sz=64&domain=youtube.com | |
// @grant unsafeWindow | |
// @noframes | |
// ==/UserScript== | |
/******************** | |
* SCRIPT CONSTANTS * | |
********************/ | |
/** | |
* A list of DOM selectors to operate on and the operation(s) to perform on them. | |
*/ | |
const HTML_NODE_ADJUSTMENTS = { | |
'ytd-rich-grid-renderer': (elem, itemCount) => { | |
elem.style.setProperty('--ytd-rich-grid-items-per-row', itemCount); | |
elem.style.setProperty('--ytd-rich-grid-posts-per-row', itemCount); | |
} | |
}; | |
/** | |
* The time to wait before handling a update events. | |
*/ | |
const DOM_UPDATE_EVENT_THROTTLE = 2000; | |
/** | |
* The time to wait before handling a resize events. | |
*/ | |
const RESIZE_EVENT_THROTTLE = 1000; | |
/** | |
* Mapping of screen widths to number of items per row. | |
* | |
* Mappings are `[Minimum Width, Maximum Width]`. | |
*/ | |
const SIZE_MAPPINGS = new Map([ | |
[[992, Infinity], '4'], | |
[[480, 768], '3'], | |
[[0, 480], '2'] | |
]); | |
/** | |
* A simple routine that returns a wrapper function that can be used to rate | |
* limit how often the passed in function is called. | |
* | |
* @param {Function} func The function to rate limit the calling of. | |
* @param {number} timeout The duration to wait before calling the provided | |
* function. Additional calls during this timeout will reset the timeout. | |
* | |
* @returns {Function} A function that can be used to initiate the timeout. | |
* Accepts the same inputs as the provided function. | |
*/ | |
function debounce(func, timeout) { | |
const inputBuffer = []; | |
let timer = null; | |
return (...inputs) => { | |
if (timer !== null) { | |
clearTimeout(timer); | |
timer = null; | |
} | |
inputBuffer[inputBuffer.length] = inputs; | |
timer = setTimeout(() => { | |
while (inputBuffer.length > 0) { | |
func.apply(this, inputBuffer.shift()); | |
} | |
}, timeout); | |
}; | |
} | |
/** | |
* Update the items per row property of the YouTube DOM. | |
* | |
* @param {HTMLElement} htmlNode The HTML element to update. | |
* @param {Function} action The action to apply to the node. | |
*/ | |
function updateHtmlNode(htmlNode, action) { | |
const sizeEntries = Array.from(SIZE_MAPPINGS.entries()); | |
sizeEntries.sort((a, b) => b[0][0] - a[0][0]); | |
for (const [[minSize, maxSize], count] of sizeEntries) { | |
if ( | |
unsafeWindow.innerWidth >= minSize && | |
unsafeWindow.innerWidth < maxSize | |
) { | |
action(htmlNode, count); | |
break; | |
} | |
} | |
} | |
(function () { | |
'use strict'; | |
//-- DOM update handling | |
const updateHandler = debounce( | |
(mutations) => | |
Object.keys(HTML_NODE_ADJUSTMENTS).forEach((selector) => | |
Array.from(document.querySelectorAll(selector)) | |
.filter((htmlNode) => | |
mutations.some((mutation) => | |
mutation.target.contains(htmlNode) | |
) | |
) | |
.forEach((htmlNode) => | |
updateHtmlNode( | |
htmlNode, | |
HTML_NODE_ADJUSTMENTS[selector] | |
) | |
) | |
), | |
DOM_UPDATE_EVENT_THROTTLE | |
); | |
const observer = new MutationObserver(updateHandler); | |
observer.observe(document.body, {childList: true, subtree: true}); | |
//-- Window resize handling | |
const resizeHandler = debounce( | |
() => | |
Object.keys(HTML_NODE_ADJUSTMENTS).forEach((selector) => | |
document | |
.querySelectorAll(selector) | |
.forEach((htmlNode) => | |
updateHtmlNode( | |
htmlNode, | |
HTML_NODE_ADJUSTMENTS[selector] | |
) | |
) | |
), | |
RESIZE_EVENT_THROTTLE | |
); | |
window.addEventListener('resize', resizeHandler); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment