ReadViewSDK/Doc/WXRead/analysis/CSS_JS资源分析.md
2026-05-21 19:40:51 +08:00

1303 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CSS/JS/字体资源分析
本文档对微信读书 EPUB 阅读器的前端资源文件进行全面分析,涵盖 CSS 样式表、JavaScript 脚本和字体文件。
---
## 一、CSS 文件分析
### 1.1 样式层叠架构
微信读书的 CSS 采用分层架构:
```
default.css (基础HTML样式)
├── replace.css (EPUB替换样式)
├── replaceForLatinLanguageBook.css (拉丁语书籍替换样式)
├── replaceForMPChapter.css (公众号章节替换样式)
└── MediaPlatform.css (公众号文章基础样式)
└── MPExtra.css (公众号文章扩展样式)
dark.css (暗黑主题 - 覆盖层)
Style/style.css (富文本编辑器样式)
Style/normalize.css (标准化样式)
Style/preview.css (预览模式样式)
xcode.min.css (代码高亮主题)
WREpubMPVideo.css (视频播放器样式)
```
### 1.2 default.css - 基础HTML样式
**路径**: `output/resources/css/default.css` (222行)
Safari 派生的基础 HTML 默认样式,为 WebView 渲染提供标准化起点。
**核心特性**:
- HTML5 元素设置 `display:block`article, aside, footer, header, nav, section 等)
- 隐藏非显示元素:`head, title, style, link, meta, script { display:none }`
- body 默认字体大小 `16px`
- 链接颜色 `#2262A3`
- 代码块样式:`background-color:rgba(0,0,0,.05)`, `border-radius:0.3em`, `padding:0.2em`
- 表格边框合并:`border-collapse:collapse`
### 1.3 replace.css - EPUB替换样式
**路径**: `output/resources/css/replace.css` (172行)
EPUB 内容的核心替换样式,定义了微信读书特有的排版规则。
**字体设置**:
- 标题和版权页使用 `"SourceHanSerifCN-Medium"` 字体
- 代码块使用 `Consolas, "Liberation Mono", Menlo, Courier, monospace`,大小 `0.7em!important`
**标题层级**:
| 类名 | 字号 | 字重 |
|------|------|------|
| `.firstTitle` | 1.5em | normal |
| `.secondTitle` | 1.3em | normal |
| `.thirdTitle` | 1.15em | normal |
| `.fourthTitle` | 1.1em | normal |
| `.fifthTitle` | 1.05em | normal |
| `.sixthTitle` | 1em | normal |
**微信读书自定义CSS属性**:
```css
/* 垂直居中样式 - 值2用于正文图片 */
.bodyPic {
wr-vertical-center-style: 2;
}
/* 垂直居中样式 - 值1用于强制垂直居中 */
.wr-vertical-center {
wr-vertical-center-style: 1 !important;
}
/* 页面关联标记 */
.weread-page-relate {
weread-page-relate: true;
}
```
**其他重要样式**:
- `.ftext` - 首字母放大效果2.38em, float:left
- `.book-chapter-tool` - 章节结束工具栏78px高度
- `p { text-align:justify; }` - 段落两端对齐
- `.eepub-single-image-title` - 单图片标题,使用 `"FZFSJW--GB1-0"` 字体
### 1.4 dark.css - 暗黑主题
**路径**: `output/resources/css/dark.css` (57行)
纯黑背景的暗黑主题样式。
**核心样式**:
```css
/* 所有元素强制黑色背景 */
* {
background-color: rgba(0, 0, 0, 1) !important;
}
/* 业务容器特殊背景 */
.business-container {
background-color: rgb(28, 28, 29) !important;
}
/* 文字颜色 */
body, p, div, span, a {
color: rgba(180, 180, 182, 1) !important;
}
/* 链接颜色 */
a {
color: rgba(84, 127, 176, 1) !important;
}
/* 引用块 */
blockquote {
color: rgb(218, 220, 224);
background-color: rgb(10, 14, 18);
}
```
**特殊处理**:
- 兼容搜狗/百度搜索页面的暗黑模式
### 1.5 replaceForLatinLanguageBook.css - 拉丁语书籍样式
**路径**: `output/resources/css/replaceForLatinLanguageBook.css` (113行)
拉丁语系书籍的特殊替换样式。
**与replace.css的关键差异**:
- 标题不设置 `font-family`(使用浏览器默认字体而非 SourceHanSerifCN-Medium
- `.eepub-single-image-title` 使用 `"FZFSJW--GB1-0"` 字体
- 保留相同的 `wr-vertical-center-style` 属性
### 1.6 replaceForMPChapter.css - 公众号章节样式
**路径**: `output/resources/css/replaceForMPChapter.css` (137行)
微信公众号文章章节的特殊样式。
**字体设置**:
- 使用 `"Source Han Serif CN"` 字体族(带空格,与 replace.css 的 `"SourceHanSerifCN-Medium"` 不同)
- 标题 `font-weight: bold`replace.css 中为 normal
**特殊样式**:
```css
/* 打赏区域 */
.chapter-reward {
height: 120px;
}
/* 强制透明背景 - epub-custom-rule-21 */
p, strong, span {
background-color: transparent !important;
}
/* 强制透明背景 - epub-custom-rule-26 */
section, .rich_media_area_primary {
background-color: transparent !important;
}
```
### 1.7 MediaPlatform.css - 公众号文章基础样式
**路径**: `output/resources/css/MediaPlatform.css` (141行)
微信公众号文章的基础样式,包含标注系统。
**主要内容区域**:
```css
.rich_media_area_primary {
font-size: 18px;
line-height: 30px;
font-family: -apple-system;
}
```
**标注系统样式**:
| 类名 | 用途 | 样式 |
|------|------|------|
| `.highlight` | 高亮标注 | `background-color: rgba(255, 116, 0, .25)` |
| `.review` | 我的想法 | 红色虚线边框 `border: 1px dashed rgba(220, 53, 69, 1)` |
| `.friend-review` | 好友想法 | 灰色虚线边框 `border: 1px dashed rgba(130, 130, 130, 1)` |
| `.reference` | 引用标注 | `background-color: rgba(255, 214, 0, .25)` |
| `.tts` | TTS朗读高亮 | `background-color: rgba(27, 136, 238, .25)` |
**复合标注样式**:
```css
/* review 覆盖 highlight 的样式 */
.highlight.review {
background-color: transparent;
border: 1px dashed rgba(220, 53, 69, 1);
}
```
**其他重要样式**:
- `-webkit-tap-highlight-color: transparent` - 禁用所有标注类型的点击高亮
- `.h5_image_link { pointer-events: none; }` - 禁用图片链接点击
### 1.8 MPExtra.css - 公众号文章扩展样式
**路径**: `output/resources/css/MPExtra.css` (673行)
完整的微信公众号文章 CSS从微信网页版复制。
**核心特性**:
```css
/* 内容区域最大宽度限制 */
.rich_media_content * {
max-width: 100% !important;
box-sizing: border-box !important;
}
```
**WeUI 对话框系统**:
- `.weui-mask` - 遮罩层
- `.weui-dialog` - 对话框
**音频播放器样式**:
- `.audio_container` - 音频容器
- `.audio_button` - 播放按钮
- `.audio_progress` - 进度条
**其他组件**:
- Loading spinnerbase64 SVG 动画)
- GIF 图片处理(`.gif_img_wrp`
- 设备特定媒体查询iPhone6/6P
- `.unsupported_iframe` 不支持元素样式
### 1.9 xcode.min.css - 代码高亮主题
**路径**: `output/resources/css/xcode.min.css` (1行压缩)
highlight.js 的 xcode 主题,用于代码语法高亮。
- 白色背景,黑色文字
- 彩色语法标记(关键字、字符串、注释等)
### 1.10 WREpubMPVideo.css - 视频播放器样式
**路径**: `output/resources/css/WREpubMPVideo.css` (191行)
腾讯视频播放器的自定义样式覆盖。
**核心组件**:
- `.tvp_container` - 视频容器
- `.tvp_controlbar` - 控制栏
- `.tvp_playbtn` / `.tvp_pausebtn` - 播放/暂停按钮
- `.tvp_progress_slider` - 进度滑块
**资源引用**:
```css
/* 使用腾讯CDN的雪碧图 */
background-image: url(https://rescdn.qqmail.com/weread/cover/icon/WREpubMPVideo.png);
```
**广告隐藏**:
```css
/* 隐藏广告和应用下载提示 */
.tvp_ads_go, .tvp_app_download_onpause {
display: none !important;
}
```
### 1.11 Style/style.css - 富文本编辑器样式
**路径**: `output/resources/css/Style/style.css` (408行)
富文本编辑器的基础样式。
**编辑器模式**:
```css
/* 编辑模式 */
.re_Write {
/* 可编辑状态样式 */
}
/* 显示模式 */
.re_Display {
/* 只读状态样式 */
}
/* 夜间模式 */
.re_Night {
color: rgba(180, 180, 182, 1);
background-color: rgba(0, 0, 0, 1);
}
```
**书籍卡片组件**:
```css
.re_bookItem {
/* 书籍信息展示 */
}
.re_bookItem_cover {
/* 封面图片 */
}
.re_bookItem_title {
/* 书籍标题 */
}
.re_bookItem_author {
/* 作者信息 */
}
```
**图片处理**:
```css
.re_img {
/* 图片样式 */
}
.bodyPic {
/* 正文图片 */
}
```
**高DPI边框处理**:
```css
/* 使用transform:scale(0.5)实现0.5px边框 */
.retina-border {
border: 1px solid #ccc;
transform: scale(0.5);
}
```
### 1.12 Style/normalize.css - 标准化样式
**路径**: `output/resources/css/Style/normalize.css` (412行)
标准的 normalize.css v3.0.2,最小修改。
### 1.13 Style/preview.css - 预览模式样式
**路径**: `output/resources/css/Style/preview.css` (174行)
预览模式的特殊样式。
**核心样式**:
```css
.re {
font-size: 18px;
line-height: 30px;
font-family: -apple-system;
}
```
**书籍卡片**:
- 使用 0.5px 边框技术retina 显示屏优化)
**占位符文本**:
```css
.re_placeholder:before {
content: attr(placeholder);
color: #999;
}
```
**引用块**:
```css
.blockquote {
border-left: 3px solid #ccc;
background-color: #f9f9f9;
padding: 10px;
}
```
---
## 二、JavaScript 文件分析
### 2.1 JS 架构概览
```
WeReadApi.js (JS Bridge 核心)
├── weread-highlighter.js (高亮/标注引擎)
│ ├── rangy-highlighter.js (Rangy高亮模块)
│ ├── rangy-classapplier.js (Rangy类应用器)
│ ├── rangy-textrange.js (Rangy文本范围)
│ └── rangy-core.js (Rangy核心库)
├── rich_editor.js (富文本编辑器)
├── rich_display.js (富文本显示控制器)
├── MediaPlatform.js (公众号工具函数)
├── mpForArticle.js (公众号内容调整)
├── mp_interceptor.js (点击事件拦截器)
├── Readability.js (文章内容提取)
├── HtmlGenerator.js (HTML生成器)
├── cssInjector.js (CSS注入器)
└── 其他工具脚本
```
### 2.2 WeReadApi.js - JS Bridge 核心
**路径**: `output/resources/js/Text/WeReadApi.js` (238行)
WebView 与原生应用之间的核心通信桥接。
**WeReadBridge 类**:
```javascript
WeReadBridge = function() {
this._sendMessageQueue = []; // 消息队列
this._callback_count = 1000; // 回调计数器
this._callback_map = {}; // 回调映射表
this.available_func = {}; // 可用函数
this._iframe = document.createElement("iframe");
this._resultIframe = document.createElement("iframe");
}
```
**通信协议**:
- 发送消息:`wereadapijs://dispatch_message/`
- 接收结果:`wereadapijs://private/setresult/`
**核心方法**:
| 方法 | 用途 |
|------|------|
| `handleWithRichEditor(apiName, params, successCallback, failCallback)` | 通用API调用 |
| `execMPReaderMethod(apiName, params, successCallback, failCallback)` | 公众号阅读器方法调用 |
| `fetchQueue()` | 获取队列中的消息JSON字符串 |
| `_sendMessage(message)` | 将消息加入队列并触发原生调用 |
| `_call(func, params, callback)` | 创建带callbackId的消息并发送 |
| `handleMessage(message)` | 处理来自原生的回调响应 |
**消息格式**:
```javascript
{
"func": "apiName",
"params": {
"key": "value",
"callbackId": "1001"
}
}
```
**数据编码**:
- `_utf8Encode(str)` - UTF-8编码
- `_base64Encode(str)` - Base64编码
- 用于通过iframe URL传递数据
**Ready机制**:
```javascript
// 检查原生是否已注入信息
if (window["__WRB_INFO__"]) {
_onReady();
} else {
window["__WRB_INFO_CALL__"] = function() {
_onReady();
};
}
```
**全局实例**:
```javascript
window.wereadBridge = new WeReadBridge();
```
### 2.3 weread-highlighter.js - 高亮/标注引擎
**路径**: `output/resources/js/weread-highlighter.js` (1530行)
公众号文章的核心高亮和标注引擎,基于 Rangy 库构建。
**初始化**:
```javascript
// 初始化Rangy使用TextRange模式
rangy.init();
highlighter = new rangy.Highlighter();
```
**标注类型**:
| 类名 | 用途 | 对应CSS |
|------|------|---------|
| `highlight` | 高亮标注 | `rgba(255, 116, 0, .25)` |
| `review` | 我的想法 | 红色虚线边框 |
| `friend-review` | 好友想法 | 灰色虚线边框 |
| `reference` | 引用标注 | `rgba(255, 214, 0, .25)` |
| `tts` | TTS朗读高亮 | `rgba(27, 136, 238, .25)` |
| `blue-clickable` | 可点击蓝色 | 用于书籍标题等 |
**核心函数**:
```javascript
// 初始化标注从JSON加载
initAnnotations(items)
// 创建高亮
didTapCreateHighlightButton()
// 创建想法
didTapCreateReviewButton(start, end)
// 删除标注
didTapDeleteAnnotationButton(start, end, type)
// 点击处理
click(el, event)
```
**TTS系统**:
```javascript
// TTS状态追踪
var lastTTSInfo = {
text: '',
index: 0,
startY: 0
};
// TTS相关类
class TTSRange {
constructor(start, end, text) { ... }
}
class TTSText {
constructor(text, index, startY) { ... }
}
// 获取TTS文本
getTTSText(startY)
// 高亮TTS文本
highlightTTSText(text, index, startY)
```
**书籍标题检测**:
```javascript
// 查找所有《》包围的书籍标题
findAllBookTitles()
// 高亮选中的书籍标题
highlightSelectedBookTitles(indexArray)
```
**链接处理**:
```javascript
// 获取所有前向链接
getAllForwardLinks()
// 转换H5链接为原生数据属性
transferH5Link()
// 解密微信读书加密链接
WRLink_decrypt(str)
// type '3' = 数字
// 其他 = 十六进制编码文本
```
**选择变化处理**:
```javascript
// 防抖处理250ms
document.addEventListener("selectionchange", debounce(function(e) {
// 发送选择变化事件到原生
wereadBridge.handleWithRichEditor("selectionChange", param, "", "");
}, 250));
```
**Bridge命令**:
- `selectionChange` - 选择变化
- `didTapHighlight` - 点击高亮
- `didTapReview` - 点击想法
- `didTapImage` - 点击图片
- `didTapBook` - 点击书籍
- `scrollToOffsetTop` - 滚动到偏移位置
- `didTapHighlightBookTitle` - 点击高亮书籍标题
### 2.4 Rangy 模块
#### 2.4.1 rangy-highlighter.js (711行)
Rangy 高亮器模块,带微信读书自定义修改。
**CharacterRange 类**:
```javascript
class CharacterRange {
constructor(start, end) { ... }
intersects(otherRange) { ... }
isContiguousWith(otherRange) { ... }
isContain(otherRange) { ... }
union(otherRange) { ... }
intersection(otherRange) { ... }
getComplements(orderedRanges) { ... }
}
```
**Highlighter 类**:
```javascript
class Highlighter {
highlightCharacterRanges(className, charRanges, ...) { ... }
highlightRanges(className, ranges, ...) { ... }
highlightSelection(selection, ...) { ... }
serialize() { ... }
deserialize(serialized) { ... }
}
```
**自定义方法**:
- `getMergedCharacterRange` - 合并重叠的高亮
- `highghtWhichContainSelection` - 查找包含当前选择的高亮
**序列化格式**:
```
type:TextRange|start$end$id$className[|$containerId]
```
#### 2.4.2 rangy-classapplier.js (1105行)
Rangy 类应用器模块。
**ClassApplier 类**:
```javascript
class ClassApplier {
constructor(className, options) { ... }
applyToRange(range) { ... }
undoToRange(range) { ... }
applyToSelection(selection) { ... }
undoToSelection(selection) { ... }
isAppliedToRange(range) { ... }
isAppliedToSelection(selection) { ... }
toggleRange(range) { ... }
toggleSelection(selection) { ... }
}
```
**暴露的工具方法**:
```javascript
rangy.dom.util.hasClass(node, className)
rangy.dom.util.addClass(node, className)
rangy.dom.util.removeClass(node, className)
rangy.dom.util.getClass(node)
rangy.dom.util.splitNodeAt(node, index, ...)
```
#### 2.4.3 rangy-textrange.js (1946行)
Rangy 文本范围模块,支持基于字符的范围操作。
**Position 类**:
```javascript
class Position {
constructor(node, offset) { ... }
// 字符类型检测
get characterType() { ... } // NON_SPACE, COLLAPSIBLE_SPACE, etc.
get isImage() { ... } // 图片文本支持
}
```
**字符类型常量**:
- `NON_SPACE` - 非空格字符
- `COLLAPSIBLE_SPACE` - 可折叠空格
- `NON_COLLAPSIBLE_SPACE` - 不可折叠空格
- `IMAGE_TEXT` - 图片文本
**Range 扩展方法**:
```javascript
range.selectCharacters(containerNode, startIndex, endIndex)
range.toCharacterRange(containerNode)
range.text // 获取文本
range.move(unit, count) // 移动范围
range.expand(unit, options) // 扩展范围
range.findText(text, options) // 查找文本
```
### 2.5 rich_editor.js - 富文本编辑器
**路径**: `output/resources/js/Text/rich_editor.js` (1710行)
用于编写想法/评论的完整富文本编辑器。
**RE 命名空间**:
```javascript
RE = {
editor: document.getElementById('editor'),
dataSeparator: 'r_e_ds',
// ... 其他属性和方法
}
```
**核心方法**:
| 方法 | 用途 |
|------|------|
| `RE.setHtml(contents)` | 设置HTML内容 |
| `RE.getHtml()` | 获取HTML内容 |
| `RE.getText()` | 获取纯文本 |
| `RE.setBold()` | 设置粗体 |
| `RE.setItalic()` | 设置斜体 |
| `RE.setHeading(level)` | 设置标题级别 |
| `RE.setBlockquote()` | 设置引用块 |
| `RE.setUnorderedList()` | 设置无序列表 |
| `RE.setOrderedList()` | 设置有序列表 |
| `RE.setJustifyLeft()` | 左对齐 |
| `RE.setJustifyCenter()` | 居中对齐 |
| `RE.setJustifyRight()` | 右对齐 |
| `RE.insertBook(id, title, author, cover)` | 插入书籍卡片 |
| `RE.insertImage(imgArray)` | 插入图片 |
| `RE.insertQQEmoticon(url, alt, w, h)` | 插入QQ表情 |
| `RE.insertLink(text, href, title)` | 插入链接 |
| `RE.unlink()` | 移除链接 |
| `RE.updateLink()` | 更新链接 |
| `RE.insertHTML(html)` | 插入HTML |
| `RE.insertText(text)` | 插入纯文本 |
| `RE.backuprange()` | 备份选择范围 |
| `RE.restorerange()` | 恢复选择范围 |
| `RE.focus()` | 获取焦点 |
| `RE.blurEditor()` | 失去焦点 |
**数据分隔符**:
```javascript
RE.dataSeparator = 'r_e_ds'; // 用于连接多个值
```
**内容变化通知**:
```javascript
RE.contentChanged = function(e) {
// 通知原生格式状态变化
wereadBridge.handleWithRichEditor("onSelectionChange", param, "", "");
}
```
**EPUB HTML导出**:
```javascript
RE.getHtmlForEpub = function() {
// 移除临时类并应用correctArticle
return correctedHtml;
}
```
**粘贴处理**:
```javascript
document.addEventListener('paste', function(e) {
// 拦截粘贴,发送到原生处理
wereadBridge.handleWithRichEditor("onPaste", {}, "", "");
});
```
### 2.6 rich_display.js - 富文本显示控制器
**路径**: `output/resources/js/Text/rich_display.js` (262行)
富文本内容的显示控制器,处理书籍和图片的交互。
**书籍交互**:
```javascript
RDisplay.onBookToucheStart = function(ele) {
ele.classList.add('re_bookItem_Touched');
}
RDisplay.onBookToucheEnd = function(ele) {
ele.classList.remove('re_bookItem_Touched');
}
RDisplay.onBookClick = function(ele) {
var id = ele.getAttribute('data-id');
wereadBridge.handleWithRichEditor("onGotoBookDetail", {"data-id": id}, "", "");
}
```
**图片交互**:
```javascript
RDisplay.onImageClick = function(ele) {
var src = ele.getAttribute('src');
var index = ele.getAttribute('index');
var position = RDisplay.getCoords(ele);
wereadBridge.handleWithRichEditor("onGotoImageDetail", {
"src": src,
"index": index,
"top": position.top,
"height": position.height,
"width": position.width,
"left": position.left
}, "", "");
}
```
**文本提取**:
```javascript
RDisplay.getText = function() {
// 过滤零宽连字符
// 将书籍替换为[书籍]
// 将图片替换为[图片]
return tempDom.innerText;
}
```
**选择变化追踪**:
```javascript
document.addEventListener("selectionchange", function(e) {
RDisplay.contentChanged(e);
});
RDisplay.contentChanged = function(e) {
// 检测链接点击
if (nodeName == 'a') {
param['link:'] = href;
param['link-title'] = title;
param['link-text'] = node.innerText;
}
wereadBridge.handleWithRichEditor("onSelectionChange", param, "", "");
}
```
**DOMContentLoaded通知**:
```javascript
document.addEventListener("DOMContentLoaded", function(event) {
wereadBridge.handleWithRichEditor("onDOMContentLoaded", "", "", "");
});
```
### 2.7 MediaPlatform.js - 公众号工具函数
**路径**: `output/resources/js/MediaPlatform.js` (126行)
公众号文章的实用工具函数。
**初始化**:
```javascript
mediaPlatform.init = function() {
// 更新空白高度
// 处理图片空白
// 保护懒加载
}
```
**信息提取**:
```javascript
mediaPlatform.getAuthor = function() {
// 为作者名称添加点击处理器
document.getElementById('js_name').addEventListener('click', ...);
}
mediaPlatform.getMPInfo = function() {
// 提取标题、缩略图URL、公众号名称
return {
title: title,
thumbnailUrl: thumbnailUrl,
accountName: accountName
};
}
```
**媒体控制**:
```javascript
pauseMedia()
pauseCurrentAudio()
pauseCurrentVideo()
```
### 2.8 mpForArticle.js - 公众号内容调整
**路径**: `output/resources/js/mpForArticle.js` (349行)
公众号文章内容的调整和优化。
**DOMContentLoaded处理**:
```javascript
document.addEventListener('DOMContentLoaded', function() {
adjustMPContent();
});
```
**内容调整**:
```javascript
adjustMPContent = function() {
// 1. 修复懒加载图片 (data-src → src)
// 2. 转换OAuth2授权链接为重定向URI
// 3. 设置视频iframe正确尺寸
// 4. 移除投票区域、小程序、小应用链接
// 5. 为<mpvoice>和<qqmusic>创建音频播放器
}
```
**音频播放器模板**:
```javascript
WRAUDIO_TEMPLATE = `
<div class="audio_container" onclick="...">
<div class="audio_button"></div>
<div class="audio_info">
<div class="audio_title">${title}</div>
<div class="audio_duration">${duration}</div>
</div>
<div class="audio_progress"></div>
</div>
`;
createWRAudio = function(title, src, duration) {
// 创建音频播放器DOM
}
```
### 2.9 mp_interceptor.js - 点击事件拦截器
**路径**: `output/resources/js/mp_interceptor.js` (74行)
公众号文章的点击事件拦截器。
**拦截目标**:
```javascript
// 作者点击
document.getElementById('js_name').addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
wereadBridge.handleWithRichEditor("clickMpInterceptor", {
contentType: "profile",
data: authorData
}, "", "");
});
// 版权信息点击
document.getElementById('copyright_info').addEventListener('click', function(e) {
// ...
});
// 公众号资料卡点击
document.querySelector('MP-COMMON-PROFILE').addEventListener('click', function(e) {
// ...
});
// 微信链接点击
document.querySelectorAll('a[href*="weixin.qq.com"]').forEach(function(el) {
el.addEventListener('click', function(e) {
wereadBridge.handleWithRichEditor("clickMpInterceptor", {
contentType: "link",
data: { href: el.href }
}, "", "");
});
});
```
### 2.10 Readability.js - 文章内容提取
**路径**: `output/resources/js/Readability.js` (1775行)
Arc90 Readability v1.7.1,用于从网页中提取可读内容。
**核心算法**:
- 基于段落内容的评分系统
- 类名权重article, story, content 等正面词 vs comment, meta 等负面词)
- 链接密度分析
- 多页支持与自动翻页
**使用场景**:
```javascript
// 从微信公众号文章中提取正文
var readable = new Readability(document);
var article = readable.parse();
// article.content - 提取的HTML
// article.title - 文章标题
// article.author - 作者
```
### 2.11 HtmlGenerator.js - HTML生成器
**路径**: `output/resources/js/HtmlGenerator.js` (27行)
HTML生成工具支持nonce注入。
```javascript
generator.generateHtml = function(content, js, css) {
// 移除headTrap/tailTrap注释
// 提取nonce: window.__nonce_str
// 注入JS和CSS
return html;
}
generator.generateHtmlForMPReview = function(content) {
// 特殊处理评论版权
}
```
### 2.12 cssInjector.js - CSS注入器
**路径**: `output/resources/js/cssInjector.js` (25行)
动态CSS样式管理工具。
```javascript
window.cssInjector = {
inject: function(json) {
// 创建/更新<style>元素按ID
var style = document.getElementById(json.id) || document.createElement('style');
style.id = json.id;
style.textContent = json.css;
document.head.appendChild(style);
},
remove: function(identifier) {
// 移除<style>元素按ID
var style = document.getElementById(identifier);
if (style) {
style.parentNode.removeChild(style);
}
}
};
```
### 2.13 其他工具脚本
#### patch_6.2.1.4.js (7行)
补丁脚本,修复版权文本大小写:
```javascript
// 将 "rights" 和 "reserved" 首字母大写
WRAboutViewController.viewDidLoad = function() {
// "All rights reserved" → "All Rights Reserved"
}
```
#### tencent-news-weread-custom.js (15行)
腾讯新闻自定义脚本:
```javascript
newsInjector.removeUnnecessary = function() {
// 隐藏"查看全文"提示
document.querySelectorAll('.detail-link').forEach(function(el) {
el.style.display = 'none';
});
}
```
#### InnerMP.js (27行)
内部公众号处理:
```javascript
(function() {
// 添加viewport meta标签
var meta = document.createElement('meta');
meta.name = 'viewport';
meta.content = 'width=device-width, initial-scale=1.0';
document.head.appendChild(meta);
// 隐藏标题前的空段落
// 隐藏第一个h1/h2/h3标题元素
})();
```
---
## 三、字体文件分析
### 3.1 字体清单
**路径**: `output/resources/fonts/`
| 字体文件 | 用途 |
|----------|------|
| `SourceHanSerifCN-Medium.ttf` | 默认正文字体(思源宋体) |
| `Lora-Regular.ttf` | Lora 衬线字体(常规) |
| `Lora-Italic.ttf` | Lora 衬线字体(斜体) |
| `PlayfairDisplay-Regular.ttf` | Playfair Display 字体 |
| `WeReadLS-Regular.otf` | WeRead LS 系列(常规) |
| `WeReadLS-Medium.otf` | WeRead LS 系列(中等) |
| `WeReadLS-Bold.otf` | WeRead LS 系列(粗体) |
| `FZJuZhenXinFang.ttf` | 方正聚珍新仿字体 |
| `OpenDyslexic-Regular.otf` | 阅读障碍友好字体 |
| `WeRead-Icon.ttf` | WeRead 图标字体 |
| `WeRead-Rating-Icon.ttf` | WeRead 评分图标字体 |
| `WeReadRN-Regular.ttf` | WeRead RN 字体 |
| `WeChatNumber.ttf` | 微信数字字体 |
| `SharpGroteskTRIAL*.ttf` | Sharp Grotesk 系列 |
| `icon_font.ttf` | 通用图标字体 |
| `weread_iconfont.ttf` | WeRead 图标字体 |
### 3.2 字体使用模式
**EPUB内容字体栈**:
```css
/* 标题和版权页 */
font-family: "SourceHanSerifCN-Medium";
/* 代码块 */
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
/* 拉丁语书籍标题 */
/* 不设置font-family使用浏览器默认 */
/* 单图片标题 */
font-family: "FZFSJW--GB1-0";
```
**公众号文章字体栈**:
```css
/* 主要内容 */
font-family: -apple-system;
/* 公众号章节 */
font-family: "Source Han Serif CN";
```
**编辑器字体栈**:
```css
/* 预览模式 */
font-family: -apple-system;
font-size: 18px;
line-height: 30px;
```
---
## 四、关键发现与实现细节
### 4.1 微信读书自定义CSS属性
**wr-vertical-center-style**:
-`1``.wr-vertical-center` 类,强制垂直居中
-`2``.bodyPic` 类,正文图片垂直居中样式
- 原生端通过解析此属性决定垂直对齐方式
**weread-page-relate**:
-`true`:标记元素与页面相关
- 用于分页计算时的元素识别
### 4.2 JS Bridge 通信机制
**iframe URL方案**:
```javascript
// 发送消息
iframe.src = 'wereadapijs://dispatch_message/';
// 接收结果
resultIframe.src = 'wereadapijs://private/setresult/' + scene + '&' + encodedData;
```
**消息队列**:
- 所有消息先进入 `_sendMessageQueue`
- 原生端通过 `fetchQueue()` 批量获取
- 每个消息带有唯一 `callbackId` 用于回调匹配
**数据编码**:
- UTF-8编码 → Base64编码 → URL拼接
- 确保中文等特殊字符正确传输
### 4.3 高亮系统架构
**基于Rangy的扩展**:
1. `rangy-core.js` - 核心范围操作
2. `rangy-textrange.js` - 字符级范围操作(支持图片文本)
3. `rangy-classapplier.js` - CSS类应用
4. `rangy-highlighter.js` - 高亮管理(序列化/反序列化)
5. `weread-highlighter.js` - 微信读书业务逻辑
**标注持久化**:
```javascript
// 序列化格式
"type:TextRange|start$end$id$className[|$containerId]"
// 反序列化
highlighter.deserialize(serializedString);
```
### 4.4 TTS实现
**文本提取**:
```javascript
getTTSText = function(startY) {
// 从Y坐标位置开始遍历DOM
// 提取文本节点
// 返回TTS文本和位置信息
}
```
**渐进高亮**:
```javascript
highlightTTSText = function(text, index, startY) {
// 移除之前的TTS高亮
// 根据index计算新的高亮位置
// 应用tts类
}
```
### 4.5 富文本编辑器设计
**基于contentEditable**:
- 使用 `document.execCommand` 执行格式化命令
- 通过 `backuprange`/`restorerange` 保持选择状态
- 粘贴拦截 → 发送到原生处理
**书籍/图片插入**:
```javascript
// 插入书籍卡片
RE.insertBook = function(id, title, author, cover) {
var html = `<div class="re_bookItem" data-id="${id}">
<img class="re_bookItem_cover" src="${cover}"/>
<div class="re_bookItem_title">${title}</div>
<div class="re_bookItem_author">${author}</div>
</div>`;
RE.insertHTML(html);
}
```
### 4.6 暗黑主题实现
**全局覆盖策略**:
```css
/* 所有元素强制黑色背景 */
* {
background-color: rgba(0, 0, 0, 1) !important;
}
```
**特殊元素处理**:
- 业务容器使用 `rgb(28, 28, 29)` 背景
- 文字颜色统一为 `rgba(180, 180, 182, 1)`
- 链接颜色为 `rgba(84, 127, 176, 1)`
### 4.7 公众号文章处理流程
```
1. 加载HTML内容
2. MediaPlatform.init() - 初始化
3. adjustMPContent() - 内容调整
├── 修复懒加载图片
├── 转换OAuth链接
├── 设置视频尺寸
├── 移除不需要的元素
└── 创建音频播放器
4. mp_interceptor.js - 设置点击拦截
5. weread-highlighter.js - 初始化标注系统
6. Readability.js - 可选的内容提取
```
### 4.8 代码高亮
使用 highlight.js 的 xcode 主题:
- 白色背景
- 彩色语法标记
- 支持多种编程语言
### 4.9 视频播放器定制
**腾讯视频覆盖**:
- 自定义控制栏样式
- 隐藏广告和应用下载提示
- 使用腾讯CDN雪碧图
- 设备特定适配iPhone6/6P
---
## 五、依赖关系图
```
┌─────────────────────────────────────────────────────────────┐
│ Native App (iOS) │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ WKWebView │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ WeReadApi.js (Bridge) │ │ │
│ │ │ wereadapijs:// protocol │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌───────────────┼───────────────┐ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ EPUB渲染 │ │ 公众号渲染 │ │ 富文本编辑 │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ replace.css│ │ MediaPlat │ │ rich_editor│ │ │
│ │ │ default.css│ │ form.css │ │ .js │ │ │
│ │ │ dark.css │ │ MPExtra.css│ │ rich_dis │ │ │
│ │ │ │ │ │ │ play.js │ │ │
│ │ │ │ │ highlighter│ │ │ │ │
│ │ │ │ │ .js │ │ │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Rangy Library │ │ │
│ │ │ core → textrange → classapplier → highlighter │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## 六、总结
微信读书的前端资源架构体现了以下设计特点:
1. **分层样式系统**:通过 default.css → replace.css/MediaPlatform.css → dark.css 的层叠,实现内容类型和主题的灵活切换
2. **原生桥接机制**WeReadApi.js 通过 iframe URL scheme 实现 JS 与原生的高效通信,支持异步回调
3. **扩展的Rangy库**:在标准 Rangy 基础上添加了图片文本支持、高亮合并、序列化等微信读书特有功能
4. **完整的标注系统**支持高亮、想法、好友想法、引用、TTS五种标注类型每种有独立的样式和交互
5. **富文本编辑能力**rich_editor.js 提供了完整的编辑功能,包括书籍/图片插入、格式化、链接管理等
6. **公众号文章优化**:通过 Readability.js 提取内容、mpForArticle.js 调整布局、mp_interceptor.js 拦截交互,实现公众号文章的原生体验
7. **多字体支持**提供思源宋体、Lora、Playfair Display等多种字体选择支持阅读障碍友好字体
8. **性能优化**选择变化防抖250ms、懒加载图片修复、iframe消息队列批量处理等
---
*分析完成时间: 2026年5月18日*
*分析文件数量: 25个23个完整分析2个因过大无法读取*