diff --git a/Chaturbate/chaturbate-thumbnails-2x.user.js b/Chaturbate/chaturbate-thumbnails-2x.user.js index c5f6ad7..e76a1fc 100644 --- a/Chaturbate/chaturbate-thumbnails-2x.user.js +++ b/Chaturbate/chaturbate-thumbnails-2x.user.js @@ -1,8 +1,8 @@ // ==UserScript== // @name Chaturbate 缩略图放大 2 倍 // @namespace https://chaturbate.com/ -// @version 0.11.0 -// @description 放大当前 Chaturbate 房间列表、发现页轮播、关注下拉与悬停预览缩略图 +// @version 0.11.3 +// @description 放大当前 Chaturbate 房间列表, 发现页轮播, 关注下拉与悬停预览缩略图 // @match https://chaturbate.com/* // @match https://*.chaturbate.com/* // @updateURL http://127.0.0.1:49234/chaturbate-thumbnails-2x.user.js @@ -14,55 +14,55 @@ // ==/UserScript== /* - * --- Microsoft Edge:脚本完全不生效时可先试 --- + * --- Microsoft Edge: 脚本完全不生效时可先试 --- * - * 1) 地址栏打开 edge://extensions,开启「开发人员模式」(Developer mode;位置依版本可能在左侧栏或页面底部)。 - * 2) 无痕窗口:Tampermonkey →「详细信息」→「在 InPrivate 中允许」。 - * 3) Tampermonkey 仪表盘确认脚本已启用;域名匹配当前站点(含 zh-hans 等语言子域)。 + * 1) 地址栏打开 edge://extensions, 开启"开发人员模式"(Developer mode; 位置依版本可能在左侧栏或页面底部). + * 2) 无痕窗口: Tampermonkey ->"详细信息"->"在 InPrivate 中允许". + * 3) Tampermonkey 仪表盘确认脚本已启用; 域名匹配当前站点(含 zh-hans 等语言子域). */ /* - * 工作原则: + * 工作原则: * - * 1) 只做“站点原始尺寸 * 倍率”的放大。 - * 不重写站点原有排版模型,不用 object-fit / aspect-ratio / 自定义行高去替代站点布局。 - * 图片宽高和卡片宽度使用 THUMBNAIL_SCALE;卡片整体高度使用 CARD_HEIGHT_SCALE。 + * 1) 只做"站点原始尺寸 * 倍率"的放大. + * 不重写站点原有排版模型, 不用 object-fit / aspect-ratio / 自定义行高去替代站点布局. + * 图片宽高和卡片宽度使用 THUMBNAIL_SCALE; 卡片整体高度使用 CARD_HEIGHT_SCALE. * - * 2) 各模块必须自己探测原始值,再乘倍率。 - * 没有探测到原始值,就不放大该模块;不拿默认尺寸猜页面。 + * 2) 各模块必须自己探测原始值, 再乘倍率. + * 没有探测到原始值, 就不放大该模块; 不拿默认尺寸猜页面. * - * 3) 延迟探测,不临时禁用样式。 - * CSS 先注入但所有模块默认关闭;等页面原始 DOM 渲染后,逐个模块探测原始尺寸,成功后才打开该模块。 - * 模块一旦探测成功就锁定,避免后续 mutation 把已经放大的尺寸当成原始值再次相乘。 + * 3) 延迟探测, 不临时禁用样式. + * CSS 先注入但所有模块默认关闭; 等页面原始 DOM 渲染后, 逐个模块探测原始尺寸, 成功后才打开该模块. + * 模块一旦探测成功就锁定, 避免后续 mutation 把已经放大的尺寸当成原始值再次相乘. * - * 4) 探测结果缓存的是“原始尺寸”,不是放大后的尺寸。 - * 缓存按模块保存;命中缓存就直接设置 CSS 变量并启用模块,未命中才延迟探测。 - * 油猴菜单可清除缓存,清除后刷新页面即可重新探测。 + * 4) 探测结果缓存的是"原始尺寸", 不是放大后的尺寸. + * 缓存按模块保存; 命中缓存就直接设置 CSS 变量并启用模块, 未命中才延迟探测. + * 油猴菜单可清除缓存, 清除后刷新页面即可重新探测. * - * 5) 顶部“关注”弹窗的 FOLLOW_DROPDOWN_SHIFT_X 只是位置微调,不属于缩略图倍率。 + * 5) 顶部"关注"弹窗的 FOLLOW_DROPDOWN_SHIFT_X 只是位置微调, 不属于缩略图倍率. * - * 6) DEBUG 只用于控制台与 window.tmThumbScaleDebug() 诊断,不应该弹窗或显示页面调试面板。 + * 6) DEBUG 只用于控制台与 window.tmThumbScaleDebug() 诊断, 不应该弹窗或显示页面调试面板. */ (function () { "use strict"; // ========== 配置项 ========== - // 图片宽高和卡片宽度倍率:2 表示按站点原始尺寸放大到 2 倍。 + // 图片宽高和卡片宽度倍率: 2 表示按站点原始尺寸放大到 2 倍. const THUMBNAIL_SCALE = 2; - // 卡片整体高度倍率:信息栏、边距等不需要和图片一样放大到 2 倍,过大会产生空白。 + // 卡片整体高度倍率: 信息栏, 边距等不需要和图片一样放大到 2 倍, 过大会产生空白. const CARD_HEIGHT_SCALE = 1.55; - // 顶部「关注」弹出层水平偏移;只调位置,不参与缩略图放大倍率。 + // 顶部"关注"弹出层水平偏移; 只调位置, 不参与缩略图放大倍率. const FOLLOW_DROPDOWN_SHIFT_X = 10; - // 调试开关:控制控制台日志、html data-tm-* 标记和 window.tmThumbScaleDebug()。 + // 调试开关: 控制控制台日志, html data-tm-* 标记和 window.tmThumbScaleDebug(). const DEBUG = true; // =========================== - const VER = "0.11.0"; - // 缓存只保存“站点原始尺寸”,不要保存乘过倍率后的尺寸。 - // 这样以后只改 THUMBNAIL_SCALE / CARD_HEIGHT_SCALE 时,可以让缓存自动失效并重新探测,避免旧倍率污染新布局。 - const CACHE_KEY = "tm-thumb-scale:size-cache:v9"; - const CACHE_SCHEMA = 9; + const VER = "0.11.3"; + // 缓存只保存"站点原始尺寸", 不要保存乘过倍率后的尺寸. + // 这样以后只改 THUMBNAIL_SCALE / CARD_HEIGHT_SCALE 时, 可以让缓存自动失效并重新探测, 避免旧倍率污染新布局. + const CACHE_KEY = "tm-thumb-scale:size-cache:v11"; + const CACHE_SCHEMA = 11; const log = (...args) => { if (DEBUG) console.log("[tm-thumb-scale]", ...args); }; @@ -76,43 +76,46 @@ const css = ` /* - * CSS 总开关说明: + * CSS 总开关说明: * - * 下面每一组规则都挂在 html[data-tm-thumb-scale-xxx="1"] 后面。 - * JS 没有探测到对应模块的原始尺寸前,不会设置这个 data 属性。 - * 这样 CSS 可以提前注入,但不会提前改变页面原始布局,延迟探测才能读到真实原始值。 + * 下面每一组规则都挂在 html[data-tm-thumb-scale-xxx="1"] 后面. + * JS 没有探测到对应模块的原始尺寸前, 不会设置这个 data 属性. + * 这样 CSS 可以提前注入, 但不会提前改变页面原始布局, 延迟探测才能读到真实原始值. */ /* 首页当前列表 */ /* - * home 模块: - * - 适用范围:主页、分类页等普通 #roomlist_root 房间列表。 - * - 探测值:卡片原始宽度。 - * - 放大方式:只放大 grid 列宽;缩略图保持 100% / auto,跟随卡片自然缩放。 - * - 注意:关注页列表单独用 followingList 模块,不和 home 共用缓存。 + * home 模块: + * - 适用范围: 主页, 分类页等普通 #roomlist_root 房间列表. + * - 探测值: 卡片原始宽高 + 缩略图原始宽高. + * - 放大方式: grid 列宽按 THUMBNAIL_SCALE 放大一次; 缩略图宽度保持 100%; 缩略图高度按 THUMBNAIL_SCALE 放大. + * - 注意: 关注页列表单独用 followingList 模块, 不和 home 共用缓存. */ html[data-tm-thumb-scale-home="1"] #main #roomlist_root ul.RoomCardGrid, html[data-tm-thumb-scale-home="1"] #roomlist_root ul.RoomCardGrid, html[data-tm-thumb-scale-home="1"] #main #roomlist_root ul.list:has(li.roomCard), html[data-tm-thumb-scale-home="1"] #roomlist_root ul.list:has(li.roomCard) { - /* 改成 grid,是为了让放大后的卡片按新宽度自然换行。 */ + /* 改成 grid, 是为了让放大后的卡片按新宽度自然换行. */ display: grid !important; - /* --tm-thumb-home-width 来自当前页面原始卡片宽度,JS 写入时已经乘过倍率。 */ + /* --tm-thumb-home-width 来自当前页面原始卡片宽度, JS 写入时已经乘过倍率. */ grid-template-columns: repeat(auto-fill, minmax(var(--tm-thumb-home-width), 1fr)) !important; - /* 保留一个稳定间距,避免放大后卡片互相贴住。 */ + /* 保留一个稳定间距, 避免放大后卡片互相贴住. */ gap: 0.6em 0.75em !important; } html[data-tm-thumb-scale-home="1"] #roomlist_root ul.RoomCardGrid > li.RoomCard, html[data-tm-thumb-scale-home="1"] #roomlist_root ul.list:has(li.roomCard) li.roomCard { - /* 列宽交给 grid 控制,清掉站点原本写死的 li 宽度限制。 */ + /* 列宽交给 grid 控制, 清掉站点原本写死的 li 宽度限制. */ width: auto !important; min-width: 0 !important; max-width: none !important; + height: var(--tm-thumb-home-card-height) !important; + min-height: var(--tm-thumb-home-card-height) !important; + max-height: var(--tm-thumb-home-card-height) !important; } html[data-tm-thumb-scale-home="1"] #roomlist_root ul.RoomCardGrid .RoomCardThumbnail, html[data-tm-thumb-scale-home="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail_container { width: 100% !important; - height: auto !important; + height: var(--tm-thumb-home-thumb-height) !important; display: block !important; max-width: none !important; box-sizing: border-box !important; @@ -121,8 +124,8 @@ html[data-tm-thumb-scale-home="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail_container img, html[data-tm-thumb-scale-home="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail { width: 100% !important; - height: auto !important; - /* block 可以消掉图片 inline baseline 带来的细小空隙。 */ + height: var(--tm-thumb-home-thumb-height) !important; + /* block 可以消掉图片 inline baseline 带来的细小空隙. */ display: block !important; max-width: none !important; box-sizing: border-box !important; @@ -130,13 +133,14 @@ /* 关注页当前列表 */ /* - * followingList 模块: - * - 适用范围:/followed-cams/ 关注页里的 #roomlist_root 房间列表。 - * - 为什么和 home 分开:两者 DOM 选择器很像,但站点可能给不同页面不同原始列宽。 - * - 缓存也单独存 followingList,避免首页探测值污染关注页。 - * - 和 home 一样只放大 grid 最小列宽;图片跟随卡片自然缩放。 - * - 注意:这里仍保留 minmax(..., 1fr),让站点原来的整行拉伸/对齐逻辑继续工作。 - * 不能改成固定列宽,否则宽屏下会出现大空洞,看起来比原站更乱。 + * followingList 模块: + * - 适用范围: /followed-cams/ 关注页里的 #roomlist_root 房间列表. + * - 为什么和 home 分开: 两者 DOM 选择器很像, 但站点可能给不同页面不同原始列宽. + * - 缓存也单独存 followingList, 避免首页探测值污染关注页. + * - 探测值: 卡片原始宽高 + 缩略图原始宽高. + * - 和 home 一样, grid 列宽按 THUMBNAIL_SCALE 放大一次; 缩略图宽度保持 100%; 缩略图高度按 THUMBNAIL_SCALE 放大. + * - 注意: 这里仍保留 minmax(..., 1fr), 让站点原来的整行拉伸/对齐逻辑继续工作. + * 不能改成固定列宽, 否则宽屏下会出现大空洞, 看起来比原站更乱. */ html[data-tm-thumb-scale-following-list="1"] #main #roomlist_root ul.RoomCardGrid, html[data-tm-thumb-scale-following-list="1"] #roomlist_root ul.RoomCardGrid, @@ -151,11 +155,14 @@ width: auto !important; min-width: 0 !important; max-width: none !important; + height: var(--tm-thumb-following-list-card-height) !important; + min-height: var(--tm-thumb-following-list-card-height) !important; + max-height: var(--tm-thumb-following-list-card-height) !important; } html[data-tm-thumb-scale-following-list="1"] #roomlist_root ul.RoomCardGrid .RoomCardThumbnail, html[data-tm-thumb-scale-following-list="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail_container { width: 100% !important; - height: auto !important; + height: var(--tm-thumb-following-list-thumb-height) !important; display: block !important; max-width: none !important; box-sizing: border-box !important; @@ -164,17 +171,17 @@ html[data-tm-thumb-scale-following-list="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail_container img, html[data-tm-thumb-scale-following-list="1"] #roomlist_root ul.list:has(li.roomCard) .room_thumbnail { width: 100% !important; - height: auto !important; + height: var(--tm-thumb-following-list-thumb-height) !important; display: block !important; max-width: none !important; box-sizing: border-box !important; } - /* 直播间页底部「更多这样的直播间」 */ + /* 直播间页底部"更多这样的直播间" */ /* - * related 模块: - * - 适用范围:直播间页面底部“更多这样的房间”。 - * - 探测值:卡片原始宽高 + 缩略图原始宽高。 - * - 放大方式:卡片和缩略图分别按自己的原始宽高乘倍率,避免把 card 宽度误当 thumb 宽度。 + * related 模块: + * - 适用范围: 直播间页面底部"更多这样的房间". + * - 探测值: 卡片原始宽高 + 缩略图原始宽高. + * - 放大方式: 卡片和缩略图分别按自己的原始宽高乘倍率, 避免把 card 宽度误当 thumb 宽度. */ html[data-tm-thumb-scale-related="1"] .BaseRoomContents ul.RoomCardGrid, html[data-tm-thumb-scale-related="1"] #main.roomPage ul.list:has(li.roomCard), @@ -189,7 +196,7 @@ width: auto !important; min-width: 0 !important; max-width: none !important; - /* 卡片整体高度也要放大;只放大缩略图高度时,下方用户名/人数栏可能被压住。 */ + /* 卡片整体高度也要放大; 只放大缩略图高度时, 下方用户名/人数栏可能被压住. */ height: var(--tm-thumb-related-card-height) !important; min-height: var(--tm-thumb-related-card-height) !important; max-height: var(--tm-thumb-related-card-height) !important; @@ -197,9 +204,9 @@ html[data-tm-thumb-scale-related="1"] .BaseRoomContents ul.RoomCardGrid .RoomCardThumbnail, html[data-tm-thumb-scale-related="1"] #main.roomPage ul.list:has(li.roomCard) .room_thumbnail_container, html[data-tm-thumb-scale-related="1"] .BaseRoomContents ul.list:has(li.roomCard) .room_thumbnail_container { - /* --tm-thumb-related-thumb-width 来自原始缩略图宽度,不直接复用卡片宽度。 */ + /* --tm-thumb-related-thumb-width 来自原始缩略图宽度, 不直接复用卡片宽度. */ width: var(--tm-thumb-related-thumb-width) !important; - /* --tm-thumb-related-height 来自原始缩略图容器高度。 */ + /* --tm-thumb-related-height 来自原始缩略图容器高度. */ height: var(--tm-thumb-related-height) !important; display: block !important; max-width: none !important; @@ -210,24 +217,24 @@ html[data-tm-thumb-scale-related="1"] #main.roomPage ul.list:has(li.roomCard) .room_thumbnail, html[data-tm-thumb-scale-related="1"] .BaseRoomContents ul.list:has(li.roomCard) .room_thumbnail_container img, html[data-tm-thumb-scale-related="1"] .BaseRoomContents ul.list:has(li.roomCard) .room_thumbnail { - /* 图片宽度也按原始缩略图宽度放大,避免卡片边框/间隙被算进图片宽度。 */ + /* 图片宽度也按原始缩略图宽度放大, 避免卡片边框/间隙被算进图片宽度. */ width: var(--tm-thumb-related-thumb-width) !important; - /* 图片和容器使用同一个高度,避免图片溢出或下方信息栏错位。 */ + /* 图片和容器使用同一个高度, 避免图片溢出或下方信息栏错位. */ height: var(--tm-thumb-related-height) !important; display: block !important; max-width: none !important; box-sizing: border-box !important; } - /* 顶部「关注」下拉 */ + /* 顶部"关注"下拉 */ /* - * follow 模块: - * - 适用范围:顶部“关注”菜单弹出的房间列表。 - * - 探测值:弹窗中单个房间卡片宽高 + 图片宽高。 - * - 放大方式:卡片和图片各按自己的原始宽高乘倍率;弹窗总宽度按 2 列计算。 + * follow 模块: + * - 适用范围: 顶部"关注"菜单弹出的房间列表. + * - 探测值: 弹窗中单个房间卡片宽高 + 图片宽高. + * - 放大方式: 卡片和图片各按自己的原始宽高乘倍率; 弹窗总宽度按 2 列计算. */ html[data-tm-thumb-scale-follow="1"] .FollowedDropdown { - /* 96vw 防止弹窗在窄窗口里横向溢出。 */ + /* 96vw 防止弹窗在窄窗口里横向溢出. */ width: min(96vw, var(--tm-thumb-follow-width)) !important; min-width: min(96vw, var(--tm-thumb-follow-width)) !important; max-width: min(96vw, var(--tm-thumb-follow-width)) !important; @@ -241,7 +248,7 @@ } html[data-tm-thumb-scale-follow="1"] .FollowedDropdown__rooms { display: grid !important; - /* 当前设计保持两列,列宽来自原始卡片宽度 * 倍率。 */ + /* 当前设计保持两列, 列宽来自原始卡片宽度 * 倍率. */ grid-template-columns: repeat(2, minmax(var(--tm-thumb-follow-card-width), 1fr)) !important; grid-gap: 0.6em 0.75em !important; } @@ -266,43 +273,43 @@ box-sizing: border-box !important; } html[data-tm-thumb-scale-follow="1"] .HeaderNavBar__following .FollowedDropdown { - /* 只做用户要求的右移微调,不参与缩略图尺寸计算。 */ + /* 只做用户要求的右移微调, 不参与缩略图尺寸计算. */ transform: translateX(${FOLLOW_DROPDOWN_SHIFT_X}px) !important; } /* - * 悬停「关注」星星等出现的浮层预览(多为 react-tooltip,portal 挂在 body) - * 仅放大直播间缩略图域名,避免误伤图标类小图 + * 悬停"关注"星星等出现的浮层预览(多为 react-tooltip, portal 挂在 body) + * 仅放大直播间缩略图域名, 避免误伤图标类小图 */ html[data-tm-thumb-scale-tooltip="1"] body > div[id^="react-tooltip"] img[src*="live.mmcdn.com"], html[data-tm-thumb-scale-tooltip="1"] body > div[id^="react-tooltip"] img[src*="thumb.live.mmcdn"], html[data-tm-thumb-scale-tooltip="1"] [data-floating-ui-portal] img[src*="live.mmcdn.com"] { - /* tooltip 模块只改直播缩略图域名,避免误伤页面里的小图标。 */ + /* tooltip 模块只改直播缩略图域名, 避免误伤页面里的小图标. */ width: var(--tm-thumb-tooltip-width) !important; max-width: min(96vw, var(--tm-thumb-tooltip-width)) !important; height: var(--tm-thumb-tooltip-height) !important; display: block !important; box-sizing: border-box !important; } - /* 发现页:轮播缩略图 + 容器高度 */ + /* 发现页: 轮播缩略图 + 容器高度 */ /* - * 发现页模块: - * - 适用范围:发现页轮播,比如“最受欢迎”。 - * - 探测值: - * 1. li 卡片宽高; - * 2. 缩略图容器高度; - * 3. triple/double/single 三种轮播 ul 高度; - * 4. 对应箭头容器高度。 - * - 放大方式:图片宽高和卡片宽度乘 THUMBNAIL_SCALE;卡片高度、轮播容器高度、箭头高度乘 CARD_HEIGHT_SCALE。 - * - 启用条件:全部值都探测到才打开发现页放大,缺一项就不动,避免局部放大导致错位。 + * 发现页模块: + * - 适用范围: 发现页轮播, 比如"最受欢迎". + * - 探测值: + * 1. li 卡片宽高; + * 2. 缩略图容器高度; + * 3. triple/double/single 三种轮播 ul 高度; + * 4. 对应箭头容器高度. + * - 放大方式: 图片宽高和卡片宽度乘 THUMBNAIL_SCALE; 卡片高度, 轮播容器高度, 箭头高度乘 CARD_HEIGHT_SCALE. + * - 启用条件: 全部值都探测到才打开发现页放大, 缺一项就不动, 避免局部放大导致错位. */ html[data-tm-thumb-scale-discover="1"] .carousel-root .triple-rows ul.list, html[data-tm-thumb-scale-discover="1"] #discover_root .triple-rows ul.list { - /* 三行轮播的整体高度按 CARD_HEIGHT_SCALE 放大,避免卡片下方出现过多空白。 */ + /* 三行轮播的整体高度按 CARD_HEIGHT_SCALE 放大, 避免卡片下方出现过多空白. */ height: var(--tm-thumb-discover-triple-ul) !important; } html[data-tm-thumb-scale-discover="1"] .carousel-root .triple-rows .carousel-arrow-container, html[data-tm-thumb-scale-discover="1"] #discover_root .triple-rows .carousel-arrow-container { - /* 箭头容器也按 CARD_HEIGHT_SCALE 放大,否则右侧箭头会和轮播高度不一致。 */ + /* 箭头容器也按 CARD_HEIGHT_SCALE 放大, 否则右侧箭头会和轮播高度不一致. */ height: var(--tm-thumb-discover-triple-arrow) !important; } html[data-tm-thumb-scale-discover="1"] .carousel-root .double-rows ul.list, @@ -323,15 +330,15 @@ } html[data-tm-thumb-scale-discover="1"] .carousel-root .room-list-carousel ul.list > li, html[data-tm-thumb-scale-discover="1"] #discover_root .room-list-carousel ul.list > li { - /* 卡片宽度来自原始 li 宽度,例如日志里出现过 182 -> 364。 */ + /* 卡片宽度来自原始 li 宽度, 例如日志里出现过 182 -> 364. */ width: var(--tm-thumb-discover-width) !important; min-width: var(--tm-thumb-discover-width) !important; max-width: var(--tm-thumb-discover-width) !important; - /* 卡片整体高度也必须放大,否则用户名/人数信息区会压住下一行。 */ + /* 卡片整体高度也必须放大, 否则用户名/人数信息区会压住下一行. */ height: var(--tm-thumb-discover-card-height) !important; min-height: var(--tm-thumb-discover-card-height) !important; max-height: var(--tm-thumb-discover-card-height) !important; - /* flex-basis 和 width 保持一致,轮播横向滑动距离才稳定。 */ + /* flex-basis 和 width 保持一致, 轮播横向滑动距离才稳定. */ flex: 0 0 var(--tm-thumb-discover-width) !important; box-sizing: border-box !important; vertical-align: top !important; @@ -348,7 +355,7 @@ html[data-tm-thumb-scale-discover="1"] #discover_root .room-list-carousel .room_thumbnail_container img, html[data-tm-thumb-scale-discover="1"] #discover_root .room-list-carousel .room_thumbnail { width: var(--tm-thumb-discover-thumb-width) !important; - /* 缩略图高度来自原始 thumb 高度,例如 101 -> 202。 */ + /* 缩略图高度来自原始 thumb 高度, 例如 101 -> 202. */ height: var(--tm-thumb-discover-height) !important; display: block !important; max-width: none !important; @@ -357,23 +364,23 @@ `; const STYLE_ID = "tm-thumb-scale-style"; - // 只观察一次 head,避免重复创建 MutationObserver。 + // 只观察一次 head, 避免重复创建 MutationObserver. let observerStarted = false; - // 只观察一次 body,页面内容变化时用于捕捉动态弹窗/tooltip。 + // 只观察一次 body, 页面内容变化时用于捕捉动态弹窗/tooltip. let pageObserverStarted = false; - // 探测防抖计时器;mutation 很多时只执行最后一次探测。 + // 探测防抖计时器; mutation 很多时只执行最后一次探测. let measureTimer = null; - // 记录样式注入次数,主要用于调试确认 GM_addStyle 是否真的执行。 + // 记录样式注入次数, 主要用于调试确认 GM_addStyle 是否真的执行. let injectCount = 0; - // 记录最后一次注入方式:GM_addStyle、手动 style、失败等。 + // 记录最后一次注入方式: GM_addStyle, 手动 style, 失败等. let lastInjectMethod = "none"; - // GM_addStyle 在某些环境不会返回可查询的 style 节点,所以单独记录逻辑状态。 + // GM_addStyle 在某些环境不会返回可查询的 style 节点, 所以单独记录逻辑状态. let styleInjected = false; - // 最近一次 window.tmThumbScaleDebug() 返回的数据。 + // 最近一次 window.tmThumbScaleDebug() 返回的数据. let lastReport = null; - // 最近一次尺寸探测结果,控制台里排错主要看这个。 + // 最近一次尺寸探测结果, 控制台里排错主要看这个. let lastMeasure = null; - // 模块锁:某模块一旦成功应用缓存或探测值,就不再重新探测。 + // 模块锁: 某模块一旦成功应用缓存或探测值, 就不再重新探测. const moduleReady = { home: false, followingList: false, @@ -383,7 +390,7 @@ discover: false, }; const setDebugAttr = (name, value) => { - // 调试属性写到 html 上,方便在 Elements 面板确认脚本是否运行。 + // 调试属性写到 html 上, 方便在 Elements 面板确认脚本是否运行. if (!DEBUG) return; try { document.documentElement.setAttribute(`data-tm-thumb-scale-${name}`, String(value)); @@ -405,14 +412,14 @@ return false; } }; - // 所有 CSS 变量都在这里统一乘倍率。调用方传入的必须是页面原始尺寸。 + // 所有 CSS 变量都在这里统一乘倍率.调用方传入的必须是页面原始尺寸. const setVar = (name, value) => { const style = rootStyle(); if (!style || !Number.isFinite(value) || value <= 0) return false; style.setProperty(name, scaledPx(value)); return true; }; - // 只给 card 整体高度使用。图片宽高仍然走 setVar(),继续乘 THUMBNAIL_SCALE。 + // 只给 card 整体高度使用.图片宽高仍然走 setVar(), 继续乘 THUMBNAIL_SCALE. const setCardHeightVar = (name, value) => { const style = rootStyle(); if (!style || !Number.isFinite(value) || value <= 0) return false; @@ -425,8 +432,8 @@ style.setProperty(name, scaledCardHeightFromThumbPx(cardHeight, thumbHeight)); return true; }; - // 每个模块的 CSS 都由 html[data-tm-thumb-scale-模块名="1"] 控制。 - // 未探测到原始值时不设置开关,对应模块完全不放大,避免用错误尺寸猜页面。 + // 每个模块的 CSS 都由 html[data-tm-thumb-scale-模块名="1"] 控制. + // 未探测到原始值时不设置开关, 对应模块完全不放大, 避免用错误尺寸猜页面. const setModuleReady = (name, ready) => { try { const attrName = name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`); @@ -436,7 +443,7 @@ } catch (_) {} }; const isRect = (value) => ( - // 缓存读取时做最低限度校验,防止坏数据进入 CSS 变量。 + // 缓存读取时做最低限度校验, 防止坏数据进入 CSS 变量. value && Number.isFinite(value.width) && Number.isFinite(value.height) @@ -444,15 +451,15 @@ && value.height > 0 ); const cloneRect = (value) => (isRect(value) ? { - // 缓存不需要亚像素无限精度,保留两位小数足够复现布局。 + // 缓存不需要亚像素无限精度, 保留两位小数足够复现布局. width: Math.round(value.width * 100) / 100, height: Math.round(value.height * 100) / 100, } : null); const readCache = () => { try { - // localStorage 可能被浏览器策略禁用,所以所有缓存操作都要 try/catch。 + // localStorage 可能被浏览器策略禁用, 所以所有缓存操作都要 try/catch. if (typeof localStorage === "undefined") return null; - // 缓存格式错误、倍率不同、结构版本不同,都视为无缓存。 + // 缓存格式错误, 倍率不同, 结构版本不同, 都视为无缓存. const parsed = JSON.parse(localStorage.getItem(CACHE_KEY) || "null"); if ( !parsed @@ -468,7 +475,7 @@ }; const writeCache = (cache) => { try { - // 写入失败不影响脚本运行,只是下次还会重新探测。 + // 写入失败不影响脚本运行, 只是下次还会重新探测. if (typeof localStorage === "undefined") return false; localStorage.setItem(CACHE_KEY, JSON.stringify(cache)); return true; @@ -477,8 +484,8 @@ } }; const updateCacheModule = (name, data) => { - // 模块级缓存:发现页、首页、关注下拉等互不影响。 - // 只更新当前模块,保留其它模块已经探测好的原始尺寸。 + // 模块级缓存: 发现页, 首页, 关注下拉等互不影响. + // 只更新当前模块, 保留其它模块已经探测好的原始尺寸. const cache = readCache() || { schema: CACHE_SCHEMA, scale: THUMBNAIL_SCALE, @@ -492,14 +499,14 @@ }; const clearCache = () => { try { - // 油猴菜单调用这里;清完后不强制刷新,由用户自己刷新页面重新探测。 + // 油猴菜单调用这里; 清完后不强制刷新, 由用户自己刷新页面重新探测. if (typeof localStorage !== "undefined") localStorage.removeItem(CACHE_KEY); } catch (_) {} log("cache cleared; refresh page to detect sizes again"); }; const registerMenu = () => { try { - // GM_registerMenuCommand 需要在 header 里 @grant;没有该 API 时静默跳过。 + // GM_registerMenuCommand 需要在 header 里 @grant; 没有该 API 时静默跳过. if (typeof GM_registerMenuCommand === "function") { GM_registerMenuCommand("清除 Chaturbate 缩略图探测缓存", clearCache); } @@ -508,12 +515,20 @@ const applyCachedModule = (name, data) => { if (!data || moduleReady[name]) return false; - // 缓存命中时直接写 CSS 变量并打开模块开关。 - // 这里仍然通过 setVar() 乘倍率,因此缓存中始终保持原始尺寸。 - if (name === "home" && !isFollowingListPage() && isRect(data.homeCard)) { - moduleReady.home = setVar("--tm-thumb-home-width", data.homeCard.width); - } else if (name === "followingList" && isFollowingListPage() && isRect(data.followingListCard)) { - moduleReady.followingList = setVar("--tm-thumb-following-list-width", data.followingListCard.width); + // 缓存命中时直接写 CSS 变量并打开模块开关. + // 这里仍然通过 setVar() 乘倍率, 因此缓存中始终保持原始尺寸. + if (name === "home" && !isFollowingListPage() && isRect(data.homeCard) && isRect(data.homeThumb)) { + moduleReady.home = [ + setVar("--tm-thumb-home-width", data.homeCard.width), + setCardHeightFromThumbVar("--tm-thumb-home-card-height", data.homeCard.height, data.homeThumb.height), + setVar("--tm-thumb-home-thumb-height", data.homeThumb.height), + ].every(Boolean); + } else if (name === "followingList" && isFollowingListPage() && isRect(data.followingListCard) && isRect(data.followingListThumb)) { + moduleReady.followingList = [ + setVar("--tm-thumb-following-list-width", data.followingListCard.width), + setCardHeightFromThumbVar("--tm-thumb-following-list-card-height", data.followingListCard.height, data.followingListThumb.height), + setVar("--tm-thumb-following-list-thumb-height", data.followingListThumb.height), + ].every(Boolean); } else if (name === "related" && isRect(data.relatedCard) && isRect(data.relatedThumb)) { const widthReady = setVar("--tm-thumb-related-width", data.relatedCard.width); const cardHeightReady = setCardHeightVar("--tm-thumb-related-card-height", data.relatedCard.height); @@ -582,18 +597,18 @@ return Object.values(applied).some(Boolean); }; const rectOf = (el) => { - // getBoundingClientRect 能拿到 CSS 布局后的实际渲染尺寸。 + // getBoundingClientRect 能拿到 CSS 布局后的实际渲染尺寸. if (!el || !el.getBoundingClientRect) return null; const rect = el.getBoundingClientRect(); const width = rect.width || el.offsetWidth || 0; const height = rect.height || el.offsetHeight || 0; - // 太小的元素通常是图标、占位或尚未布局完成的节点,不作为缩略图基准。 + // 太小的元素通常是图标, 占位或尚未布局完成的节点, 不作为缩略图基准. if (width < 20 && height < 20) return null; return { width, height }; }; const firstRect = (selector) => { try { - // 同一类列表里取第一个有尺寸的节点即可,因为同模块卡片原始尺寸应一致。 + // 同一类列表里取第一个有尺寸的节点即可, 因为同模块卡片原始尺寸应一致. if (!document.querySelectorAll) return null; const nodes = document.querySelectorAll(selector); for (const node of nodes) { @@ -605,29 +620,31 @@ }; const detectAndApplySizes = (stage = "detect") => { /* - * 探测策略: + * 探测策略: * - * 1) CSS 已经注入,但各模块默认关闭,所以页面先按站点原始布局渲染。 - * 2) 延迟/MutationObserver 触发后,读取还没 ready 的模块尺寸。 - * 3) 某个模块探测成功后,写入 CSS 变量、打开模块开关、写入缓存,并把 moduleReady 锁住。 - * 4) 锁住后的模块后续不再探测,避免把已放大的尺寸再次当成原始尺寸。 + * 1) CSS 已经注入, 但各模块默认关闭, 所以页面先按站点原始布局渲染. + * 2) 延迟/MutationObserver 触发后, 读取还没 ready 的模块尺寸. + * 3) 某个模块探测成功后, 写入 CSS 变量, 打开模块开关, 写入缓存, 并把 moduleReady 锁住. + * 4) 锁住后的模块后续不再探测, 避免把已放大的尺寸再次当成原始尺寸. */ const followingListPage = isFollowingListPage(); const measured = { - // 首页普通房间列表:只探测卡片宽度。缩略图跟随卡片自然缩放,避免重复放大。 + // 首页普通房间列表: 卡片宽高 + 缩略图宽高四项独立探测. homeCard: moduleReady.home || followingListPage ? null : firstRect("#roomlist_root li.RoomCard, #roomlist_root li.roomCard"), - // 关注页房间列表:只探测卡片宽度;独立缓存,避免和首页混用。 + homeThumb: moduleReady.home || followingListPage ? null : firstRect("#roomlist_root .RoomCardThumbnail, #roomlist_root .room_thumbnail_container"), + // 关注页房间列表: 选择器和首页相似, 但独立探测, 独立缓存, 独立 CSS 变量. followingListCard: moduleReady.followingList || !followingListPage ? null : firstRect("#roomlist_root li.RoomCard, #roomlist_root li.roomCard"), - // 频道页“更多这样的房间”:卡片宽高和缩略图宽高都要探测,否则放大后行高容易不齐。 + followingListThumb: moduleReady.followingList || !followingListPage ? null : firstRect("#roomlist_root .RoomCardThumbnail, #roomlist_root .room_thumbnail_container"), + // 频道页"更多这样的房间": 卡片宽高和缩略图宽高都要探测, 否则放大后行高容易不齐. relatedCard: moduleReady.related ? null : firstRect(".BaseRoomContents ul.RoomCardGrid > li.RoomCard, #main.roomPage ul.list:has(li.roomCard) li.roomCard, .BaseRoomContents ul.list:has(li.roomCard) li.roomCard"), relatedThumb: moduleReady.related ? null : firstRect(".BaseRoomContents ul.RoomCardGrid .RoomCardThumbnail, #main.roomPage ul.list:has(li.roomCard) .room_thumbnail_container, .BaseRoomContents ul.list:has(li.roomCard) .room_thumbnail_container"), - // 顶部“关注”弹窗:通常弹出后才有 DOM,靠 mutation 触发探测。 + // 顶部"关注"弹窗: 通常弹出后才有 DOM, 靠 mutation 触发探测. followCard: moduleReady.follow ? null : firstRect(".FollowedDropdown__room, .FollowedDropdown__room-image"), followThumb: moduleReady.follow ? null : firstRect(".FollowedDropdown__room-image"), - // 悬浮预览:tooltip 是 portal 动态插入,也靠 mutation 捕捉。 + // 悬浮预览: tooltip 是 portal 动态插入, 也靠 mutation 捕捉. tooltipThumb: moduleReady.tooltip ? null : firstRect('body > div[id^="react-tooltip"] img[src*="live.mmcdn.com"], body > div[id^="react-tooltip"] img[src*="thumb.live.mmcdn"], [data-floating-ui-portal] img[src*="live.mmcdn.com"]'), - // 发现页 carousel:必须同时探测 card、thumb、行容器和箭头高度,全部成功后才启用。 - // 这样“最受欢迎”等多行轮播能保持站点原来的对齐关系,只是整体按倍率放大。 + // 发现页 carousel: 必须同时探测 card, thumb, 行容器和箭头高度, 全部成功后才启用. + // 这样"最受欢迎"等多行轮播能保持站点原来的对齐关系, 只是整体按倍率放大. discoverCard: moduleReady.discover ? null : firstRect(".carousel-root .room-list-carousel ul.list > li, #discover_root .room-list-carousel ul.list > li"), discoverThumb: moduleReady.discover ? null : firstRect(".carousel-root .room-list-carousel .room_thumbnail_container, #discover_root .room-list-carousel .room_thumbnail_container"), discoverTripleUl: moduleReady.discover ? null : firstRect(".carousel-root .triple-rows ul.list, #discover_root .triple-rows ul.list"), @@ -639,29 +656,39 @@ }; if (!moduleReady.home && !followingListPage) { - // home 只依赖卡片宽度;成功后立刻缓存,后续普通列表页面可直接套用。 - moduleReady.home = setVar("--tm-thumb-home-width", measured.homeCard && measured.homeCard.width); + // home 依赖 card/thumb 各自宽高; 成功后立刻缓存, 后续普通列表页面可直接套用. + moduleReady.home = [ + setVar("--tm-thumb-home-width", measured.homeCard && measured.homeCard.width), + setCardHeightFromThumbVar("--tm-thumb-home-card-height", measured.homeCard && measured.homeCard.height, measured.homeThumb && measured.homeThumb.height), + setVar("--tm-thumb-home-thumb-height", measured.homeThumb && measured.homeThumb.height), + ].every(Boolean); setModuleReady("home", moduleReady.home); if (moduleReady.home) { updateCacheModule("home", { homeCard: cloneRect(measured.homeCard), + homeThumb: cloneRect(measured.homeThumb), }); } } if (!moduleReady.followingList && followingListPage) { - // followingList 单独缓存,避免关注页拿到首页的原始宽度。 - moduleReady.followingList = setVar("--tm-thumb-following-list-width", measured.followingListCard && measured.followingListCard.width); + // followingList 单独缓存, 避免关注页拿到首页的原始宽度. + moduleReady.followingList = [ + setVar("--tm-thumb-following-list-width", measured.followingListCard && measured.followingListCard.width), + setCardHeightFromThumbVar("--tm-thumb-following-list-card-height", measured.followingListCard && measured.followingListCard.height, measured.followingListThumb && measured.followingListThumb.height), + setVar("--tm-thumb-following-list-thumb-height", measured.followingListThumb && measured.followingListThumb.height), + ].every(Boolean); setModuleReady("followingList", moduleReady.followingList); if (moduleReady.followingList) { updateCacheModule("followingList", { followingListCard: cloneRect(measured.followingListCard), + followingListThumb: cloneRect(measured.followingListThumb), }); } } if (!moduleReady.related) { - // related 同时依赖卡片宽度、卡片高度、缩略图宽度和缩略图高度;四者缺一不可。 + // related 同时依赖卡片宽度, 卡片高度, 缩略图宽度和缩略图高度; 四者缺一不可. const relatedWidthReady = setVar("--tm-thumb-related-width", measured.relatedCard && measured.relatedCard.width); const relatedCardHeightReady = setCardHeightVar("--tm-thumb-related-card-height", measured.relatedCard && measured.relatedCard.height); const relatedThumbWidthReady = setVar("--tm-thumb-related-thumb-width", measured.relatedThumb && measured.relatedThumb.width); @@ -677,7 +704,7 @@ } if (!moduleReady.follow) { - // follow 的总弹窗宽度按两列计算:单卡原始宽度 * 倍率 * 2 + 2em 间距余量。 + // follow 的总弹窗宽度按两列计算: 单卡原始宽度 * 倍率 * 2 + 2em 间距余量. const followReady = [ setVar("--tm-thumb-follow-card-width", measured.followCard && measured.followCard.width), setCardHeightFromThumbVar("--tm-thumb-follow-card-height", measured.followCard && measured.followCard.height, measured.followThumb && measured.followThumb.height), @@ -699,7 +726,7 @@ } if (!moduleReady.tooltip) { - // tooltip 出现时机不固定,所以通常由 body mutation 触发探测。 + // tooltip 出现时机不固定, 所以通常由 body mutation 触发探测. moduleReady.tooltip = [ setVar("--tm-thumb-tooltip-width", measured.tooltipThumb && measured.tooltipThumb.width), setVar("--tm-thumb-tooltip-height", measured.tooltipThumb && measured.tooltipThumb.height), @@ -711,7 +738,7 @@ } if (!moduleReady.discover) { - // 发现页要求更严格:少一个高度都不开启,宁可不放大,也不要错位。 + // 发现页要求更严格: 少一个高度都不开启, 宁可不放大, 也不要错位. const discoverWidthReady = setVar("--tm-thumb-discover-width", measured.discoverCard && measured.discoverCard.width); const discoverCardHeightReady = setCardHeightVar("--tm-thumb-discover-card-height", measured.discoverCard && measured.discoverCard.height); const discoverThumbWidthReady = setVar("--tm-thumb-discover-thumb-width", measured.discoverThumb && measured.discoverThumb.width); @@ -746,7 +773,7 @@ return lastMeasure; }; const scheduleMeasure = (stage, delay = 0) => { - // 所有探测都通过这里排队,避免 React 连续插入节点时频繁测量布局。 + // 所有探测都通过这里排队, 避免 React 连续插入节点时频繁测量布局. if (typeof setTimeout !== "function") { detectAndApplySizes(stage); return; @@ -760,7 +787,7 @@ const selectorCount = (selector) => { try { - // 诊断用计数;selector 不被浏览器支持时返回错误字符串而不是中断脚本。 + // 诊断用计数; selector 不被浏览器支持时返回错误字符串而不是中断脚本. if (!document.querySelectorAll) return -1; return document.querySelectorAll(selector).length; } catch (e) { @@ -770,7 +797,7 @@ const computedInfo = (selector) => { try { - // 诊断用:输出关键容器的 display/grid/width,方便在控制台判断 CSS 是否命中。 + // 诊断用: 输出关键容器的 display/grid/width, 方便在控制台判断 CSS 是否命中. if (!document.querySelector || typeof getComputedStyle !== "function") return null; const el = document.querySelector(selector); if (!el) return null; @@ -791,7 +818,7 @@ }; const reportDiagnostics = (stage) => { - // 统一收集调试信息,手动执行 window.tmThumbScaleDebug() 也会走这里。 + // 统一收集调试信息, 手动执行 window.tmThumbScaleDebug() 也会走这里. const styleNodePresent = !!document.getElementById(STYLE_ID); const stylePresent = styleInjected || styleNodePresent; const counts = { @@ -869,13 +896,13 @@ lastReport = report; if (typeof window !== "undefined") { - // 暴露在页面 window,方便控制台直接查看最近一次报告。 + // 暴露在页面 window, 方便控制台直接查看最近一次报告. try { window.tmThumbScaleLastReport = report; } catch (_) {} } if (typeof unsafeWindow !== "undefined") { - // Tampermonkey 隔离环境下,unsafeWindow 能把 API 暴露给页面上下文。 + // Tampermonkey 隔离环境下, unsafeWindow 能把 API 暴露给页面上下文. try { unsafeWindow.tmThumbScaleLastReport = report; } catch (_) {} @@ -895,7 +922,7 @@ }; const exposeDebugApi = () => { - // 手动调试入口:控制台执行 tmThumbScaleDebug() 可立即生成报告。 + // 手动调试入口: 控制台执行 tmThumbScaleDebug() 可立即生成报告. const api = (stage = "manual") => reportDiagnostics(stage); const targets = []; if (typeof window !== "undefined") targets.push(window); @@ -910,7 +937,7 @@ }; const inject = () => { - // 注入 CSS 前先处理旧版本 style,避免更新脚本后新旧规则同时存在。 + // 注入 CSS 前先处理旧版本 style, 避免更新脚本后新旧规则同时存在. const existingStyle = document.getElementById(STYLE_ID); if (existingStyle) { const existingVer = existingStyle.getAttribute("data-tm-thumb-scale-ver"); @@ -930,7 +957,7 @@ try { if (typeof GM_addStyle === "function") { - // Tampermonkey 推荐使用 GM_addStyle,比手动 append style 更稳定。 + // Tampermonkey 推荐使用 GM_addStyle, 比手动 append style 更稳定. const style = GM_addStyle(css); if (style) { style.id = STYLE_ID; @@ -945,7 +972,7 @@ } const style = document.createElement("style"); - // GM_addStyle 不可用时才走手动 style 兜底。 + // GM_addStyle 不可用时才走手动 style 兜底. style.id = STYLE_ID; style.setAttribute("data-tm-thumb-scale-ver", VER); style.textContent = css; @@ -968,7 +995,7 @@ }; const startHeadObserver = () => { - // 有些 SPA/扩展会移除 head 里的 style,这里只负责把本脚本样式补回去。 + // 有些 SPA/扩展会移除 head 里的 style, 这里只负责把本脚本样式补回去. if (!document.head || observerStarted) return; observerStarted = true; const mo = new MutationObserver(() => { @@ -981,8 +1008,8 @@ }; const startPageObserver = () => { - // Chaturbate 很多内容是 React 动态插入的,尤其关注下拉和 tooltip。 - // 观察 body 后,每次 DOM 变化都延迟探测一次尚未 ready 的模块。 + // Chaturbate 很多内容是 React 动态插入的, 尤其关注下拉和 tooltip. + // 观察 body 后, 每次 DOM 变化都延迟探测一次尚未 ready 的模块. if (!document.body || pageObserverStarted || typeof MutationObserver !== "function") return; pageObserverStarted = true; const mo = new MutationObserver(() => scheduleMeasure("mutation", 300)); @@ -990,30 +1017,30 @@ }; log("start v" + VER); - // 注册油猴菜单;用户需要重测布局时,可从菜单清除 localStorage 缓存。 + // 注册油猴菜单; 用户需要重测布局时, 可从菜单清除 localStorage 缓存. registerMenu(); - // 尽早尝试应用缓存。缓存命中的模块会立即设置 CSS 变量和 data 开关。 + // 尽早尝试应用缓存.缓存命中的模块会立即设置 CSS 变量和 data 开关. applyCachedSizes(); - // 暴露调试 API,方便后续在控制台手动检查。 + // 暴露调试 API, 方便后续在控制台手动检查. exposeDebugApi(); - // 第一次报告:记录启动时是否有缓存、CSS 变量是否已经设置。 + // 第一次报告: 记录启动时是否有缓存, CSS 变量是否已经设置. reportDiagnostics("start-before-inject"); - // 注入 CSS。因为模块默认关闭,即使 CSS 很早注入,也不会改变原始布局。 + // 注入 CSS.因为模块默认关闭, 即使 CSS 很早注入, 也不会改变原始布局. inject(); - // GM_addStyle 后再暴露一次 API,保证 lastReport / style 状态更新。 + // GM_addStyle 后再暴露一次 API, 保证 lastReport / style 状态更新. exposeDebugApi(); - // 观察 head,防止 style 被站点或扩展移除。 + // 观察 head, 防止 style 被站点或扩展移除. startHeadObserver(); - // 观察 body,用于动态内容出现后触发尚未 ready 模块的探测。 + // 观察 body, 用于动态内容出现后触发尚未 ready 模块的探测. startPageObserver(); if (!document.head && document.addEventListener) { - // 极早 document-start 时 head 可能还不存在;DOMContentLoaded 再补一次注入和探测。 + // 极早 document-start 时 head 可能还不存在; DOMContentLoaded 再补一次注入和探测. document.addEventListener("DOMContentLoaded", () => { inject(); exposeDebugApi(); - // DOMContentLoaded 后再等 600ms,让 React/图片容器有时间完成初始布局。 + // DOMContentLoaded 后再等 600ms, 让 React/图片容器有时间完成初始布局. scheduleMeasure("domcontentloaded", 600); startHeadObserver(); startPageObserver(); @@ -1022,11 +1049,11 @@ } if (typeof setTimeout === "function") { - // 固定时间点探测用于覆盖:缓存未命中、React 首屏慢、图片容器稍后才出现等情况。 + // 固定时间点探测用于覆盖: 缓存未命中, React 首屏慢, 图片容器稍后才出现等情况. setTimeout(() => { scheduleMeasure("after-800ms"); reportDiagnostics("after-800ms"); startPageObserver(); }, 800); - // 第二次探测给慢一点的页面留余量。 + // 第二次探测给慢一点的页面留余量. setTimeout(() => { scheduleMeasure("after-2000ms"); reportDiagnostics("after-2000ms"); startPageObserver(); }, 2000); - // 最后一次兜底探测;成功的模块会被 moduleReady 锁住,不会重复相乘。 + // 最后一次兜底探测; 成功的模块会被 moduleReady 锁住, 不会重复相乘. setTimeout(() => { scheduleMeasure("after-5000ms"); reportDiagnostics("after-5000ms"); startPageObserver(); }, 5000); } diff --git a/tests/followed-dropdown-css.test.js b/tests/followed-dropdown-css.test.js index 412140f..62f76be 100644 --- a/tests/followed-dropdown-css.test.js +++ b/tests/followed-dropdown-css.test.js @@ -256,8 +256,8 @@ const cacheFakeDocument = { addEventListener() {}, }; const cacheStore = new Map([ - ["tm-thumb-scale:size-cache:v9", JSON.stringify({ - schema: 9, + ["tm-thumb-scale:size-cache:v11", JSON.stringify({ + schema: 11, scale: 2, cardHeightScale: 1.55, modules: { @@ -350,15 +350,17 @@ vm.runInNewContext(source, { localStorage: { getItem() { return JSON.stringify({ - schema: 9, + schema: 11, scale: 2, cardHeightScale: 1.55, modules: { home: { homeCard: { width: 174, height: 120 }, + homeThumb: { width: 174, height: 98 }, }, followingList: { followingListCard: { width: 190, height: 120 }, + followingListThumb: { width: 188, height: 106 }, }, }, }); @@ -385,7 +387,7 @@ vm.runInNewContext(source, { assert.equal(followingAttrs.get("data-tm-thumb-scale-following-list"), "1", "followed-cams should use its own cached list module"); assert.equal(followingAttrs.get("data-tm-thumb-scale-home"), undefined, "followed-cams should not apply homepage cache"); assert.equal(followingVars.get("--tm-thumb-following-list-width"), "380px"); -assert.equal(followingVars.get("--tm-thumb-following-list-card-height"), undefined); +assert.equal(followingVars.get("--tm-thumb-following-list-card-height"), "226px"); assert.equal(followingVars.get("--tm-thumb-following-list-thumb-width"), undefined); -assert.equal(followingVars.get("--tm-thumb-following-list-thumb-height"), undefined); +assert.equal(followingVars.get("--tm-thumb-following-list-thumb-height"), "212px"); assert.equal(followingVars.get("--tm-thumb-home-width"), undefined);