在现代Web应用中集成 PDF.js (pdfjs-dist 5.2 ESM): 通过 jsdelivr 实现动态加载与批注功能的思考

PDF 文档在现代 Web 应用中越来越常见,无论是作为文档预览、报告展示还是在线编辑的载体。Mozilla 的 PDF.js 是一个功能强大的 JavaScript 库,它使得在浏览器端渲染和显示 PDF 文件成为可能,无需依赖原生插件。

本文将深入探讨如何在你的项目中使用 pdfjs-dist 库的 5.2 版本,特别关注其通过 jsdelivr 引入的 ESM (ECMAScript Module) 版本 (.mjs 文件),并在此基础上实现 PDF 文件的动态加载,同时对实现批注功能给出思路和指导。

为什么选择 pdfjs-dist 5.2 ESM 和 jsdelivr?

  • pdfjs-dist: 这是 PDF.js 的发布版本,包含了核心渲染代码和相关的构建产物,方便在项目中使用。
  • 5.2 版本: 选择特定版本有助于保证代码的稳定性,避免未来版本更新可能带来的兼容性问题。
  • ESM (.mjs): ECMAScript Modules 是现代 JavaScript 的标准模块系统。使用 ESM 可以更好地组织代码、提高性能(如 tree shaking)并避免全局变量污染。它需要现代浏览器的支持,并通过 <script type="module"> 标签引入。
  • jsdelivr: 这是一个免费的、快速的 CDN (Content Delivery Network),可以直接从 npm 包获取文件。使用 CDN 可以加速库的加载,减轻自己服务器的压力。

第一步:核心集成 - 引入 pdfjs-dist ESM 版本

使用 ESM 格式引入库需要在你的 HTML 文件中做一些调整。我们需要引入 pdf.min.mjs(核心库)和 pdf.worker.min.mjs(用于在 Web Worker 中执行耗时任务)。

在你的 HTML 文件中,使用 <script type="module"> 标签来编写或引用你的 JavaScript 代码:

<!DOCTYPE html>
<html>
<head><title>PDF.js ESM Integration</title><meta charset="UTF-8"><style>/* 可以添加一些基本的样式 */#pdf-viewer-container {width: 800px; /* 根据需要设置容器宽度 */margin: 20px auto;border: 1px solid #ccc;overflow: auto; /* 如果PDF很大需要滚动 */}/* 其他样式将在后续步骤中添加 */</style>
</head>
<body><h1>PDF.js ESM Example</h1><!-- 你的 PDF 查看器 UI 元素将在这里 --><!-- 使用 type="module" 引入你的主要 JavaScript 文件 --><!-- 假设你的主要逻辑在 main.js 中 --><script type="module" src="./main.js"></script></body>
</html>

在你的 main.js 文件中,使用 import 语句从 jsdelivr 引入 pdfjs-dist

// 从 jsdelivr 引入 pdfjs-dist 的核心模块
// 使用具体的版本号 5.2.133 (这是一个示例版本号,请根据实际需要调整)
import { getDocument, GlobalWorkerOptions } from 'https://cdn.jsdelivr.net/npm/pdfjs-dist@5.2.133/build/pdf.min.mjs';// 设置 workerSrc,这是 pdfjs-dist 必须的配置
// workerSrc 应该指向 pdf.worker.min.mjs 文件,且版本应与主库一致
GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@5.2.133/build/pdf.worker.min.mjs';// 现在你可以使用导入的 getDocument 函数来加载 PDF
// 例如加载一个远程 PDF 文件 (这将在下一节详细展开)
/*
async function loadSamplePdf() {const samplePdfUrl = 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf';try {const loadingTask = getDocument(samplePdfUrl);const pdfDocument = await loadingTask.promise;console.log('Sample PDF loaded:', pdfDocument);// TODO: 渲染 PDF 页面} catch (error) {console.error('Error loading sample PDF:', error);}
}loadSamplePdf(); // 页面加载后尝试加载一个示例 PDF
*/

解释:

  • <script type="module" src="./main.js">:告诉浏览器加载 ./main.js 文件作为一个 ES 模块。
  • import { getDocument, GlobalWorkerOptions } from '...':使用 ESM 导入语法,从 jsdelivr 上的 pdf.min.mjs 导入 getDocument 函数和 GlobalWorkerOptions 对象。
  • GlobalWorkerOptions.workerSrc = '...'非常重要,这配置了 PDF.js worker 脚本的 URL。worker 负责在后台处理 PDF 的解析,避免阻塞主线程,提高用户体验。

