尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Readest 重排 EPUB 单击打开图片/表格:iframe 事件处理管线与媒体查看器触发机制解析

Readest 重排 EPUB 单击打开图片/表格:iframe 事件处理管线与媒体查看器触发机制解析 Readest 重排 EPUB 单击打开图片/表格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导读本篇文章围绕 Readest 阅读器在重排reflowableEPUB中单击图片/表格直接打开媒体查看器这一交互特性展开它解释了为何在单击即翻页的阅读器里图片与表格能拥有独立的单击语义并逐层剖析了从 iframe 内部 DOM 事件、detectMediaTarget目标判定、postSingleClick守卫链、iframe-open-media消息管道到FoliateViewer消费并驱动ImageViewer/TableViewer的完整链路。读完你将掌握 Readest 的 iframe 事件转发模型、双击/长按/翻页手势与媒体点击如何仲裁以及固定版式PDF/漫画与重排版式的行为差异。一、背景单击即翻页图片与表格如何获得第二语义在绝大多数阅读器中单击 翻页或切换工具栏这是用户肌肉记忆的一部分。但当页面里出现图片、表格这类需要放大查看的内容时单击翻页就会与我想看大图发生冲突。Readest 通过 PR #4600由 [issue-4584-tap-death-investigation] 触发的点击死区调查给出了明确方案在重排 EPUB中单击img、带image子元素的svg、以及table现在会打开ImageViewer图片画廊或TableViewer表格缩放而不再翻页长按仍然可以触发相同效果在早期实现中与单击并存固定版式PDF、漫画/漫画书以bookData.isFixedLayout为标志中单击保持翻页语义不变——因为在那里单击本身就是翻页手势。这份设计约束与实现细节记录于仓库记忆文档 tap-to-open-image-table-4600.md核心实现在 iframeEventHandlers.ts。二、核心判定函数detectMediaTargetiframeEventHandlers.ts中定义了统一的媒体目标判定函数它同时服务于单击与早期实现中的长按两条路径避免逻辑重复type MediaTarget { elementType: image; src: string } | { elementType: table; html: string }; const detectMediaTarget (target: HTMLElement | null): MediaTarget | null { if (!target) return null; if (target.localName img) { return { elementType: image, src: (target as HTMLImageElement).src }; } const svgImage target.closest(svg)?.querySelector(image); if (svgImage) { const href svgImage.getAttribute(href) || svgImage.getAttributeNS(http://www.w3.org/1999/xlink, href); if (href) return { elementType: image, src: href }; } const table target.localName table ? target : target.closest(table); if (table) return { elementType: table, html: (table as HTMLElement).outerHTML }; return null; };判定规则可以归纳为三档见 iframeEventHandlers.ts目标判定逻辑返回载荷img直接命中元素{ elementType: image, src }src为解析后的绝对地址svg内含image向上closest(svg)后查找子级image兼容href与xlink:href两种属性{ elementType: image, src }table直接命中或向上closest(table)即点击单元格也算{ elementType: table, html }html为整张表的outerHTML注意这是从任意子元素向上回溯的判定closest因此点击表格内的td、SVG 内部的形状元素都能正确命中父级媒体容器。返回的载荷恰好是ImageViewer/TableViewer消费所需的两种形态图片地址与表格 HTML。三、单击分支的守卫链postSingleClick内部顺序单次单击的真正处理在postSingleClick中完成iframeEventHandlers.ts。它不会一上来就判定媒体而是按严格顺序通过多道守卫防止拖拽、长按、双击、选词等操作与单击媒体互相双重触发滑动合成点击抑制检查consumeSuppressedDomClick把一次滑动手势残留的浏览器合成 click 直接吞掉延迟翻页仲裁lateLayeredClaim检查——原生捕获翻页的认领可能比 iframe 的 touchend/click 晚一个任务到达此时不能把 1–6px 的早认领误判为单击脚注判定命中.js_readerFooterNote、.zhangyue-footnote、.duokan-footnote、.qqreader-footnote之一视为脚注走footnote-popup弹窗而非媒体查看媒体判定提前计算const media !isFixedLayout !footnote ? detectMediaTarget(element) : null——只有在重排版式且非脚注时才算媒体目标链接守卫element?.closest(sup, a, audio, video)命中时直接返回不翻页不跳转但第 4 步算出的media会被并入该守卫!media 前缀保证被链接包裹的媒体能够绕过链接守卫这就是 #4757 的改动点拖拽守卫isMouseDown为真说明鼠标尚未抬起、正处于拖拽选择中丢弃本次单击对应 #4524长按守卫!longHoldTimeout时丢弃即按住不放的长按预备不会提前触发单击早期实现中长按随后单独触发媒体查看Word Lens 选词findGlossWord(element)命中注音词时走wordlens-dictionary词典查询媒体分发if (media)时window.postMessage({ type: iframe-open-media, bookKey, ...media }, *)兜底其余情况发送iframe-single-click翻页/工具栏切换。其中媒体判定之所以提前到链接守卫之前是为了让detectMediaTarget只执行一次、结果在后面的分发阶段复用避免二次 DOM 遍历。双击去抖250ms 延迟postSingleClick并不是同步执行的。handleClick首先检查now - lastClickTime DOUBLE_CLICK_INTERVAL_THRESHOLD_MS若在双击阈值250ms常量定义于 constants.ts内发生第二次点击则走双击分支见下节。否则设置lastClickTime now并在setTimeout中延迟DOUBLE_CLICK_INTERVAL_THRESHOLD_MS毫秒后再执行postSingleClick且执行前还会复核Date.now() - lastClickTime DOUBLE_CLICK_INTERVAL_THRESHOLD_MS——保证第一次点击不会与紧随其后的第二次点击产生冲突。也就是说单击打开图片/表格天然继承了 250ms 双击去抖窗口快速连点不会反复弹出查看器。四、固定版式特例单击翻页、双击看大图固定版式PDF/漫画中bookData.isFixedLayout为真postSingleClick里的media恒为null因此单击图片仍然发送iframe-single-click用于翻页。漫画CBZ则有一个特例漫画页面是一整幅全出血图片、没有文本因此双击不会触发选区而是被复用来打开图片查看器——这是唯一能放大到超过页面适配并支持保存/分享图片的界面const comicPage isComicBook ? detectMediaTarget(event.target as HTMLElement | null) : null; if (comicPage?.elementType image) { window.postMessage({ type: iframe-open-media, bookKey, ...comicPage }, *); return; }isComicBook与isFixedLayout两个标志均由FoliateViewer在绑定监听器时注入见下文第六节handleClick的完整签名因此是(bookKey, doubleClickDisabled, isFixedLayout, isComicBook, event)。五、消息管道iframe-long-press→iframe-open-media的更名陷阱媒体查看器运行在主进程/父页面的 React 侧而点击事件发生在 iframe 内部的书籍 DOM 上两者通过window.postMessage通信。这里有一个必须注意的更名旧消息名iframe-long-press已更名为iframe-open-media它同时承载单击与长按两种手势的触发消费端的 Hook 由useLongPressEvent更名为useOpenMediaEvent内部处理函数为handleOpenMedia长按探测器addLongPressListeners/handleLongPress的名称被保留下来——但它现在只负责检测一次真实长按这一语义不再承担消息命名职责。因此在源码中检索时应搜索iframe-open-media而不是iframe-long-press。当前仓库中该消息的发送点共有两处iframeEventHandlers.ts 的漫画双击分支与 L562 的重排单击分支消费者useOpenMediaEvent定义于 useIframeEvents.tsexport const useOpenMediaEvent ( bookKey: string, handleImagePress: (src: string) void, handleTablePress: (html: string) void, ) { const handleOpenMedia (msg: MessageEvent) { if (msg.data msg.data.bookKey bookKey msg.data.type iframe-open-media) { if (msg.data.elementType image) { handleImagePress(msg.data.src); } else if (msg.data.elementType table) { handleTablePress(msg.data.html); } } }; // ... };消息按bookKey过滤同一时间打开的多个书籍互不串扰elementType字段直接决定分发到图片还是表格处理器。六、FoliateViewer 的消费侧注入标志位与打开查看器FoliateViewer是事件链路的宿主组件FoliateViewer.tsx。它在docLoadHandler中把handleClick绑定到每份加载的文档上同时注入两个关键标志detail.doc.addEventListener( click, handleClick.bind( null, bookKey, doubleClickDisabled, !!bookData?.isFixedLayout, // 固定版式 → 单击翻页 bookData?.book?.format CBZ, // 漫画 → 双击开大图 ), );随后通过useOpenMediaEvent(bookKey, handleImagePress, handleTablePress)订阅消息两个处理函数分别驱动两个查看器handleImagePress(src)FoliateViewer.tsx调用collectDocumentImages收集当前文档全部图片定位被点击图片在画廊中的索引记录 alt 文本并将 blob URL 通过convertBlobUrlToDataUrl转换为 data URL 后设置selectedImage——因此ImageViewer不仅展示单张图还支持上一张/下一张翻看handlePreviousImage/handleNextImage还会联动goTo(cfi)跳转正文位置handleTablePress(html)FoliateViewer.tsx直接把表格outerHTML存入selectedTableHtml交给TableViewer渲染缩放。两个查看器组件分别位于 ImageViewer.tsx 与 TableViewer.tsx。七、行为矩阵与边界情况综合以上实现可以将交互行为归纳为下表场景版式行为单击img/svgimage/table重排 EPUB打开ImageViewer/TableVieweriframe-open-media单击被a包裹的图片重排 EPUB打开图片查看器不跟随链接#4757单击脚注链接内图片重排 EPUB保持脚注弹窗/跳转不打开查看器单击图片/表格固定版式PDF/CBZ翻页iframe-single-click双击漫画整页图片CBZ打开图片查看器放大/保存/分享拖拽/长按/双击选词途中任意被守卫链拦截不误开查看器两个值得一提的边界被链接包裹的图片#4757比如出版方把插图用a href大图包起来做点击看原图的场景。此前这类元素落在sup, a, audio, video跳过守卫里单击会尝试跟随链接在阅读器内造成页面跳转。现在media提前计算并让链接守卫带上!media 前缀媒体目标直接绕过链接守卫进入查看器。脚注被显式排除!footnote条件保证脚注锚点内的图片仍走脚注弹窗/导航避免破坏出版物的脚注交互。八、测试佐证行为契约被固化该特性的行为契约在单元测试中被完整固化见 iframeEventHandlers.test.ts五条核心断言分别是重排版式单击img→ 发送iframe-open-mediaelementType: image且不发送iframe-single-click重排版式点击td表格内部→ 发送iframe-open-mediaelementType: table载荷为table.outerHTML固定版式单击img→ 仍然只发送iframe-single-click单击翻页不受影响重排版式单击a包裹的图片 → 打开查看器而非跟随链接#4757 回归保护单击duokan-footnote链接内的图片 → 不发送iframe-open-media脚注行为保留。测试通过vi.advanceTimersByTime(260)推进虚拟时钟越过 250ms 双击窗口验证延迟分发路径这从侧面印证了第六节描述的单击媒体继承双击去抖的实现细节。九、演进线索从 #4600 到 #5069最后补充一条版本演进脉络帮助你理解当前源码与早期记忆文档的差异#4600引入单击打开媒体查看器detectMediaTarget同时服务单击与长按两条路径消息名为iframe-open-media#4757扩展支持被普通链接包裹的图片单击直接缩放而非跟随链接#5069当前源码状态长按打开查看器的能力被移除。原因记录在测试注释中长按依赖一个 500ms 定时器只有指针移动超过 10px 才会取消而缓慢滚动在半秒内的位移不足 10px导致定时器在滚动中途触发、查看器在用户手指下方突然弹出。由于单击#4584已经能到达同一个查看器长按不再注册任何处理器——测试 明确断言handlers不再暴露addLongPressListeners属性。因此当你阅读仓库记忆文档时需要注意文档描述的长按同样打开查看器是 #4600/#4757 时代的行为而当前代码已经收敛为重排版式仅单击打开、固定版式仅单击翻页的简洁模型。这正是该记忆文件作为项目决策日志的价值所在——它记录了每个 PR 的设计动机与改名陷阱iframe-open-media方便后续开发者在git log与源码之间快速对齐。【免费下载链接】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),仅供参考
返回列表