35 KiB
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属性:
/* 垂直居中样式 - 值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行)
纯黑背景的暗黑主题样式。
核心样式:
/* 所有元素强制黑色背景 */
* {
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)
特殊样式:
/* 打赏区域 */
.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行)
微信公众号文章的基础样式,包含标注系统。
主要内容区域:
.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) |
复合标注样式:
/* 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,从微信网页版复制。
核心特性:
/* 内容区域最大宽度限制 */
.rich_media_content * {
max-width: 100% !important;
box-sizing: border-box !important;
}
WeUI 对话框系统:
.weui-mask- 遮罩层.weui-dialog- 对话框
音频播放器样式:
.audio_container- 音频容器.audio_button- 播放按钮.audio_progress- 进度条
其他组件:
- Loading spinner(base64 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- 进度滑块
资源引用:
/* 使用腾讯CDN的雪碧图 */
background-image: url(https://rescdn.qqmail.com/weread/cover/icon/WREpubMPVideo.png);
广告隐藏:
/* 隐藏广告和应用下载提示 */
.tvp_ads_go, .tvp_app_download_onpause {
display: none !important;
}
1.11 Style/style.css - 富文本编辑器样式
路径: output/resources/css/Style/style.css (408行)
富文本编辑器的基础样式。
编辑器模式:
/* 编辑模式 */
.re_Write {
/* 可编辑状态样式 */
}
/* 显示模式 */
.re_Display {
/* 只读状态样式 */
}
/* 夜间模式 */
.re_Night {
color: rgba(180, 180, 182, 1);
background-color: rgba(0, 0, 0, 1);
}
书籍卡片组件:
.re_bookItem {
/* 书籍信息展示 */
}
.re_bookItem_cover {
/* 封面图片 */
}
.re_bookItem_title {
/* 书籍标题 */
}
.re_bookItem_author {
/* 作者信息 */
}
图片处理:
.re_img {
/* 图片样式 */
}
.bodyPic {
/* 正文图片 */
}
高DPI边框处理:
/* 使用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行)
预览模式的特殊样式。
核心样式:
.re {
font-size: 18px;
line-height: 30px;
font-family: -apple-system;
}
书籍卡片:
- 使用 0.5px 边框技术(retina 显示屏优化)
占位符文本:
.re_placeholder:before {
content: attr(placeholder);
color: #999;
}
引用块:
.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 类:
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) |
处理来自原生的回调响应 |
消息格式:
{
"func": "apiName",
"params": {
"key": "value",
"callbackId": "1001"
}
}
数据编码:
_utf8Encode(str)- UTF-8编码_base64Encode(str)- Base64编码- 用于通过iframe URL传递数据
Ready机制:
// 检查原生是否已注入信息
if (window["__WRB_INFO__"]) {
_onReady();
} else {
window["__WRB_INFO_CALL__"] = function() {
_onReady();
};
}
全局实例:
window.wereadBridge = new WeReadBridge();
2.3 weread-highlighter.js - 高亮/标注引擎
路径: output/resources/js/weread-highlighter.js (1530行)
公众号文章的核心高亮和标注引擎,基于 Rangy 库构建。
初始化:
// 初始化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 |
可点击蓝色 | 用于书籍标题等 |
核心函数:
// 初始化标注(从JSON加载)
initAnnotations(items)
// 创建高亮
didTapCreateHighlightButton()
// 创建想法
didTapCreateReviewButton(start, end)
// 删除标注
didTapDeleteAnnotationButton(start, end, type)
// 点击处理
click(el, event)
TTS系统:
// 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)
书籍标题检测:
// 查找所有《》包围的书籍标题
findAllBookTitles()
// 高亮选中的书籍标题
highlightSelectedBookTitles(indexArray)
链接处理:
// 获取所有前向链接
getAllForwardLinks()
// 转换H5链接为原生数据属性
transferH5Link()
// 解密微信读书加密链接
WRLink_decrypt(str)
// type '3' = 数字
// 其他 = 十六进制编码文本
选择变化处理:
// 防抖处理(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 类:
class CharacterRange {
constructor(start, end) { ... }
intersects(otherRange) { ... }
isContiguousWith(otherRange) { ... }
isContain(otherRange) { ... }
union(otherRange) { ... }
intersection(otherRange) { ... }
getComplements(orderedRanges) { ... }
}
Highlighter 类:
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 类:
class ClassApplier {
constructor(className, options) { ... }
applyToRange(range) { ... }
undoToRange(range) { ... }
applyToSelection(selection) { ... }
undoToSelection(selection) { ... }
isAppliedToRange(range) { ... }
isAppliedToSelection(selection) { ... }
toggleRange(range) { ... }
toggleSelection(selection) { ... }
}
暴露的工具方法:
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 类:
class Position {
constructor(node, offset) { ... }
// 字符类型检测
get characterType() { ... } // NON_SPACE, COLLAPSIBLE_SPACE, etc.
get isImage() { ... } // 图片文本支持
}
字符类型常量:
NON_SPACE- 非空格字符COLLAPSIBLE_SPACE- 可折叠空格NON_COLLAPSIBLE_SPACE- 不可折叠空格IMAGE_TEXT- 图片文本
Range 扩展方法:
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 命名空间:
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() |
失去焦点 |
数据分隔符:
RE.dataSeparator = 'r_e_ds'; // 用于连接多个值
内容变化通知:
RE.contentChanged = function(e) {
// 通知原生格式状态变化
wereadBridge.handleWithRichEditor("onSelectionChange", param, "", "");
}
EPUB HTML导出:
RE.getHtmlForEpub = function() {
// 移除临时类并应用correctArticle
return correctedHtml;
}
粘贴处理:
document.addEventListener('paste', function(e) {
// 拦截粘贴,发送到原生处理
wereadBridge.handleWithRichEditor("onPaste", {}, "", "");
});
2.6 rich_display.js - 富文本显示控制器
路径: output/resources/js/Text/rich_display.js (262行)
富文本内容的显示控制器,处理书籍和图片的交互。
书籍交互:
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}, "", "");
}
图片交互:
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
}, "", "");
}
文本提取:
RDisplay.getText = function() {
// 过滤零宽连字符
// 将书籍替换为[书籍]
// 将图片替换为[图片]
return tempDom.innerText;
}
选择变化追踪:
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通知:
document.addEventListener("DOMContentLoaded", function(event) {
wereadBridge.handleWithRichEditor("onDOMContentLoaded", "", "", "");
});
2.7 MediaPlatform.js - 公众号工具函数
路径: output/resources/js/MediaPlatform.js (126行)
公众号文章的实用工具函数。
初始化:
mediaPlatform.init = function() {
// 更新空白高度
// 处理图片空白
// 保护懒加载
}
信息提取:
mediaPlatform.getAuthor = function() {
// 为作者名称添加点击处理器
document.getElementById('js_name').addEventListener('click', ...);
}
mediaPlatform.getMPInfo = function() {
// 提取标题、缩略图URL、公众号名称
return {
title: title,
thumbnailUrl: thumbnailUrl,
accountName: accountName
};
}
媒体控制:
pauseMedia()
pauseCurrentAudio()
pauseCurrentVideo()
2.8 mpForArticle.js - 公众号内容调整
路径: output/resources/js/mpForArticle.js (349行)
公众号文章内容的调整和优化。
DOMContentLoaded处理:
document.addEventListener('DOMContentLoaded', function() {
adjustMPContent();
});
内容调整:
adjustMPContent = function() {
// 1. 修复懒加载图片 (data-src → src)
// 2. 转换OAuth2授权链接为重定向URI
// 3. 设置视频iframe正确尺寸
// 4. 移除投票区域、小程序、小应用链接
// 5. 为<mpvoice>和<qqmusic>创建音频播放器
}
音频播放器模板:
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行)
公众号文章的点击事件拦截器。
拦截目标:
// 作者点击
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 等负面词)
- 链接密度分析
- 多页支持与自动翻页
使用场景:
// 从微信公众号文章中提取正文
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注入。
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样式管理工具。
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行)
补丁脚本,修复版权文本大小写:
// 将 "rights" 和 "reserved" 首字母大写
WRAboutViewController.viewDidLoad = function() {
// "All rights reserved" → "All Rights Reserved"
}
tencent-news-weread-custom.js (15行)
腾讯新闻自定义脚本:
newsInjector.removeUnnecessary = function() {
// 隐藏"查看全文"提示
document.querySelectorAll('.detail-link').forEach(function(el) {
el.style.display = 'none';
});
}
InnerMP.js (27行)
内部公众号处理:
(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内容字体栈:
/* 标题和版权页 */
font-family: "SourceHanSerifCN-Medium";
/* 代码块 */
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
/* 拉丁语书籍标题 */
/* 不设置font-family,使用浏览器默认 */
/* 单图片标题 */
font-family: "FZFSJW--GB1-0";
公众号文章字体栈:
/* 主要内容 */
font-family: -apple-system;
/* 公众号章节 */
font-family: "Source Han Serif CN";
编辑器字体栈:
/* 预览模式 */
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方案:
// 发送消息
iframe.src = 'wereadapijs://dispatch_message/';
// 接收结果
resultIframe.src = 'wereadapijs://private/setresult/' + scene + '&' + encodedData;
消息队列:
- 所有消息先进入
_sendMessageQueue - 原生端通过
fetchQueue()批量获取 - 每个消息带有唯一
callbackId用于回调匹配
数据编码:
- UTF-8编码 → Base64编码 → URL拼接
- 确保中文等特殊字符正确传输
4.3 高亮系统架构
基于Rangy的扩展:
rangy-core.js- 核心范围操作rangy-textrange.js- 字符级范围操作(支持图片文本)rangy-classapplier.js- CSS类应用rangy-highlighter.js- 高亮管理(序列化/反序列化)weread-highlighter.js- 微信读书业务逻辑
标注持久化:
// 序列化格式
"type:TextRange|start$end$id$className[|$containerId]"
// 反序列化
highlighter.deserialize(serializedString);
4.4 TTS实现
文本提取:
getTTSText = function(startY) {
// 从Y坐标位置开始遍历DOM
// 提取文本节点
// 返回TTS文本和位置信息
}
渐进高亮:
highlightTTSText = function(text, index, startY) {
// 移除之前的TTS高亮
// 根据index计算新的高亮位置
// 应用tts类
}
4.5 富文本编辑器设计
基于contentEditable:
- 使用
document.execCommand执行格式化命令 - 通过
backuprange/restorerange保持选择状态 - 粘贴拦截 → 发送到原生处理
书籍/图片插入:
// 插入书籍卡片
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 暗黑主题实现
全局覆盖策略:
/* 所有元素强制黑色背景 */
* {
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 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
六、总结
微信读书的前端资源架构体现了以下设计特点:
-
分层样式系统:通过 default.css → replace.css/MediaPlatform.css → dark.css 的层叠,实现内容类型和主题的灵活切换
-
原生桥接机制:WeReadApi.js 通过 iframe URL scheme 实现 JS 与原生的高效通信,支持异步回调
-
扩展的Rangy库:在标准 Rangy 基础上添加了图片文本支持、高亮合并、序列化等微信读书特有功能
-
完整的标注系统:支持高亮、想法、好友想法、引用、TTS五种标注类型,每种有独立的样式和交互
-
富文本编辑能力:rich_editor.js 提供了完整的编辑功能,包括书籍/图片插入、格式化、链接管理等
-
公众号文章优化:通过 Readability.js 提取内容、mpForArticle.js 调整布局、mp_interceptor.js 拦截交互,实现公众号文章的原生体验
-
多字体支持:提供思源宋体、Lora、Playfair Display等多种字体选择,支持阅读障碍友好字体
-
性能优化:选择变化防抖(250ms)、懒加载图片修复、iframe消息队列批量处理等
分析完成时间: 2026年5月18日 分析文件数量: 25个(23个完整分析,2个因过大无法读取)