第二步:实现 PDF 文件的动态加载与渲染

在实际应用中,你通常需要根据用户的操作(例如选择文件或输入 URL)来加载不同的 PDF 文件。我们需要一个 HTML 结构来接收用户输入,并编写 JavaScript 代码来处理加载和渲染过程。

扩展你的 HTML (在 <body> 内):

<!-- ... (head and previous body content) ... -->
<body><h1>My Dynamic PDF Viewer</h1><input type="file" id="pdfFilePicker" accept="application/pdf"><button id="loadPdfButton">Load Selected PDF</button><button id="loadUrlPdfButton">Load Sample PDF from URL</button><div id="pdf-viewer-container"><!-- PDF 页面将渲染到这里 --></div><script type="module" src="./main.js"></script>
</body>
</html>

添加必要的 CSS (在 <style> 标签内):

/* ... (previous styles) ... */
.pdfPage {margin-bottom: 10px; /* 页与页之间的间距 */border-bottom: 1px solid #eee; /* 页之间分隔线 */position: relative; /* 为后续添加批注层做准备 */box-shadow: 0 0 8px rgba(0,0,0,0.1); /* 添加一些阴影效果 */
}
.pdfPage canvas {display: block; /* 防止 canvas 下方出现空白 */margin: 0 auto; /* canvas 居中 */
}
/* 批注层样式,如果需要 */
.annotationLayer {position: absolute;top: 0;left: 0;width: 100%;height: 100%;pointer-events: none; /* 默认不捕获鼠标事件,除非需要交互 */overflow: hidden; /* 避免批注超出页面边界 */
}

修改 main.js 来实现动态加载和渲染逻辑:

