YouTube: Return Grid Layout

유튜브 홈에서 보여지는 영상 갯수 제한을 원래대로 돌립니다

当前为 2025-05-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube: Return Grid Layout
  3. // @namespace Return YouTube Grid Layout
  4. // @version 1.3.4
  5. // @description 유튜브 홈에서 보여지는 영상 갯수 제한을 원래대로 돌립니다
  6. // @description:en Force YouTube grid layout to show 1~6 videos per row responsively, and scale up thumbnails on wide screens
  7. // @author DOGJIP
  8. // @match https://www.youtube.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  10. // @grant none
  11. // @run-at document-start
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. const STYLE_ID = 'custom-grid-style';
  18.  
  19. function getItemsPerRow(width) {
  20. if (width >= 2300) return 6;
  21. if (width >= 1900) return 5;
  22. if (width >= 1500) return 4;
  23. if (width >= 1050) return 3;
  24. if (width >= 650) return 2;
  25. return 1;
  26. }
  27.  
  28. function getItemWidth(width, itemsPerRow) {
  29. const containerWidth = width - 96;
  30. const totalMargin = 16 * (itemsPerRow - 1);
  31. const itemWidth = Math.floor((containerWidth - totalMargin) / itemsPerRow);
  32.  
  33. const maxDefault = 300;
  34. const minDefault = 220;
  35.  
  36. const maxW = Math.min(Math.max(itemWidth, maxDefault), 400);
  37. const minW = Math.min(Math.max(itemWidth - 20, minDefault), maxW - 10);
  38.  
  39. return { maxW, minW };
  40. }
  41.  
  42. function generateStyle(width) {
  43. const n = getItemsPerRow(width);
  44. const { maxW, minW } = getItemWidth(width, n);
  45.  
  46. return `
  47. /* —— 기존 유튜브 CSS 변수 유지 —— */
  48. ytd-rich-grid-renderer {
  49. --ytd-rich-grid-item-max-width: ${maxW}px !important;
  50. --ytd-rich-grid-item-min-width: ${minW}px !important;
  51. --ytd-rich-grid-row-margin: 32px !important;
  52. --ytd-rich-grid-items-per-row: ${n} !important;
  53. --ytd-rich-grid-item-margin: 16px !important;
  54. --ytd-rich-grid-posts-per-row: ${n} !important;
  55. --ytd-rich-grid-slim-items-per-row: ${n} !important;
  56. --ytd-rich-grid-game-cards-per-row: ${n} !important;
  57. --ytd-rich-grid-mini-game-cards-per-row: ${n} !important;
  58. --ytd-rich-grid-content-offset-top: 56px !important;
  59.  
  60. /* 시작 오프셋 제거 */
  61. padding-inline-start: 0 !important;
  62. --ytd-rich-grid-content-offset-left: 0 !important;
  63. }
  64.  
  65. /* —— CSS Grid: n개 컬럼 고정 + 밀도 채우기 —— */
  66. ytd-rich-grid-renderer ytd-rich-grid-row {
  67. display: grid !important;
  68. grid-auto-flow: row dense !important;
  69. /* 여기만 바뀌었습니다 ↓ */
  70. grid-template-columns: repeat(${n}, minmax(${minW}px, 1fr)) !important;
  71. column-gap: 16px !important;
  72. row-gap: 32px !important;
  73. margin: 0 !important;
  74. justify-items: stretch !important;
  75. }
  76.  
  77. /* 각 아이템이 셀 크기에 꽉 차도록 */
  78. ytd-rich-grid-renderer ytd-rich-grid-row ytd-rich-item-renderer {
  79. width: 100% !important;
  80. margin: 0 !important;
  81. }
  82.  
  83. /* 숨겨진 아이템(placeholder)만 다시 보이게 */
  84. ytd-rich-item-renderer[hidden],
  85. ytd-rich-item-renderer[hidden][is-responsive-grid] {
  86. display: block !important;
  87. }
  88.  
  89. /* 재생목록/믹스 섹션 제거 */
  90. ytd-rich-section-renderer,
  91. ytd-rich-section-renderer ytd-rich-shelf-renderer {
  92. display: none !important;
  93. }
  94. `;
  95. }
  96.  
  97. function isExcludedPage(path) {
  98. return /^\/@[^\/]+\/(?:videos|streams)(?:[\/?].*)?$/.test(path);
  99. }
  100.  
  101. function applyStyle() {
  102. if (isExcludedPage(location.pathname + location.search)) {
  103. const old = document.getElementById(STYLE_ID);
  104. if (old) old.remove();
  105. return;
  106. }
  107.  
  108. const old = document.getElementById(STYLE_ID);
  109. if (old) old.remove();
  110.  
  111. const styleEl = document.createElement('style');
  112. styleEl.id = STYLE_ID;
  113. styleEl.textContent = generateStyle(window.innerWidth);
  114. document.head.appendChild(styleEl);
  115. }
  116.  
  117. function registerNavigationListener() {
  118. window.addEventListener('yt-navigate-finish', applyStyle);
  119. const pushState = history.pushState;
  120. history.pushState = function () {
  121. pushState.apply(this, arguments);
  122. applyStyle();
  123. };
  124. window.addEventListener('popstate', applyStyle);
  125. }
  126.  
  127. registerNavigationListener();
  128. document.addEventListener('DOMContentLoaded', applyStyle);
  129. window.addEventListener('resize', applyStyle);
  130. const observer = new MutationObserver(applyStyle);
  131. observer.observe(document.body, { childList: true, subtree: true });
  132. })();