十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Readest 深色模式背景纹理被 `body.theme-dark` 不透明背景遮挡(4446)的排查与修复实录

Readest 深色模式背景纹理被 `body.theme-dark` 不透明背景遮挡(4446)的排查与修复实录 Readest 深色模式背景纹理被body.theme-dark不透明背景遮挡#4446的排查与修复实录【免费下载链接】readestReadest is a modern, feature-rich ebook reader designed for avid readers offering seamless cross-platform access, powerful tools, and an intuitive interface to elevate your reading experience.项目地址: https://gitcode.com/gh_mirrors/re/readest本文基于 Readest 仓库中记录于 dark-mode-texture-body-bg-4446.md 的调试记忆文档结合 style.ts、textures.ts 及对应测试源码完整还原背景纹理background texture在深色模式下被遮挡的根因、修复方案与验证方法。读完本文你将掌握 Readest 阅读器页面背景纹理的绘制链路、foliate 分页器的背景捕获机制以及一套可复用的多层遮挡物排障方法论。问题现象#4446 深色模式下纹理消失Readest 阅读器允许用户为阅读界面叠加纸张、木纹等背景纹理texture。在修复 #4399分页模式纹理被分页器不透明填充遮挡之后测试者发现深色模式下纹理依然消失具体表现为分页模式paginated整个视图区域完全没有纹理滚动模式scrolled文本列区域没有纹理但页眉/页脚横条仍能显示纹理。也就是说这是一个深色模式专属、比 #4399 更深入一层的第二遮挡物。该问题在小米设备Xiaomi 2211133C上通过 CDPChrome DevTools Protocol实时验证无需重新构建应用即可复现。背景纹理是如何绘制出来的要理解遮挡问题先要看清纹理的绘制机制。Readest 的纹理不是贴在书籍内容上的而是挂在宿主页面容器的伪元素上。在 textures.ts 中createTextureCSS生成如下规则.sidebar-container, .notebook-container, .foliate-viewer { position: relative; } body::before, .sidebar-container::before, .notebook-container::before, .foliate-viewer::before, .notch-masked::before { content: ; position: absolute; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; z-index: 0; background-image: url(...); background-repeat: repeat; background-size: var(--bg-texture-size, cover); mix-blend-mode: var(--bg-texture-blend-mode, multiply); opacity: var(--bg-texture-opacity, 0.6); }关键点在于纹理挂在.foliate-viewer::before等伪元素上而书籍内容渲染在section iframe中两者是不同的渲染层。纹理要透出来就必须保证 iframe 内部以及分页器的背景段是透明的否则不透明背景会像墙一样盖住下面的纹理。纹理的装载与卸载由mountBackgroundTexture/unmountBackgroundTexture完成textures.ts共用同一个 id 为background-texture的style元素。内置纹理在PREDEFINED_TEXTURES中定义textures.ts包括 concrete、paper、sand、parchment、scrapbook、leaves、moon、night-sky 等对应资源位于 apps/readest-app/public/images 目录。根因分析getDarkModeLightBackgroundOverrides的不透明body.theme-dark通过 CDP 注入样式验证根因很快被锁定在 style.ts 的getDarkModeLightBackgroundOverrides上。该函数的本意是解决深色模式下书籍内联浅色背景如白色 callout 提示框导致黑字不可读的问题#4028 等。它生成一组属性选择器规则把带白色内联背景的元素强制改成主题背景色例如*[style*background-color: #fff], *[style*background-color: white], *[style*background-color: rgb(255] { background-color: #1a1a1a !important; }问题出在它同时追加的一条规则。修复前的版本位于getDarkModeLightBackgroundOverrides内部会生成body.theme-dark { background-color: #1a1a1a !important; /* 修复前不透明主题背景 */ }这条规则在isDarkMode !overrideColor时被拼入最终的样式串见 style.ts${isDarkMode !overrideColor ? getDarkModeLightBackgroundOverrides(bg) : }。它的作用是给整个 iframe 的 body 涂上一层不透明的深色实测为rgb(34,34,34)结果就是直接遮挡iframe body 铺满不透明深色把宿主页面.foliate-viewer::before上的纹理完全盖住下游污染foliate 分页器的resolveBackground(view.docBackground)拿到的是一个不透明颜色textureAwareBackground便认为页面自带不透明背景从而在分页模式下保持#background背景段不透明、在滚动模式下保持view.element的内联背景不透明——纹理继续被双重遮挡。这也解释了现象差异的来源分页模式背景段 iframe body 都铺满整个视口 → 任何角落都看不到纹理滚动模式iframe 只覆盖文本列区域 → 只有文本列被遮住页眉/页脚横条grid-cell #4486 的 notch 遮罩依然透出纹理。排除法不是 foliate-js也不是 transformStylesheet排查过程中曾怀疑两个嫌疑人但都被证伪foliate-jsfoliate 的 swipe-flash 回归#4399commit167757a→142bf11在同一发布窗口破坏了浅色模式纹理表面上看起来像同一件事。但 #4399 的修复让容器透明依然有效本问题是深色模式下更深一层的独立遮挡。transformStylesheet 浅色背景重写器同样来自 #4392针对复现书籍Alice做了真机样式表枚举其样式表中没有一条 body/html 背景规则因此该重写器被排除。通过git溯源确认回归来源commit176b950c9 PR #43922026-06-01随 v0.11.4 发布加入了这条body.theme-dark不透明规则。但#4392 整体不需要回退——它的 callout 属性选择器和样式表重写器修复了真实的可读性问题#4028#4419/#4426 还构建在这些修复之上需要回退的只是其中这一条规则。修复方案无条件透明而非按纹理存在与否门控修复思路很直接把规则改为background-color: transparent !important但有一个关键设计决策——不做hasBackgroundTexture门控即不写有纹理才透明。原因是 foliate 的docBackground每个 section 加载时只捕获一次见 paginator.js 的load监听器setStyles会重新执行#replaceBackground但不会重新捕获docBackground。如果规则按当前是否有纹理来决定是否透明那么在阅读过程中实时切换纹理时已经加载的 section 拿到的 body 背景还是旧的捕获值纹理开关就会失效背景变 stale。修复后的规则当前 style.ts 中的实际代码/* Force transparent, not the theme bg: the dark page fill already comes from the paginator container / reader grid cell, while an opaque body paints over the host background texture (#4446) — and foliate captures docBackground once per section load, so the body must stay transparent regardless of texture state. Book-forced light page backgrounds still get neutralized (#4392) since the theme-dark fill shows through. */ body.theme-dark { background-color: transparent !important; }为什么无条件透明后视觉不退化因为深色填充的职责被转移到了更外层分页模式下深色填充来自分页器容器的fallbackBg滚动模式下深色填充来自 reader 的 grid cell。也就是说body.theme-dark不再是深色背景的提供者透明的 body 让宿主层的深色填充与纹理按原有层次正确合成。同时几条原有策略被确认继续成立书籍强制的浅色页面背景仍然被中和theme-dark填充可以透出页面级重写器输出的规则被我们的!importantbody 规则和更靠后head 内的html规则级联压制只有书籍自身带!important的页面规则能存活——这与 #4399 确立的book-forced opaque page wins书籍强制不透明页面优先策略一致。测试验证style-get-styles.test.ts的 #4446 用例修复的回归测试位于 style-get-styles.test.ts核心断言如下it(keeps body.theme-dark transparent in dark mode so the host background texture is not occluded (#4446), () { const vs makeViewSettings({ overrideColor: false, backgroundTextureId: leaves }); const theme makeThemeCode({ isDarkMode: true, bg: #1a1a1a, fg: #e0e0e0 }); const css getStyles(vs, theme); expect(css).toMatch(/body\.theme-dark\s*\{\s*background-color: transparent !important;/); expect(css).not.toMatch(/body\.theme-dark\s*\{\s*background-color: #1a1a1a !important/); // #4392 inline light-callout overrides must keep forcing the theme bg expect(css).toContain(background-color: #fff]); expect(css).toContain(background-color: #1a1a1a !important); }); it(keeps body.theme-dark transparent even without a texture (docBackground is captured once per section load), () { const vs makeViewSettings({ overrideColor: false, backgroundTextureId: none }); // ...同样断言 body.theme-dark 为 transparent });第二个用例专门守护无条件透明的设计决策即使没有纹理backgroundTextureId: nonebody 也必须保持透明否则实时开关纹理时docBackground的 stale 捕获会让纹理再次消失。分页器侧的配套行为由 paginator-background-segments.test.ts 覆盖textureAwareBackground在纹理激活时丢弃透明页背景让纹理透出但保留不透明页与携带背景图的页如封面图。真机 E2E 验证结果带着修复后的 CSS 加载时捕获的页面背景是透明的分页模式的 segments 数组输出为空——即分页器不再为透明页绘制不透明填充。调试与验证的五个坑耗时约 30 分钟的教训记忆文档特别记录了验证阶段踩过的坑对任何涉及 iframe 渲染的阅读器调试都有普适价值Multiview影子根里有多个 section iframe。渲染器renderer的 shadow root 会因相邻预加载adjacent preload持有多个section iframe。如果只 patchsr.querySelector(iframe)命中的可能是离视口 18000px 之外的 section导致修复了但没生效的假象。必须用sr.querySelectorAll(iframe)全部处理。elementsFromPoint的盲区。它返回的是 iframe元素的计算样式透明而真正遮挡的绘制来自 iframe 内部 content document 的 body——这是顶层文档/shadow 栈遍历看不见的。切换renderer.setAttribute(flow, ...)会重载 section 文档静默清掉注入其中的样式但不一定每次都发生——每次切换 flow 后都要复查注入是否还在。伪元素绘制测试把#background-texture样式文本改成background-color: red做探针——如果红色不出现说明伪元素是被遮挡而非失效。Stale preload viewspatch 前已加载的视图会保留旧的不透明docBackground且#clearViewsExcept在翻页时只清理|i−index|≤2的视图——一个不透明背景段可能来自被保留的旧视图而非新视图。要保证新鲜捕获跳 3 个以上 section再观察。捕获时机的插桩技巧分页器在docBackground getBackground(doc)之前同步派发load事件——在渲染器上挂一个事件监听器看到的恰好就是捕获那一刻的类名、计算样式与生效规则是最精确的观察窗口。相关实现与参考文件修复实现apps/readest-app/src/utils/style.tsgetDarkModeLightBackgroundOverrides与 L361注入条件isDarkMode !overrideColor纹理挂载机制apps/readest-app/src/styles/textures.ts伪元素绘制、mountBackgroundTexture、PREDEFINED_TEXTURES回归测试apps/readest-app/src/tests/utils/style-get-styles.test.ts分页器背景行为apps/readest-app/src/tests/document/paginator-background-segments.test.ts纹理应用 Hookapps/readest-app/src/hooks/useBackgroundTexture.ts纹理与翻页背面合成apps/readest-app/src/app/reader/hooks/useCapturedTurn.ts原始记忆文档apps/readest-app/.claude/memory/dark-mode-texture-body-bg-4446.md总结#4446 是一次典型的修复引入新遮挡回归——为深色模式可读性而加的不透明body.theme-dark规则恰好压在了宿主纹理伪元素之上。最终方案以无条件透明 外层容器提供填充重新划分了背景职责并用docBackground 每 section 只捕获一次这一 foliate 行为论证了不做纹理门控的必要性。这套识别遮挡层 → 排除法溯源 → 重新分配绘制职责 → 守护用例锁定行为的流程同样适用于任何多层 iframe 渲染场景下的显示问题排查。【免费下载链接】readestReadest is a modern, feature-rich ebook reader designed for avid readers offering seamless cross-platform access, powerful tools, and an intuitive interface to elevate your reading experience.项目地址: https://gitcode.com/gh_mirrors/re/readest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表