// ... (import and workerSrc setup) ...const pdfViewerContainer = document.getElementById('pdf-viewer-container');
const pdfFilePicker = document.getElementById('pdfFilePicker');
const loadPdfButton = document.getElementById('loadPdfButton');
const loadUrlPdfButton = document.getElementById('loadUrlPdfButton');let pdfDocument = null; // 存储当前加载的 PDF 文档对象// 函数:渲染单个页面
async function renderPage(pageNum, pdfDocument) {if (!pdfDocument) return; // 确保文档已加载try {const page = await pdfDocument.getPage(pageNum);const scale = 1.5; // 渲染比例,可以根据需要调整const viewport = page.getViewport({ scale: scale });// 创建一个 div 容器用于包裹 canvas 和其他层 (如批注层)const pageDiv = document.createElement('div');pageDiv.className = 'pdfPage';// 设置 pageDiv 的尺寸以匹配渲染后的页面尺寸pageDiv.style.width = `${viewport.width}px`;pageDiv.style.height = `${viewport.height}px`;pageDiv.dataset.pageNumber = pageNum; // 存储页码方便后续查找// 创建 canvas 元素用于渲染 PDF 内容const canvas = document.createElement('canvas');const context = canvas.getContext('2d');canvas.height = viewport.height;canvas.width = viewport.width;pageDiv.appendChild(canvas); // 将 canvas 添加到 pageDiv 中// 创建一个用于自定义批注的层 (将在第三步讨论)const annotationLayer = document.createElement('div');annotationLayer.className = 'annotationLayer';// annotationLayer.style.width = `${viewport.width}px`; // 批注层尺寸通常与 viewport 一致// annotationLayer.style.height = `${viewport.height}px`;// 批注层需要定位在 pageDiv 内部,且覆盖 canvas// 通过 CSS .annotationLayer 设置 absolute position 和 top/left 0 即可pageDiv.appendChild(annotationLayer); // 将批注层添加到 pageDiv 中pdfViewerContainer.appendChild(pageDiv); // 将 pageDiv 添加到主容器// 渲染 PDF 页面内容到 canvasconst renderContext = {canvasContext: context,viewport: viewport,// 如果需要渲染内置的文本层或批注层,可以在这里指定容器// 引入并使用 TextLayerBuilder 和 AnnotationLayerBuilder 会增加代码复杂度// textLayer: textLayerDiv,// annotationLayer: annotationLayerDiv,// annotationMode: pdfjsLib.AnnotationMode.ENABLE_FORMS,};// 执行渲染,这是一个异步操作await page.render(renderContext).promise;console.log(`Page ${pageNum} rendered.`);// TODO: 如果需要渲染内置文本层和批注层,在这里调用其 render 方法// 例如 textLayer.render(); annotationLayer.render();} catch (error) {console.error(`Error rendering page ${pageNum}:`, error);// 可以在页面位置显示一个错误消息}
}// 函数:加载并渲染整个 PDF
async function loadAndRenderPdf(pdfData) {// 清空之前的渲染内容pdfViewerContainer.innerHTML = '';pdfDocument = null; // 清除之前加载的文档对象try {// 加载 PDF 文档,pdfData 可以是 URL 字符串、ArrayBuffer、Blob 等const loadingTask = getDocument(pdfData);pdfDocument = await loadingTask.promise;console.log('PDF loaded:', pdfDocument);const numPages = pdfDocument.numPages;console.log('Number of pages:', numPages);// 循环渲染每一页for (let pageNum = 1; pageNum <= numPages; pageNum++) {// 使用 Promise.resolve() 包裹 renderPage 可以让循环继续,而无需等待每页渲染完成// 这样可以更快地显示第一页Promise.resolve().then(() => renderPage(pageNum, pdfDocument));}// TODO: 如果需要处理 PDF 元数据、大纲、缩略图等,可以在这里访问 pdfDocument 对象} catch (reason) {console.error('Error during PDF loading:', reason);alert(`Failed to load PDF: ${reason.message || reason}`); // 给用户提示}
}// 事件监听器:加载本地文件
loadPdfButton.addEventListener('click', () => {const file = pdfFilePicker.files[0];if (file) {const reader = new FileReader();reader.onload = function(e) {const arrayBuffer = e.target.result;loadAndRenderPdf(arrayBuffer); // 加载 ArrayBuffer};reader.onerror = function(e) {console.error("FileReader error:", e);alert("Error reading file.");}reader.readAsArrayBuffer(file);} else {alert('Please select a PDF file.');}
});// 事件监听器:加载示例 URL
loadUrlPdfButton.addEventListener('click', () => {const samplePdfUrl = 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf'; // 替换为你自己的 PDF URLloadAndRenderPdf(samplePdfUrl); // 加载 URL
});// 注意:你的 HTML 文件需要通过 Web 服务器打开 (http:// 或 https://),
// 直接用浏览器打开本地文件 (file://) 可能因为跨域问题导致 worker 加载失败或 PDF 文件无法加载。

解释上述代码:

  • #pdf-viewer-container:一个容器,用于容纳所有渲染后的 PDF 页面。
  • input[type="file"]button:用于触发本地文件选择和加载示例 URL。
  • pdfDocument: 存储通过 getDocument 加载成功后的 PDF 文档对象。
  • loadAndRenderPdf(pdfData):核心函数,接收 PDF 数据(可以是 URL 或 ArrayBuffer),清空容器,调用 getDocument 加载,然后遍历所有页码,为每一页调用 renderPage
  • renderPage(pageNum, pdfDocument):为指定的页码创建一个 div.pdfPage,内部包含一个 canvas 用于绘制 PDF 内容,以及一个 div.annotationLayer 用于后续的自定义批注。计算 viewport 并设置 canvas 尺寸,然后调用 page.render() 将页面内容绘制到 canvas。
  • 事件监听器:分别为文件选择按钮和加载 URL 按钮添加点击事件,读取文件或指定 URL,然后调用 loadAndRenderPdf

至此,你已经构建了一个基本的 PDF 查看器,可以动态加载本地或远程的 PDF 文件并将其渲染到页面上。

第三步:实现自定义批注功能

重要提示: pdfjs-dist 库的主要功能是渲染 PDF 内容,包括显示 PDF 文件中已有的批注。它不提供添加、编辑或保存新的批注的功能。实现批注(如高亮、下划线、矩形框、文本框等)是一个需要在 PDF 渲染层之上自定义构建的功能。

实现自定义批注功能的整体思路是在每个 PDF 页面渲染出的 canvas 上方,叠加一个透明的 HTML 元素(我们在第二步中创建了 div.annotationLayer),然后在这个叠加层上通过 DOM 操作、SVG 绘制或额外的 Canvas 绘制来表示批注。

以下是实现批注功能的关键步骤和考虑因素:

  1. 批注层管理: 确保每个 PDF 页面都有一个精确覆盖其渲染区域的批注层 (div.annotationLayer)。通过 CSS position: absolute; top: 0; left: 0; width: 100%; height: 100%; 来定位。
  2. 用户交互:
    • 工具选择: 提供 UI 元素(按钮、工具栏)让用户选择要添加的批注类型(例如,高亮、矩形、文本框、直线等)。
    • 事件监听: 在每个页面的 annotationLayer 或一个委托的父容器上监听鼠标事件 (mousedown, mousemove, mouseup) 或触摸事件 (touchstart, touchmove, touchend)。
    • 绘制反馈:mousemove/touchmove 过程中,根据用户选择的工具和鼠标/触摸位置,在批注层上实时绘制一个临时图形(例如,绘制矩形时显示一个虚线框),给用户即时反馈。
  3. 数据模型: 设计一个数据结构来存储每个批注的信息。这些信息至少应该包括:
    • 批注所在的页码
    • 批注的类型(如 ‘highlight’, ‘rectangle’, ‘text’, ‘line’)。
    • 批注在页面上的位置和尺寸信息。这通常需要将屏幕坐标转换为 PDF 页面内部的坐标系统。
    • 批注的样式信息(颜色、线宽、透明度等)。
    • 如果是文本批注,则需要存储文本内容
  4. 坐标转换: 这是实现批注的关键难点之一。鼠标/触摸事件提供的坐标是相对于浏览器视口或页面的像素坐标。你需要将这些像素坐标转换为 PDF 页面内部的坐标(PDF 坐标系统通常以点为单位,原点在左下角)。pdfjs-dist 提供的 page.getViewport(scale).convertToPdfPoint(x, y) 方法可以将视口像素坐标转换为 PDF 坐标,而 page.getViewport(scale).convertToViewportPoint(x, y) 可以将 PDF 坐标转换为视口像素坐标。在处理不同缩放比例时,正确进行坐标转换至关重要。
  5. 批注渲染: 当页面加载、缩放或批注数据更新时,遍历当前页面的批注数据。根据批注类型,在对应的 annotationLayer 中创建并添加相应的 HTML 元素、SVG 元素或在批注层的 Canvas 上绘制图形来显示批注。例如:
    • 高亮:创建 <span><div> 元素,设置背景颜色和位置。
    • 矩形/直线:创建 SVG 元素 (<rect>, <line>) 并设置属性,或者在批注层的 Canvas 上使用 2D Context 绘制。
    • 文本框:创建 <div><textarea>,设置位置和内容。
  6. 批注管理界面: 实现选中批注、显示编辑框、拖动、改变大小、删除等功能。这涉及到监听批注元素的事件,更新批注数据,并重新渲染批注层。
  7. 数据持久化: 实现将批注数据保存到后端服务器或浏览器的本地存储中,以便下次打开同一个 PDF 时可以加载并恢复批注。批注数据通常需要与 PDF 文件本身关联(例如通过 PDF 的哈希值或文件名)。

关于改造官方 viewer.html

pdfjs-dist 源码中的 web/viewer.htmlweb/viewer.js 提供了一个完整的 PDF 查看器实现。虽然你可以借鉴其结构和逻辑(尤其是文本层和内置批注层的渲染方式),但直接修改和嵌入到你的项目会非常复杂。viewer.js 是为一个独立应用设计的,其内部耦合度高,依赖于许多辅助类和资源。从头开始,使用核心库构建你自己的查看器,并逐步添加所需功能(包括批注),通常是更灵活和易于维护的方式。

总结

通过 jsdelivr 引入 pdfjs-dist 的 5.2 版本 ESM 文件,你可以轻松地在现代 Web 应用中集成 PDF 查看功能。使用 <script type="module">import 是 ESM 的标准方式。实现 PDF 的动态加载需要处理文件读取或 URL 请求,并通过 getDocumentrenderPage 函数来完成。

然而,实现自定义的 PDF 批注功能是一个相对独立的任务,它建立在 PDF 渲染之上,需要你自行设计批注的数据模型、用户交互、坐标转换以及批注的渲染和管理逻辑。这部分功能需要投入额外的开发工作,并且可能需要处理复杂的细节,尤其是在保证批注位置准确性和在不同缩放级别下同步更新方面。如果你需要开箱即用的复杂批注功能,可能需要考虑集成商业的 PDF SDK。

希望这篇博文能帮助你理解如何在现代 Web 项目中集成和使用 pdfjs-dist,并为实现动态加载和批注功能提供清晰的思路。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/80248.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于FPGA控制ADC0832双通道采样+电压电流采样+LCD屏幕显示

基于FPGA控制ADC0832双通道采样电压电流采样LCD屏幕显示 前言一、芯片手册阅读1.SPI通信时序 二、仿真分析三、代码分析总结视频演示 前言 定制 要求使用ADC0832芯片进行ADC采样。其中电压采样以及电流采样是固定电路&#xff0c;是硬件设计&#xff0c;跟软件没没关系。本质上…

生产部署方案pm2配合python3脚本

前言 使用python3来处理redis 消息队列&#xff0c;记录下生产部署方案 「生产部署方案」&#xff1a; 多进程&#xff08;动态扩容&#xff09;无限自愈日志自动压缩系统级守护可多队列多worker 终极稳健版&#xff1a;PM2 Logrotate 自动扩容 守护链 适合&#xff1a…

Python全流程开发实战:基于IMAP协议安全下载个人Gmail邮箱内所有PDF附件

文章目录 一、需求分析与安全前置&#xff1a;为什么需要专用工具&#xff1f;1.1 痛点场景1.2 技术方案选择 二、准备工作&#xff1a;Gmail账号安全配置与环境搭建2.1 开启两步验证&#xff08;必做&#xff01;&#xff09;2.2 创建应用专用密码&#xff08;替代普通密码&am…

巧用python之--模仿PLC(PLC模拟器)

工作中用到了VM(VisionMaster4.3)有时候需要和PLC打交道,但是PLC毕竟是别人的,不方便修改别人的程序,这时候需要一个灵活的PLC模拟器是多么好呀! 先说背景: PLC型号 汇川Easy521: Modbus TCP 192.168.1.10:502 在汇川Easy521中Modbus保持寄存器D寄存器 ,在modbus协议中 0-4区…

docker构建镜像并上传dockerhub

docker构建镜像并上传dockerhub 前提条件&#xff1a;需要连接梯子 将梯子配置到虚拟机中&#xff08;确保主机能够连接 hub.docker.com&#xff09; 使用ipconfig 查询主机的 ip4地址虚拟机的连接模式改成桥接模式&#xff08;复制主机的地址网络&#xff09;将ip4配置到虚拟…

python实现的音乐播放器

python实现的音乐播放器 音乐播放器,原来写过一个简陋的例子,可见 https://blog.csdn.net/cnds123/article/details/137874107 那个不能拖动播放进度条上的滑块到新的位置播放。下面介绍的可以拖动播放进度条上的滑块到新的位置播放。 简单实用的音乐播放器 这个简单实用的…

[网安工具] 端口信息收集工具 —— 御剑高速 TCP 全端口扫描工具 · 使用手册

&#x1f31f;想了解其它网安工具&#xff1f;看看这个&#xff1a;[网安工具] 网络安全工具管理 —— 工具仓库 管理手册 https://github.com/NepoloHebo/Yujian-high-speed-TCP-full-port-scannerhttps://github.com/NepoloHebo/Yujian-high-speed-TCP-full-port-scanner 0…

数字孪生赋能智慧城市:从概念到落地的深度实践

在城市规模与复杂度持续攀升的当下&#xff0c;传统管理模式已难以满足现代城市精细化治理需求。数字孪生技术凭借构建虚拟城市镜像、实现实时数据交互与智能决策的特性&#xff0c;成为智慧城市建设的核心引擎。本文将通过多个典型案例&#xff0c;深度解析数字孪生技术如何重…

DeFi开发系统软件开发:技术架构与生态重构

DeFi开发系统软件开发&#xff1a;技术架构与生态重构 ——2025年去中心化金融开发的范式革新与实践指南 一、技术架构演进&#xff1a;从单一链到多链混合引擎 现代DeFi系统开发已从单一公链架构转向“跨链互操作混合模式”&#xff0c;结合中心化效率与去中心化安全双重优势…

相同IP和端口的服务器ssh连接时出现异常

起因 把服务器上的一个虚拟机搞坏了&#xff0c;所以删除重新创建了一个&#xff0c;端口号和IP与之前的虚拟机相同。 ssh usernameIP -p port 时报错 WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone…

验证es启动成功

1. 查看命令行输出信息 在启动 Elasticsearch 时&#xff0c;命令行窗口会输出一系列日志信息。若启动成功&#xff0c;日志里通常会有类似下面的信息&#xff1a; plaintext [2025-05-06T13:20:00,000][INFO ][o.e.n.Node ] [node_name] started其中 [node_na…

CentOS网络之network和NetworkManager深度解析

文章目录 CentOS网络之network和NetworkManager深度解析1. CentOS网络服务发展历史1.1 传统network阶段&#xff08;CentOS 5-6&#xff09;1.2 过渡期&#xff08;CentOS 7&#xff09;1.3 新时代&#xff08;CentOS 8&#xff09; 2. network和NetworkManager的核心区别3. ne…

Unity:父挂 Rigidbody2D、子挂 Collider2D 时触发器不生效的问题分析

目录 ❓问题现象 &#x1f50d; 排查与定位 ⚠️ Unity 触发机制的核心要求 ✅ 为什么把 Collider2D 移到父物体后就能触发&#xff1f; &#x1f4a1; 解决方案 在 Unity 2D 游戏开发中&#xff0c;很多人习惯用父物体挂载 Rigidbody2D&#xff0c;而将不同的身体部位&am…

Google AI版图:解析AI Studio, Gemini, NotebookLM与GCP

1. 2C vs 2B: AI Studio: 主要是面向开发者&#xff0c;提供一个易用的界面来探索和构建基于Google模型的应用。虽然最终的应用可能服务于C端或B端&#xff0c;但AI Studio本身更多是一个开发者的工具平台&#xff0c;可以看作是连接模型能力和各种应用的桥梁。它可以被个人开…

Oracle EBS AP发票被预付款核算创建会计科目时间超长

背景 由于客户职能部门的水电、通信和物业等等费用统一管理或对接部门报销费,在报销费的时候,用户把所有费用分摊到各个末级部门,形成AP发票行有上千行, 问题症状 1、用户过账时,请求创建会计科目一直执行20多个小时未完成,只能手工强行取消请求。 2、取消请求以后,从后…

MySQL中MVCC指什么?

简要回答&#xff1a; MVCC&#xff08;multi version concurrency control&#xff09;即多版本并发控制&#xff0c;为了确保多线程下数据的安全&#xff0c;可以通过undo log和ReadView来实现不同的事务隔离级别。 对于已提交读和可重复读隔离级别的事务来说&#xff0c;M…

赛季7靶场 -- Checker --User flag

本系列仅说明靶场的攻击思路&#xff0c;不会给出任何的详细代码执行步骤&#xff0c;因为个人觉得找到合适的工具以实现攻击思路的能力也非常重要。root要逆向&#xff0c;没做了&#xff0c;但是user flag也有借鉴意义&#xff0c;关于2FA的绕过我们有必要了解 1.首先Nmap扫描…

【RAG技术全景解读】从原理到工业级应用实践

目录 &#x1f31f; 前言&#x1f3d7;️ 技术背景与价值&#x1f6a8; 当前技术痛点&#x1f6e0;️ 解决方案概述&#x1f465; 目标读者说明 &#x1f50d; 一、技术原理剖析&#x1f4d0; 核心概念图解&#x1f4a1; 核心作用讲解⚙️ 关键技术模块说明⚖️ 技术选型对比 &…

【嵌入式开发-RS-485】

嵌入式开发-RS-485 ■ RS-485 连接方式■ RS-485 半双工通讯■ RS-485 的特点■ UART硬流控■ RS-4851. 全双工、半双工接线2. 拓扑结构3. RS-485收发器3.1 发送模式&#xff08;TX&#xff09;3.2 接收模式&#xff08;RX&#xff09; 4. RS-485数据链路5. RS-485常用电路6. C…

[硬件电路-18]:MCU - LPC1765FBD100是恩智浦(NXP)半导体推出的一款基于ARM Cortex-M3内核的高性能32位微控制器

LPC1765FBD100是恩智浦&#xff08;NXP&#xff09;半导体推出的一款基于ARM Cortex-M3内核的高性能32位微控制器&#xff0c;具备高集成度、低功耗、丰富的外设接口和强大的处理能力&#xff0c;适用于工业控制、消费电子、医疗设备、通信系统等嵌入式应用场景。 以下从核心特…