跳到主要内容
SnapDOM + jsPDF 高保真 HTML 转 PDF 实践指南 | 极客日志
JavaScript AI 大前端
SnapDOM + jsPDF 高保真 HTML 转 PDF 实践指南 综述由AI生成 介绍基于 SnapDOM 和 jsPDF 的高保真 HTML 转 PDF 方案,解决了传统 html2canvas 在样式还原、布局兼容及清晰度上的不足。文章详细阐述了环境准备、DOM 预处理、高清截图、智能分页及 PDF 生成的核心实现步骤,并提供了性能优化策略(如 Web Worker)及高级特性(页眉页脚、元数据)。此外,还探讨了结合 AI 进行布局分析与样式增强的未来方向,以及在不同业务场景下的适配方案与测试策略,为开发者提供完整的现代化导出解决方案。
剑仙 发布于 2026/3/23 更新于 2026/5/6 5.5K 浏览一、引言:为何我们需要告别'远古'的 HTML 转 PDF 方案?
1.1 无处不在的导出需求
在数字化浪潮中,PDF 作为一种跨平台、格式固定的文档格式,其地位无可替代。试想以下场景,你是否感到熟悉?
📊 报表系统 :用户在线分析了复杂的数据看板后,希望将最终的图表和结论一键导出 为报告,用于邮件汇报或线下存档。
🛒 电商交易 :用户完成购物,需要一张格式工整、细节无误 的电子发票或订单详情单,作为报销或售后凭证。
💬 客服工单 :一场漫长的在线技术支持对话结束后,双方都希望将完整的沟通记录固化下来 ,形成可追溯的工单文档。
📑 合同协议 :在线签署的电子合同,最终必须以一份不可篡改、版式严谨 的 PDF 文件交付给各方。
这些场景都对前端导出 PDF 的质量 、效率 和可靠性 提出了极高要求。
1.2 传统方案的'阿喀琉斯之踵':为何html2canvas力有不逮?
长久以来,html2canvas配合jsPDF是前端实现 HTML 转 PDF 的'标配'方案。其核心思路是:先将 DOM 元素渲染到<canvas>画布上,再将 canvas 图像内容嵌入 PDF。然而,这一方案在面对日益复杂的现代 Web 应用时,其弊端暴露无遗:
痛点维度 html2canvas 方案表现带来的业务困扰 🎨 样式还原度 部分 CSS3 属性(如混合模式、滤镜)无法完美支持,字体、间距可能存在细微偏差。 导出的 PDF 与用户在网页上看到的'长得不一样',专业度大打折扣。 📏 布局兼容性 对 Flexbox、Grid 等现代布局模型的解析有时会出现错乱,导致内容重叠或错位。 精心设计的响应式布局在 PDF 中面目全非,需要为导出做大量适配 hack。 🖼 图像清晰度 尽管可以通过缩放倍数提升清晰度,但会显著增加处理时间和文件体积,权衡困难。 二维码、条形码等关键信息可能模糊不清,影响扫描识别。 ⚡ 性能与内存 渲染超长页面时容易导致浏览器卡顿甚至内存溢出(OOM)。 用户体验差,无法导出大型报表或长对话记录。 📦 维护性 库的更新迭代相对缓慢,对新浏览器特性的跟进可能存在延迟。 长期项目有技术债风险。
流程图:传统方案的核心痛点闭环
样式/布局解析偏差 -> 开发者投入额外 hack 成本 -> 复杂现代 UI 布局 -> html2canvas 渲染 -> Canvas 图像失真 -> jsPDF 打包成 PDF -> 最终 PDF 质量不佳 -> 用户不满意/业务价值低
这个闭环清晰地表明,传统方案已难以满足当前高品质、高效率的导出需求。我们迫切需要一种新的、更可靠的破局之道。
二、新王登基:为什么是 SnapDOM + jsPDF?
2.1 黄金组合:珠联璧合的技术选型
我们的新方案由两位'主角'构成:
SnapDOM:高保真 DOM 快照捕捉器
SnapDOM 是一个新兴的、专为高质量 DOM 截图而生的前端库。它的设计哲学不同于html2canvas的'模拟渲染',而是致力于 。它更轻量(压缩后约 20KB),对现代 CSS 特性(如 Flexbox、Grid、渐变、阴影)的支持近乎完美,从根源上保障了源与果的一致性。
更精准地捕获 DOM 的视觉表现
jsPDF:成熟稳定的 PDF 生成引擎
jsPDF 是久经考验的纯 JavaScript PDF 生成库。它提供了丰富而灵活的 API,用于创建页面、添加文本、图像、矢量图形,并支持多种压缩算法。它的职责是将 SnapDOM 生成的高清图像,按照 PDF 的规范进行分页、组装和输出。这个组合的核心优势在于'各司其职' :SnapDOM 专注于完美地'拍照' ,jsPDF 专注于专业地'装订' 。这种解耦使得整个流程更加清晰、可靠和易于维护。
2.2 方案核心优势对比 为了更直观地展示新方案的优越性,我们进行一场全方位的'擂台赛':
特性维度 SnapDOM + jsPDF (新方案) html2canvas + jsPDF (传统方案) 优势解读 🎯 保真度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ SnapDOM 从底层渲染引擎捕获样式,还原度极高,所见即所得。 🛠 现代 CSS 支持 ⭐⭐⭐⭐⭐ ⭐⭐ 对 Flex/Grid/渐变/字体支持出色,无需为导出调整布局。 📷 清晰度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ 默认支持高分屏(Retina)截图,文字和图像边缘锐利。 🚀 性能 ⭐⭐⭐⭐ ⭐⭐⭐ 更轻量,渲染效率更高,尤其在复杂页面上优势明显。 🧩 维护性 ⭐⭐⭐⭐ ⭐⭐ SnapDOM 更活跃,API 设计更现代,技术债风险低。 📚 社区生态 ⭐⭐⭐ ⭐⭐⭐⭐ jsPDF 生态强大,但 SnapDOM 作为新秀,社区仍在成长中。
三、庖丁解牛:方案核心原理与实现步骤
3.1 整体架构与工作流 一套健壮的导出流程,其内部运作如同一条精密的自动化生产线。下图清晰地展示了从 HTML 到 PDF 的完整工作流:
成功 -> 失败 -> 原始 HTML 节点 -> 预处理 样式调整 -> SnapDOM 高清截图 -> 高清 Canvas/PNG -> 降级处理 基础 Canvas -> 分页计算与切割 -> PDF Page 1 -> PDF Page 2 -> ... -> jsPDF 组装与压缩 -> 最终 PDF 文件
3.2 步步为营:核心代码实现详解 接下来,我们将按照工作流,逐一拆解每个关键步骤的实现细节。
步骤一:环境准备与依赖引入
npm install snappdom jspdf
import SnapDOM from 'snapdom' ;
import jsPDF from 'jspdf' ;
步骤二:DOM 预处理 - '磨刀不误砍柴工' 在截图前,对目标 DOM 节点进行适当的预处理,是确保结果完美的关键。这就像拍照前整理场景一样重要。
function prepareElementForSnapshot (element ) {
const originalStyle = {
overflow : element.style .overflow ,
overflowX : element.style .overflowX ,
overflowY : element.style .overflowY ,
};
Object .assign (element.style , {
overflow : 'visible' ,
overflowX : 'visible' ,
overflowY : 'visible' ,
});
element.offsetHeight ;
return () => {
Object .assign (element.style , originalStyle);
};
}
const targetElement = document .getElementById ('export-content' );
const restoreStyles = prepareElementForSnapshot (targetElement);
步骤三:高保真截图 - SnapDOM 的核心魔法 这是整个流程的灵魂所在。我们配置 SnapDOM 以高质量完成截图。
async function captureHighFidelitySnapshot (element, options = {} ) {
const {
scale = 2 ,
backgroundColor = '#ffffff' ,
quality = 1 ,
} = options;
try {
const snapDOM = new SnapDOM ();
const snapshotOptions = {
scale,
backgroundColor,
quality,
useCORS : true ,
allowTaint : false ,
logging : false ,
};
const dataUrl = await snapDOM.toPng (element, snapshotOptions);
return dataUrl;
} catch (error) {
console .error ('SnapDOM 截图失败,尝试降级方案:' , error);
try {
const canvas = await snapDOM.toCanvas (element, { scale, backgroundColor });
return canvas.toDataURL ('image/png' , quality);
} catch (fallbackError) {
console .error ('降级方案也失败:' , fallbackError);
throw new Error ('无法完成 DOM 截图' );
}
}
}
步骤四:智能分页 - 从一张长图到多页 PDF 拿到高清长图后,我们需要根据 PDF 的页面尺寸(如 A4)将其智能地分割成多页。
function paginateImageForPDF (dataUrl, pdfConfig ) {
const { pageWidth, pageHeight, pageMargin = 0 } = pdfConfig;
return new Promise ((resolve ) => {
const img = new Image ();
img.src = dataUrl;
img.onload = () => {
const canvas = document .createElement ('canvas' );
const ctx = canvas.getContext ('2d' );
const contentWidth = pageWidth - (pageMargin * 2 );
const contentHeight = pageHeight - (pageMargin * 2 );
const scaledContentWidth = contentWidth * img.width / img.naturalWidth ;
const scaledContentHeight = contentHeight * img.height / img.naturalWidth ;
const totalPages = Math .ceil (img.height / scaledContentHeight);
const pages = [];
for (let i = 0 ; i < totalPages; i++) {
canvas.width = scaledContentWidth;
canvas.height = Math .min (scaledContentHeight, img.height - (i * scaledContentHeight));
ctx.clearRect (0 , 0 , canvas.width , canvas.height );
ctx.drawImage (
img,
0 ,
i * scaledContentHeight,
img.width ,
Math .min (scaledContentHeight, img.height - (i * scaledContentHeight)),
0 ,
0 ,
canvas.width ,
canvas.height
);
pages.push (canvas.toDataURL ('image/png' , 1.0 ));
}
resolve (pages);
};
});
}
步骤五:PDF 生成与导出 - 最后的临门一脚 最后,使用 jsPDF 将分页后的图片组装成最终的 PDF 文件。
function generatePDFFromPages (pageImages, options = {} ) {
const {
filename = 'exported-document.pdf' ,
orientation = 'portrait' ,
unit = 'mm' ,
format = 'a4' ,
pageMargin = 10 ,
compress = true ,
} = options;
const pdf = new jsPDF ({ orientation, unit, format, compress });
const pageWidth = pdf.internal .pageSize .getWidth ();
const pageHeight = pdf.internal .pageSize .getHeight ();
const contentWidth = pageWidth - (pageMargin * 2 );
const contentHeight = pageHeight - (pageMargin * 2 );
pageImages.forEach ((imgData, index ) => {
if (index > 0 ) {
pdf.addPage ();
}
pdf.addImage ({
imageData : imgData,
x : pageMargin,
y : pageMargin,
width : contentWidth,
height : contentHeight,
});
});
pdf.save (filename);
}
步骤六:统一的导出入口函数 现在,我们将所有步骤整合到一个统一的函数中,提供简洁的 API 给业务层调用。
async function exportToPDF (config = {} ) {
const {
elementSelector = '#export-content' ,
filename = `export-${new Date ().getTime()} .pdf` ,
scale = 2 ,
quality = 0.95 ,
onProgress = null ,
} = config;
try {
const targetElement = document .querySelector (elementSelector);
if (!targetElement) {
throw new Error (`未找到选择器为 ${elementSelector} 的元素` );
}
if (onProgress) onProgress (10 , '开始预处理 DOM' );
const restoreStyles = prepareElementForSnapshot (targetElement);
if (onProgress) onProgress (30 , '正在进行高保真截图' );
const snapshotDataUrl = await captureHighFidelitySnapshot (targetElement, {
scale,
quality,
});
if (onProgress) onProgress (60 , '图片分页处理中' );
const pageImages = await paginateImageForPDF (snapshotDataUrl, {
pageWidth : 210 ,
pageHeight : 297 ,
pageMargin : 10 ,
});
if (onProgress) onProgress (90 , '生成 PDF 文件中' );
generatePDFFromPages (pageImages, { filename });
restoreStyles ();
if (onProgress) onProgress (100 , '导出完成' );
return {
success : true ,
message : 'PDF 导出成功' ,
};
} catch (error) {
console .error ('导出 PDF 失败:' , error);
if (onProgress) onProgress (0 , `导出失败:${error.message} ` );
return {
success : false ,
message : `导出失败:${error.message} ` ,
};
}
}
document .getElementById ('export-btn' ).addEventListener ('click' , async () => {
const result = await exportToPDF ({
elementSelector : '.report-container' ,
filename : `业务报表-${new Date ().toLocaleDateString()} .pdf` ,
onProgress : (percent, message ) => {
updateProgressBar (percent, message);
},
});
if (!result.success ) {
alert (result.message );
}
});
四、锦上添花:性能优化与高级特性
4.1 性能优化:让导出体验如丝般顺滑
优化一:分段截图与流式生成 对于超长内容,可以将其分割成多个部分分别截图,避免一次性处理导致的性能问题。
async function captureInSections (container, sectionHeight = 2000 ) {
const sections = [];
let currentPosition = 0 ;
const totalHeight = container.scrollHeight ;
const originalOverflow = container.style .overflow ;
container.style .overflow = 'hidden' ;
while (currentPosition < totalHeight) {
container.scrollTop = currentPosition;
await new Promise ((resolve ) => requestAnimationFrame (resolve));
const sectionData = await captureHighFidelitySnapshot (container, {
scale : 2 ,
windowHeight : Math .min (sectionHeight, totalHeight - currentPosition),
});
sections.push ({
data : sectionData,
height : Math .min (sectionHeight, totalHeight - currentPosition),
});
currentPosition += sectionHeight;
await new Promise ((resolve ) => setTimeout (resolve, 50 ));
}
container.style .overflow = originalOverflow;
container.scrollTop = 0 ;
return sections;
}
优化二:Web Worker 异步处理 将耗时的截图和分页操作放入 Web Worker,避免阻塞主线程。
self.addEventListener ('message' , async (e) => {
const { type, payload } = e.data ;
if (type === 'EXPORT_PDF' ) {
try {
const result = await processExport (payload);
self.postMessage ({
success : true ,
data : result,
});
} catch (error) {
self.postMessage ({
success : false ,
error : error.message ,
});
}
}
});
class PDFExportWorker {
constructor ( ) {
this .worker = new Worker ('./pdf-worker.js' );
}
async export (config ) {
return new Promise ((resolve, reject ) => {
this .worker .onmessage = (e ) => {
if (e.data .success ) {
resolve (e.data .data );
} else {
reject (new Error (e.data .error ));
}
};
this .worker .postMessage ({
type : 'EXPORT_PDF' ,
payload : config,
});
});
}
}
4.2 高级特性:满足复杂业务需求
特性一:添加页眉页脚 通过操作 PDF 上下文,可以为每一页添加自定义的页眉页脚。
function addHeaderFooter (pdf, headerText, footerText ) {
const totalPages = pdf.getNumberOfPages ();
for (let i = 1 ; i <= totalPages; i++) {
pdf.setPage (i);
pdf.setFontSize (10 );
pdf.setTextColor (100 );
pdf.text (headerText, pdf.internal .pageSize .getWidth () / 2 , 10 , {
align : 'center' ,
});
const pageText = `${footerText} - 第 ${i} 页 / 共 ${totalPages} 页` ;
pdf.text (
pageText,
pdf.internal .pageSize .getWidth () / 2 ,
pdf.internal .pageSize .getHeight () - 10 ,
{
align : 'center' ,
}
);
}
}
特性二:PDF 元数据和安全性 设置 PDF 的元数据,并可以添加基础的安全保护。
function setPDFMetadata (pdf, metadata = {} ) {
const {
title = '导出文档' ,
subject = '通过 SnapDOM + jsPDF 生成' ,
author = '业务系统' ,
keywords = '导出,报表,PDF' ,
creator = 'SnapDOM-to-PDF Processor' ,
} = metadata;
pdf.setProperties ({
title,
subject,
author,
keywords,
creator,
});
pdf.setEncryption ({
userPassword : '' ,
ownerPassword : 'owner-pass' ,
permissions : {
printing : 'lowResolution' ,
modifying : false ,
copying : true ,
annotating : true ,
fillingForms : true ,
contentAccessibility : true ,
documentAssembly : true ,
},
});
}
五、智见未来:AI 增强的 HTML 转 PDF 新范式 在 AI 技术蓬勃发展的今天,我们可以将智能能力融入传统工作流,创造出更强大的解决方案。
5.1 AI 辅助的布局分析与优化 痛点 :复杂动态布局在分页时可能导致元素被不当切割,影响阅读体验。
AI 解决方案 :使用计算机视觉模型分析 DOM 截图,识别逻辑内容块(如表格、段落、图片),实现智能分页。
原始 HTML -> SnapDOM 高清截图 -> AI 布局分析 -> 识别内容区块 -> 智能分页决策 -> 避免在表格/图片中间分页 -> 优化后的分页结果 -> 调整布局适应 PDF -> 高质量 PDF 输出
5.2 智能样式修复与增强 痛点 :网页样式在转换为 PDF 时,某些效果(如半透明、复杂动画)可能丢失。
AI 解决方案 :训练深度学习模型学习网页样式到 PDF 样式的映射,自动进行样式修复和优化。
async function enhanceWithAI (domElement, pdfConfig ) {
const styleFeatures = extractStyleFeatures (domElement);
const aiSuggestions = await fetchAIStyleSuggestions ({
features : styleFeatures,
targetMedium : 'pdf' ,
config : pdfConfig,
});
return applyAISuggestions (domElement, aiSuggestions);
}
const aiSuggestions = {
contrast : 1.2 ,
fontSizes : {
base : '14pt' ,
heading : '18pt' ,
},
colorAdjustment : {
saturation : 1.1 ,
brightness : 0.95 ,
},
layout : 'single-column' ,
};
5.3 个性化与自适应输出 痛点 :一份数据需要根据不同用户角色生成不同样式和内容的 PDF。
AI 解决方案 :基于用户画像和行为数据,智能生成个性化的 PDF 文档。
用户类型 传统方案输出 AI 增强输出 管理层 详细数据报表 智能摘要 :关键指标突出显示,自动生成执行摘要技术员 统一技术文档 深度版本 :包含技术细节、调试日志、相关文档链接客户 标准合同文本 通俗版本 :专业术语解释,重点条款提示,个性化问候
六、实战演练:典型业务场景深度适配
6.1 场景一:大型数据报表导出 挑战 :报表通常包含大量数据表格、图表,需要保持高清晰度和正确分页。
class ReportExporter {
constructor ( ) {
this .complexCharts = [];
}
async exportReport (config ) {
await this .ensureChartsRendered ();
this .replaceAnimationsWithStatic ();
const result = await exportToPDF ({
...config,
scale : 3 ,
preProcess : (element ) => this .preProcessReport (element),
});
this .restoreAnimations ();
return result;
}
async ensureChartsRendered ( ) {
const chartPromises = this .complexCharts .map ((chart ) => {
return new Promise ((resolve ) => {
if (chart.animationComplete ) {
resolve ();
} else {
chart.onAnimationComplete = resolve;
}
});
});
await Promise .all (chartPromises);
}
}
6.2 场景二:聊天记录与会话导出 挑战 :聊天记录具有时序性,需要保持对话连贯性,避免消息被分页切断。
function exportConversation (messages, config = {} ) {
const messageGroups = groupMessagesByPage (messages, {
maxPageHeight : 280 ,
keepRepliesTogether : true ,
});
const pagesHTML = messageGroups.map ((group, index ) => {
return generateConversationPage (group, {
pageNumber : index + 1 ,
totalPages : messageGroups.length ,
showTimestamps : config.showTimestamps ,
});
});
return exportPaginatedContent (pagesHTML, config);
}
function groupMessagesByPage (messages, config ) {
const groups = [];
let currentGroup = [];
let currentHeight = 0 ;
for (const message of messages) {
const messageHeight = calculateMessageHeight (message);
if (
currentHeight + messageHeight > config.maxPageHeight &&
!isStartOfThread (message)
) {
groups.push ([...currentGroup]);
currentGroup = [message];
currentHeight = messageHeight;
} else {
currentGroup.push (message);
currentHeight += messageHeight;
}
}
if (currentGroup.length > 0 ) {
groups.push (currentGroup);
}
return groups;
}
七、质量保障:测试策略与最佳实践
7.1 自动化测试金字塔 为确保导出功能的可靠性,需要建立多层次的测试策略。
单元测试 -> 核心工具函数
集成测试 -> 模块间协作
视觉回归测试 -> PDF 输出对比
E2E 测试 -> 完整用户流程
7.2 核心工具函数单元测试示例
import {
prepareElementForSnapshot,
paginateImageForPDF,
} from '../src/pdf-utils' ;
describe ('PDF 工具函数' , () => {
test ('DOM 预处理应正确修改并恢复样式' , () => {
document .body .innerHTML = '<div></div>' ;
const element = document .getElementById ('test' );
const restore = prepareElementForSnapshot (element);
expect (element.style .overflow ).toBe ('visible' );
restore ();
expect (element.style .overflow ).toBe ('auto' );
});
test ('图片分页应正确处理不同尺寸' , async () => {
const mockImageData = 'data:image/png;base64,...' ;
const pages = await paginateImageForPDF (mockImageData, {
pageWidth : 210 ,
pageHeight : 297 ,
});
expect (pages.length ).toBeGreaterThan (1 );
expect (pages[0 ]).toMatch (/^data:image\/png;base64,/ );
});
});
7.3 视觉回归测试 使用类似jest-image-snapshot进行 PDF 输出的视觉一致性测试。
describe ('PDF 视觉回归测试' , () => {
test ('业务报表导出应保持视觉一致性' , async () => {
renderTestReport ();
const pdfBuffer = await exportToPDF ({
elementSelector : '.test-report' ,
returnAsBuffer : true ,
});
const pdfImage = await convertPDFToImage (pdfBuffer);
expect (pdfImage).toMatchImageSnapshot ({
customDiffConfig : {
threshold : 0.1 ,
},
failureThreshold : 0.02 ,
failureThresholdType : 'percent' ,
});
});
});
八、总结与展望
8.1 方案价值回顾 SnapDOM + jsPDF 方案为前端 PDF 导出带来了质的飞跃 :
🎯 高保真度 :真正实现'所见即所得',完美还原现代 CSS 布局
⚡ 优异性能 :轻量高效,有效避免内存溢出问题
🛠 易于维护 :现代 API 设计,清晰的架构分离
🚀 面向未来 :良好的 AI 集成能力,支持智能化演进
8.2 未来演进方向 随着技术的不断发展,HTML 转 PDF 方案还将继续进化:
方向 描述 潜在影响 Web Assembly 增强 使用 WASM 重编核心算法,进一步提升性能 处理速度提升 5-10 倍 3D 内容支持 支持导出 WebGL、3D 模型等内容 扩展导出内容范围 实时协作集成 支持多人同时编辑的文档导出 满足协同办公需求 无障碍优化 自动生成符合无障碍标准的 PDF 满足法规要求,提升包容性
8.3 实施建议 开始使用 SnapDOM + jsPDF 的方案 :
npm install snappdom jspdf
从小规模开始,逐步扩展到复杂场景
建立监控机制,跟踪导出成功率与性能指标
定期更新依赖版本,获取性能改进和新特性
考虑服务端渲染 (SSR) 场景下的兼容性处理
通过本文的详细阐述,我们不仅提供了一个高质量的 HTML 转 PDF 技术实现,更重要的是展示了一种面向未来、可演进的技术架构思路 。在 AI 技术快速发展的背景下,传统的前端任务正迎来智能化升级的历史机遇。SnapDOM + jsPDF 方案既是当前业务需求的优秀解答,也是通向更智能文档处理世界的桥梁。
技术的选择不应只看眼前,更要考量其长期演进潜力 。这正是 SnapDOM + jsPDF 组合的核心价值所在——它为我们打下了坚实的地基,让我们能够在上面建造更加智能、高效的文档处理大厦。
相关免费在线工具 RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online