YouTube Smaller Thumbnails

Adds 3 additional thumbnails per row

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

  1. // ==UserScript==
  2. // @name YouTube Smaller Thumbnails
  3. // @namespace http://greasyfork.org
  4. // @version 0.0.2
  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. function getTargetValue(currentValue) {
  19. let newValue = currentValue + DELTA;
  20.  
  21. if (newValue > MAX_COLUMNS) {
  22. newValue = MAX_COLUMNS;
  23. }
  24.  
  25. return newValue;
  26. }
  27.  
  28. function modifyGridStyle(gridElement) {
  29. const currentStyle = gridElement.getAttribute('style');
  30. if (!currentStyle) return;
  31.  
  32. const itemsPerRowMatch = currentStyle.match(/--ytd-rich-grid-items-per-row:\s*(\d+)/);
  33. if (!itemsPerRowMatch) return;
  34.  
  35. const currentValue = parseInt(itemsPerRowMatch[1], 10);
  36.  
  37. if (isNaN(currentValue)) {
  38. return;
  39. }
  40.  
  41. const newValue = getTargetValue(currentValue);
  42.  
  43. if (currentValue === newValue) {
  44. return;
  45. }
  46.  
  47. const newStyle = currentStyle.replace(
  48. /--ytd-rich-grid-items-per-row:\s*\d+/,
  49. `--ytd-rich-grid-items-per-row: ${newValue}`
  50. );
  51.  
  52. gridElement.setAttribute('style', newStyle);
  53. console.log(`[YouTube Smaller Thumbnails] Modified grid items per row: ${currentValue} -> ${newValue}`);
  54. }
  55.  
  56. function modifyItemsPerRow(itemElement) {
  57. const currentValue = parseInt(itemElement.getAttribute('items-per-row'), 10);
  58.  
  59. if (isNaN(currentValue)) {
  60. return;
  61. }
  62.  
  63. const newValue = getTargetValue(currentValue)
  64.  
  65. if (currentValue === newValue) {
  66. return;
  67. }
  68.  
  69. itemElement.setAttribute('items-per-row', newValue);
  70. console.log(`[YouTube Smaller Thumbnails] Modified item renderer items per row: ${currentValue} -> ${newValue}`);
  71. }
  72.  
  73. function processExistingElements() {
  74. document.querySelectorAll('ytd-rich-grid-renderer').forEach(gridElement => {
  75. modifyGridStyle(gridElement);
  76. });
  77.  
  78. document.querySelectorAll('ytd-rich-item-renderer').forEach(itemElement => {
  79. modifyItemsPerRow(itemElement);
  80. });
  81. }
  82.  
  83. const observer = new MutationObserver((mutations) => {
  84. mutations.forEach((mutation) => {
  85. if (mutation.addedNodes && mutation.addedNodes.length > 0) {
  86. mutation.addedNodes.forEach((node) => {
  87. if (node.nodeType === Node.ELEMENT_NODE) {
  88. if (node.tagName === 'YTD-RICH-GRID-RENDERER') {
  89. modifyGridStyle(node);
  90. }
  91. if (node.tagName === 'YTD-RICH-ITEM-RENDERER') {
  92. modifyItemsPerRow(node);
  93. }
  94.  
  95. node.querySelectorAll('ytd-rich-grid-renderer').forEach(gridElement => {
  96. modifyGridStyle(gridElement);
  97. });
  98. node.querySelectorAll('ytd-rich-item-renderer').forEach(itemElement => {
  99. modifyItemsPerRow(itemElement);
  100. });
  101. }
  102. });
  103. }
  104.  
  105. if (mutation.type === 'attributes') {
  106. const target = mutation.target;
  107.  
  108. if (target.tagName === 'YTD-RICH-GRID-RENDERER' && mutation.attributeName === 'style') {
  109. modifyGridStyle(target);
  110. }
  111. if (target.tagName === 'YTD-RICH-ITEM-RENDERER' && mutation.attributeName === 'items-per-row') {
  112. modifyItemsPerRow(target);
  113. }
  114. }
  115. });
  116. });
  117.  
  118. function startObserver() {
  119. processExistingElements();
  120. observer.observe(document.documentElement, {
  121. childList: true,
  122. subtree: true,
  123. attributes: true,
  124. attributeFilter: ['style', 'items-per-row']
  125. });
  126.  
  127. console.log('[YouTube Smaller Thumbnails] Observer started');
  128. }
  129.  
  130. if (document.readyState === 'loading') {
  131. document.addEventListener('DOMContentLoaded', startObserver);
  132. } else {
  133. startObserver();
  134. }
  135.  
  136. setInterval(processExistingElements, 3000);
  137. })();