YouTube Smaller Thumbnails

Adds 3 additional thumbnails per row

当前为 2025-04-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Smaller Thumbnails
  3. // @namespace http://greasyfork.org
  4. // @version 0.0.4
  5. // @description Adds 3 additional thumbnails per row
  6. // @author you
  7. // @license MIT
  8. // @match *://www.youtube.com/*
  9. // @match *://youtube.com/*
  10. // @run-at document-start
  11. // @grant none
  12. // ==/UserScript==
  13. (function() {
  14. 'use strict';
  15. const DELTA = 3; // Amount of columns to add.
  16. const MAX_COLUMNS = 6; // Maximum amount of columns.
  17.  
  18. const DELTA_SHORTS = 9; // Amount of columns to add.
  19. const MAX_SHORTS_COLUMNS = 12; // Maximum amount of columns for shorts.
  20.  
  21. function makeShortsSmaller() {
  22. var style = document.createElement('style');
  23. style.innerText = `
  24. ytd-rich-item-renderer[is-slim-media] {
  25. width: 10% !important;
  26. }
  27. `;
  28. document.body.appendChild(style);
  29. console.log('[YouTube Smaller Thumbnails] Reduced shorts size')
  30. }
  31.  
  32. document.addEventListener("DOMContentLoaded", makeShortsSmaller);
  33. document.addEventListener("load", makeShortsSmaller);
  34.  
  35.  
  36. function installStyle(contents) {
  37. var style = document.createElement('style');
  38. style.innerHTML = contents;
  39. document.body.appendChild(style);
  40. }
  41.  
  42. function getTargetValue(currentValue) {
  43. let newValue = currentValue + DELTA;
  44.  
  45. if (newValue > MAX_COLUMNS) {
  46. newValue = MAX_COLUMNS;
  47. }
  48.  
  49. return newValue;
  50. }
  51.  
  52. function getShortsTargetValue(currentValue) {
  53. let newValue = currentValue + DELTA_SHORTS;
  54.  
  55. if (newValue > MAX_SHORTS_COLUMNS) {
  56. newValue = MAX_SHORTS_COLUMNS;
  57. }
  58.  
  59. return newValue;
  60. }
  61.  
  62. function isShorts(itemElement) {
  63. return null !== itemElement.getAttribute('is-slim-media')
  64. }
  65.  
  66. function modifyGridStyle(gridElement) {
  67. const currentStyle = gridElement.getAttribute('style');
  68. if (!currentStyle) {
  69. return;
  70. }
  71.  
  72. const itemsPerRowMatch = currentStyle.match(/--ytd-rich-grid-items-per-row:\s*(\d+)/);
  73. if (!itemsPerRowMatch) {
  74. return;
  75. }
  76.  
  77. const currentValue = parseInt(itemsPerRowMatch[1], 10);
  78.  
  79. if (isNaN(currentValue)) {
  80. return;
  81. }
  82.  
  83. const newValue = getTargetValue(currentValue);
  84.  
  85. if (currentValue === newValue) {
  86. return;
  87. }
  88.  
  89. const newStyle = currentStyle.replace(
  90. /--ytd-rich-grid-items-per-row:\s*\d+/,
  91. `--ytd-rich-grid-items-per-row: ${newValue}`
  92. );
  93.  
  94. gridElement.setAttribute('style', newStyle);
  95. console.log(`[YouTube Smaller Thumbnails] Modified items per row: ${currentValue} -> ${newValue}`);
  96. }
  97.  
  98. function modifyItemsPerRow(itemElement) {
  99. const currentValue = parseInt(itemElement.getAttribute('items-per-row'), 10);
  100.  
  101. if (isNaN(currentValue)) {
  102. return;
  103. }
  104.  
  105. const newValue = isShorts(itemElement) ?
  106. getShortsTargetValue(currentValue) :
  107. getTargetValue(currentValue);
  108.  
  109. if (currentValue === newValue) {
  110. return;
  111. }
  112.  
  113. itemElement.setAttribute('items-per-row', newValue);
  114. console.log(`[YouTube Smaller Thumbnails] Modified items per row: ${currentValue} -> ${newValue}`);
  115. }
  116.  
  117. function modifyShortHidden(itemElement) {
  118. if (!isShorts(itemElement)) {
  119. return;
  120. }
  121.  
  122. if (null === itemElement.getAttribute('hidden')) {
  123. return
  124. }
  125.  
  126. itemElement.removeAttribute('hidden');
  127. console.log(`[YouTube Smaller Thumbnails] Modified hidden`);
  128. }
  129.  
  130. function modifyShelfRenderer(itemElement) {
  131. const currentStyle = itemElement.getAttribute('style');
  132. if (!currentStyle) {
  133. return;
  134. }
  135.  
  136. const itemsCountMatch = currentStyle.match(/--ytd-rich-shelf-items-count:\s*(\d+)/);
  137. if (!itemsCountMatch) {
  138. return;
  139. }
  140.  
  141. const currentValue = parseInt(itemElement.getAttribute('elements-per-row'), 10);
  142. if (isNaN(currentValue)) {
  143. return;
  144. }
  145.  
  146. const newValue = getShortsTargetValue(currentValue)
  147. if (currentValue === newValue) {
  148. return;
  149. }
  150.  
  151. const newStyle = currentStyle.replace(
  152. /--ytd-rich-shelf-items-count:\s*\d+/,
  153. `--ytd-rich-shelf-items-count: ${newValue}`
  154. );
  155.  
  156. itemElement.setAttribute('style', newStyle);
  157. itemElement.setAttribute('elements-per-row', newValue);
  158. console.log(`[YouTube Smaller Thumbnails] Modified elements per row: ${currentValue} -> ${newValue}`);
  159. }
  160.  
  161. function processExistingElements() {
  162. document.querySelectorAll('ytd-rich-grid-renderer').forEach(gridElement => {
  163. modifyGridStyle(gridElement);
  164. });
  165.  
  166. document.querySelectorAll('ytd-rich-item-renderer').forEach(itemElement => {
  167. modifyItemsPerRow(itemElement);
  168. modifyShortHidden(itemElement);
  169. });
  170. }
  171.  
  172. const observer = new MutationObserver((mutations) => {
  173. mutations.forEach((mutation) => {
  174. if (mutation.addedNodes && mutation.addedNodes.length > 0) {
  175. mutation.addedNodes.forEach((node) => {
  176. if (node.nodeType === Node.ELEMENT_NODE) {
  177. if (node.tagName === 'YTD-RICH-GRID-RENDERER') {
  178. modifyGridStyle(node);
  179. }
  180. if (node.tagName === 'YTD-RICH-ITEM-RENDERER') {
  181. modifyItemsPerRow(node);
  182. }
  183. if (node.tagName === 'YTD-RICH-SHELF-RENDERER') {
  184. modifyShelfRenderer(node);
  185. }
  186.  
  187. node.querySelectorAll('ytd-rich-grid-renderer').forEach(gridElement => {
  188. modifyGridStyle(gridElement);
  189. });
  190. node.querySelectorAll('ytd-rich-item-renderer').forEach(itemElement => {
  191. modifyItemsPerRow(itemElement);
  192. modifyShortHidden(itemElement);
  193. });
  194. node.querySelectorAll('ytd-rich-shelf-renderer').forEach(itemElement => {
  195. modifyShelfRenderer(itemElement);
  196. });
  197. }
  198. });
  199. }
  200.  
  201. if (mutation.type === 'attributes') {
  202. const target = mutation.target;
  203.  
  204. if (target.tagName === 'YTD-RICH-GRID-RENDERER' && mutation.attributeName === 'style') {
  205. modifyGridStyle(target);
  206. }
  207. if (target.tagName === 'YTD-RICH-ITEM-RENDERER' && mutation.attributeName === 'items-per-row') {
  208. if (mutation.attributeName === 'items-per-row') {
  209. modifyItemsPerRow(target);
  210. }
  211.  
  212. if (mutation.attributeName === 'hidden') {
  213. modifyShortHidden(target);
  214. }
  215.  
  216. }
  217. if (target.tagName === 'YTD-RICH-SHELF-RENDERER' && mutation.attributeName === 'elements-per-row') {
  218. modifyShelfRenderer(target);
  219. }
  220. }
  221. });
  222. });
  223.  
  224. function startObserver() {
  225. processExistingElements();
  226. observer.observe(document.documentElement, {
  227. childList: true,
  228. subtree: true,
  229. attributes: true,
  230. attributeFilter: ['style', 'hidden', 'items-per-row', 'elements-per-row']
  231. });
  232.  
  233. console.log('[YouTube Smaller Thumbnails] Observer started');
  234. }
  235.  
  236. if (document.readyState === 'loading') {
  237. document.addEventListener('DOMContentLoaded', startObserver);
  238. } else {
  239. startObserver();
  240. }
  241.  
  242. setInterval(processExistingElements, 3000);
  243. })();