从零自制红外遥控器:深入解析IR通信原理与Arduino实践

发布时间:2026/10/7 6:17:47

从零自制红外遥控器:深入解析IR通信原理与Arduino实践 1. 项目概述从“万能遥控”到红外通信的底层探索几年前我家里各种电器的遥控器堆满了茶几电视、空调、机顶盒、风扇……找起来麻烦不说有些老设备的遥控器坏了配一个原装的又贵又难找。当时市面上已经有不少“万能遥控器”或者手机红外遥控APP但用起来总觉得隔了一层要么是支持的设备库不全要么是操作逻辑不符合我的习惯。这让我萌生了一个想法能不能自己动手做一个完全听我指挥的、可以学习并自定义任何红外指令的遥控器这就是“Simple IR Remote Project”的起点。这个项目的核心远不止是复制一个遥控器。它是一次对红外IR通信这个古老但无处不在的技术进行从原理到应用的全方位拆解。红外遥控技术自上世纪七八十年代普及以来几乎成了所有消费电子产品的标配其原理简单、成本低廉、抗干扰能力尚可。通过这个项目你不仅能获得一个功能强大的自定义遥控终端更能深入理解红外信号的编码、发射与接收机制掌握用微控制器“对话”家电的底层技能。无论你是电子爱好者想入门嵌入式开发还是智能家居玩家想实现更灵活的自动化控制这个项目都是一个绝佳的实践切入点。简单来说这个项目就是利用一块常见的开发板如Arduino、ESP8266等搭配一个红外发射管和一个接收头构建一个可以学习、存储和发射红外信号的自制系统。你可以用它来统一控制所有支持红外的设备甚至可以编程实现复杂的联动场景比如“一键观影模式”关灯、开电视、开音响、调低空调温度。接下来我将从设计思路、硬件解析、代码实现到调试心得完整地分享这个项目的构建过程。2. 项目整体设计与核心思路拆解2.1 为什么选择“自研”而非成品方案市面上成熟的智能红外遥控产品很多比如Broadlink RM系列、小米万能遥控器等。选择自己动手主要基于以下几点考量第一极致的学习与自定义能力。成品方案通常依赖于云端数据库或内置的固定码库。当你面对一个非常小众的品牌、一个老旧的设备或者一个经过改装的电器时云端可能没有数据内置码库也可能失效。自研方案的核心是“学习”功能用红外接收头直接录制原始设备发出的红外波形将其转化为一串数字编码存储起来。这意味着理论上任何能发出红外信号的遥控器都能被你的设备完美复制没有任何品牌和型号的限制。第二深度集成与可编程性。自研的遥控核心本质上是一个可编程的微控制器。你不仅可以存储和发射单个按键的指令更能编写逻辑代码。例如你可以设计一个“宏命令”按下一个物理按键程序自动依次发送“电视开机”、“切换至HDMI 2”、“音响开机”三条红外指令并加入适当的延时以确保设备响应。这种复杂的联动逻辑是许多成品遥控器难以实现的。第三硬件成本与知识收获。一套自研红外遥控的核心硬件成本可能不到50元但其带来的学习价值远超这个数字。你会亲手触摸到红外载波、调制解调、信号编码如NEC、RC5、SIRC协议等通信基础概念这些知识是理解更复杂无线通信如Wi-Fi、蓝牙的基石。2.2 系统架构与工作流程整个项目的系统架构可以清晰地分为三个部分输入、处理核心、输出。输入部分负责信号的捕获。这包括物理按键用于触发发射指令或进入学习模式。红外接收头如VS1838B、HS0038B这是一个集成了光电二极管、前置放大器、解调电路的三引脚器件。它的关键作用是将接收到的、被38kHz载波调制的红外光脉冲解调还原成原始的数字信号波形高/低电平并输出给微控制器。它只能接收不能发射。处理核心是一块微控制器开发板如Arduino Uno。它是项目的大脑负责监听按键动作。在“学习模式”下读取红外接收头传来的波形测量每个高电平和低电平的持续时间通常以微秒为单位并将这一系列时间数据编码成特定格式如RAW码存储起来。在“发射模式”下根据存储的编码数据重新生成对应的波形信号通过特定的引脚输出。输出部分即红外发射管通常是一个940nm波长的LED及其驱动电路。由于红外发射管需要较大的电流可达100mA才能有足够的发射距离而微控制器GPIO引脚只能提供约20mA电流因此必须使用三极管如NPN型8050或MOSFET来构建一个简单的开关电路进行电流放大驱动发射管工作。整个工作流程就像一个“录音与回放”系统学习时用原装遥控器对准接收头按下按键微控制器录下波形发射时微控制器将存储的波形通过驱动电路驱动红外发射管复现出完全一样的红外信号。3. 核心硬件解析与电路搭建要点3.1 关键元器件选型与原理1. 微控制器MCU选型Arduino Uno/Nano推荐入门基于ATmega328P资源足够社区支持极好有现成的红外库如IRremote可用极大降低了开发难度。ESP8266如NodeMCU或ESP32进阶选择如果你希望遥控器具备Wi-Fi功能实现手机APP控制或接入智能家居平台如Home Assistant那么ESP系列是更好的选择。它们性能更强但开发环境搭建稍复杂。2. 红外接收头IR Receiver型号VS1838B、HS0038B、TSOP38238 是最常见的型号末尾的“38”代表其解调频率为38kHz这是红外遥控最常用的载波频率。引脚通常三个引脚分别是VCC接3.3V或5V、GND、OUT信号输出。输出信号是反向的即当接收到38kHz红外信号时输出低电平无信号时输出高电平。这一点在编程解析时需要特别注意。方向性接收头有一定的指向性正面接收效果最好摆放时需注意。3. 红外发射管IR LED波长940nm是最通用的与绝大多数家电遥控器兼容。驱动绝对不能直接接到MCU引脚必须使用驱动电路。一个典型的NPN三极管驱动电路如下MCU引脚串联一个220Ω限流电阻连接到三极管基极红外发射管阳极串联一个100Ω左右的限流电阻接到电源5V阴极连接到三极管集电极三极管发射极接地。当MCU引脚输出高电平时三极管导通电流从5V经红外管、三极管到地红外管发光。4. 按键与存储按键轻触开关即可用于模式切换和指令触发。存储如果需要断电保存学习到的红外码ATmega328P自带的EEPROM1KB空间有限通常只够存几十个RAW格式的码。对于大量存储或ESP系列可以考虑使用外置的EEPROM芯片如24Cxx系列或利用ESP的Flash模拟存储。3.2 电路连接实战与注意事项以下是一个基于Arduino Uno的经典连接示意图文字描述Arduino Uno引脚连接 - 数字引脚 D2: 接红外接收头的OUT脚。 - 数字引脚 D3: 接三极管驱动电路的基极电阻用于控制发射管。 - 数字引脚 D11, D12, D13: 分别接三个轻触开关一端开关另一端接地。引脚内部启用上拉电阻。 - 5V: 接红外接收头VCC、红外发射管驱动电路电源。 - GND: 接红外接收头GND、三极管发射极、所有开关的公共地。 驱动电路连接 - Arduino D3 - 220Ω电阻 - NPN三极管如8050基极。 - 三极管发射极 - GND。 - 5V - 100Ω电阻 - 红外发射管阳极。 - 红外发射管阴极 - 三极管集电极。注意这是最基本的电路。在实际制作中为了稳定性和发射距离可以在红外发射管两端并联一个反向的1N4148二极管用于吸收关断时产生的反向感应电动势保护三极管。也可以在电源入口处加一个100uF的电解电容进行滤波。布局与焊接心得电源去耦在Arduino的5V和GND引脚附近焊接一个0.1uF的瓷片电容可以有效滤除高频噪声避免红外接收头误触发。发射管朝向将红外发射管与接收头并排放置但方向最好错开一定角度或者用隔板稍微遮挡防止发射时信号过强直接串扰到接收头导致系统自激。驱动电流测试通过测量发射管串联电阻两端的电压可以计算实际电流。例如电阻为100Ω测得电压为0.5V则电流 I V/R 0.5/100 0.005A 5mA。这个电流可能偏小导致距离短。可以尝试减小电阻如到47Ω但务必确保总电流在发射管和三极管的允许范围内查数据手册。4. 软件逻辑与代码实现详解我们将使用Arduino平台和著名的IRremote库来简化开发。这个库封装了红外信号的接收、解码和发射的复杂时序操作。4.1 开发环境准备与库安装首先确保你安装了Arduino IDE。然后通过“工具” - “管理库...”搜索“IRremote”选择由“Arduino-IRremote”或“shirriff”维护的版本进行安装。这个库同时支持接收和发射功能。4.2 核心代码结构解析下面是一个集成了学习与发射功能的简化版代码框架并附有详细注释。#include IRremote.h // 引入红外库 // 引脚定义 const int RECV_PIN 2; // 红外接收头接D2 const int LED_PIN 3; // 红外发射管控制接D3 const int LEARN_BTN 11; // 学习模式按键 const int SEND_BTN1 12; // 发射按键1 const int SEND_BTN2 13; // 发射按键2 // 状态变量 bool learnMode false; // 是否处于学习模式 unsigned int rawCodes[RAWBUF]; // 存储RAW码的数组RAWBUF需足够大 int codeLen 0; // 存储的码长度 int khz 38; // 载波频率通常为38kHz // 初始化红外接收和发射对象 IRrecv irrecv(RECV_PIN); IRsend irsend(LED_PIN); // 对于IRremote库v3.x发射对象可能不同 void setup() { Serial.begin(115200); pinMode(LEARN_BTN, INPUT_PULLUP); pinMode(SEND_BTN1, INPUT_PULLUP); pinMode(SEND_BTN2, INPUT_PULLUP); irrecv.enableIRIn(); // 启动红外接收 Serial.println(Simple IR Remote Ready.); } void loop() { // 1. 检查模式切换按键学习键 if (digitalRead(LEARN_BTN) LOW) { delay(50); // 简单消抖 if (digitalRead(LEARN_BTN) LOW) { learnMode !learnMode; // 切换模式 Serial.println(learnMode ? Enter Learn Mode. Point remote and press key. : Exit Learn Mode.); while(digitalRead(LEARN_BTN) LOW); // 等待按键释放 delay(200); } } // 2. 学习模式接收并存储红外码 if (learnMode) { decode_results results; if (irrecv.decode(results)) { // 尝试解码 // 对于未知协议我们可以存储RAW数据 if (results.decode_type UNKNOWN) { Serial.println(Received unknown code, saving as RAW...); codeLen results.rawlen - 1; // 注意rawbuf[0]是第一个间隙长度通常从rawbuf[1]开始复制 for (int i 1; i codeLen; i) { // 存储的时间单位是微秒但rawbuf里存储的是50微秒的倍数取决于库版本 // 这里需要根据库的实际定义调整以下为示意 rawCodes[i-1] results.rawbuf[i] * USECPERTICK; // 转换为微秒 } Serial.print(Code length saved: ); Serial.println(codeLen); // 这里可以添加将rawCodes和codeLen保存到EEPROM的代码 } else { // 如果是已知协议如NEC可以打印出标准编码值更易于存储 Serial.print(Received known code: 0x); Serial.println(results.value, HEX); } irrecv.resume(); // 准备接收下一个信号 } } else { // 3. 发射模式检查发射按键 if (digitalRead(SEND_BTN1) LOW) { sendIRCode(1); // 发送存储的代码1 } if (digitalRead(SEND_BTN2) LOW) { sendIRCode(2); // 发送存储的代码2 } } } // 发送红外码的函数 void sendIRCode(int codeIndex) { Serial.print(Sending Code ); Serial.println(codeIndex); // 在实际项目中这里需要根据codeIndex从EEPROM中读取对应的编码数据和长度 // 此处假设使用上面学习到的rawCodes和codeLen if (codeLen 0) { // 注意irsend.sendRaw的参数要求第一个是起始的高电平时间 // 而rawCodes存储的格式可能与库要求匹配可能需要调整数组顺序 irsend.sendRaw(rawCodes, codeLen, khz); delay(100); // 发送间隔 irrecv.enableIRIn(); // 发送完成后重新使能接收某些版本库需要 } else { Serial.println(No code learned yet.); } }代码关键点解析协议处理IRremote库能自动识别NEC、Sony、RC5等多种常见协议。对于已知协议直接存储results.value这个十六进制值最为简洁。对于未知或库不支持的协议必须使用RAW码方式存储一系列的时间间隔数据。RAW码存储RAW码数据量很大一个按键信号可能包含几百个时间点。存储时要注意库的存储格式USECPERTICK定义了每个单位对应的微秒数。直接存储results.rawbuf并不可靠因为其内容与库版本和配置相关需要按文档处理。发送RAW码irsend.sendRaw()函数要求传入的数组其数据格式是“高电平时间低电平时间高电平时间低电平时间...”。确保你存储的rawCodes数组符合这个格式。消抖与状态管理按键检测使用了简单的延时消抖。在更复杂的项目中可以使用状态机或millis()进行非阻塞式消抖。learnMode状态变量用于清晰地分隔学习与发射两种逻辑。4.3 功能扩展EEPROM存储与协议解码优化上面的示例代码将编码存储在易失性内存中断电即丢失。要实现永久存储需要使用EEPROM。Arduino内部EEPROM存储示例#include EEPROM.h #define EEPROM_ADDR_START 0 void saveToEEPROM(unsigned int* data, int len, int baseAddr) { EEPROM.put(baseAddr, len); // 先存储长度 for (int i 0; i len; i) { EEPROM.put(baseAddr sizeof(int) i * sizeof(unsigned int), data[i]); } } void readFromEEPROM(unsigned int* data, int len, int baseAddr) { EEPROM.get(baseAddr, len); for (int i 0; i len; i) { EEPROM.get(baseAddr sizeof(int) i * sizeof(unsigned int), data[i]); } }注意EEPROM有写入寿命限制约10万次应避免在循环中频繁写入。通常只在学习成功时写入一次。协议解码优化对于已知协议存储协议类型和键值比存储RAW码更节省空间发射也更可靠。可以在学习时判断results.decode_type如果是NEC、SONY等则存储类型和值。发射时根据类型调用对应的发送函数如irsend.sendNEC(value, len)。5. 调试、问题排查与性能优化实录即使电路和代码都正确在实际调试中仍会遇到各种问题。下面是我在多次实践中总结的常见问题与解决方法。5.1 信号学习失败或不稳定现象按下学习键后用原装遥控器对准发射串口监视器没有输出或输出乱码。排查步骤检查供电首先用万用表测量红外接收头的VCC引脚电压是否稳定5V或3.3V。电压不足或波动大会导致接收头工作异常。检查连接与引脚确认接收头的OUT脚是否接在了代码中定义的RECV_PIN如D2上并且代码中irrecv对象初始化也使用了这个引脚。测试接收头运行一个最简单的接收测试程序如IRremote库的示例程序IRrecvDump。将已知良好的遥控器如电视遥控对准接收头按下看串口是否有稳定输出。如果没有可能是接收头损坏或型号不匹配比如用了56kHz的接收头接收38kHz信号。环境光干扰强烈的日光灯、太阳光可能包含红外成分干扰接收。尝试在较暗的环境下测试或者给接收头加一个不透光的黑色热缩管或橡胶套。距离与角度学习时原装遥控器的发射头应对准接收头距离在10-30厘米为宜太近可能饱和太远信号弱。5.2 信号发射无效或距离太短现象按下发射键目标设备无反应或者需要将自制遥控器贴到设备接收窗上才有反应。排查步骤驱动电路检查这是最常见的问题。用万用表测量发射管两端在发射时的电压。当三极管导通时发射管阴极接集电极电压应接近0V阳极电压应比电源电压略低因为有电阻分压。如果阴极电压很高说明三极管没有饱和导通检查基极电阻是否太大或MCU引脚输出高电平电压是否足够。发射管电流计算或测量流过发射管的电流。断开发射管阳极串联电阻的一端串联万用表电流档。发射时电流应在50-100mA范围内。如果电流太小减小限流电阻阻值如果电流过大则增加阻值防止烧坏发射管。发射管性能红外发射管有发射角度中心轴方向强度最强。确保发射管正对目标设备。可以尝试并联2-3个发射管以增强信号但需要相应增大驱动电流能力可能需要换用更大功率的三极管或MOSFET。载波频率确认发射代码中设置的khz值通常为38与接收头解调频率一致。某些设备可能使用40kHz或其他频率。信号验证最有效的调试方法是“自我验证”。用另一个红外接收头或同一个项目板但需暂时断开其发射电路防止干扰连接到一个独立的Arduino上运行接收程序。用你的自制发射器对准这个测试接收头发射看能否正确接收到你学习的编码。这样可以隔离发射问题。5.3 存储的编码发射几次后失效现象学习到的编码第一次发射可能成功但连续快速发射几次后设备不再响应。原因与解决协议重复码许多红外协议如NEC有“重复码”机制。当按住遥控器按键不放时发射的不是完整编码而是一个特殊的短“重复码”。如果你的学习程序只学习了完整编码而发射程序在循环发送时没有模拟这种重复码机制设备在接收第二次信号时可能将其识别为无效指令。解决方案是在学习时也尝试长按按键看看库是否解码出REPEAT类型在发射时如果需要连续发射应模拟“一次完整码多次重复码”的序列。发射间隔与设备响应有些设备处理红外指令需要时间。连续发送两条指令间隔太短可能导致后一条被忽略。在发送代码后增加一个delay(200)以上的间隔。EEPROM读取错误如果编码存储在EEPROM中确保读取函数正确并且每次读取的地址没有错乱。可以在每次上电后将读取到的编码长度和头几个数据打印到串口进行比对。5.4 进阶优化技巧提升发射距离使用透镜在红外发射管前加一个小型的凸透镜可以将散射的光线汇聚成束显著增加指向性距离。优化驱动采用MOSFET如IRF520代替三极管其导通内阻更小可以在相同电压下提供更大的瞬间电流。脉冲电流驱动红外发射管可以承受比连续工作电流大得多的脉冲电流。在发射的几十毫秒内可以短暂提高驱动电压或减小限流电阻以产生更强的光脉冲。但需严格控制脉冲宽度防止过热损坏。实现多设备与场景联动为每个物理按键分配一个“场景ID”。在代码中一个场景ID对应一组有序的红外指令序列包括指令码和间隔延时。使用ESP8266/ESP32通过Wi-Fi接入网络。利用MQTT协议接收来自手机APP或智能家居服务器的指令触发对应的红外场景。这样你就可以用语音助手如小爱同学、天猫精灵来控制传统红外家电了。低功耗设计适用于电池供电主控MCU大部分时间进入深度睡眠模式。按键通过外部中断唤醒MCU。红外接收头通常耗电在0.5-1mA如果不需要常开学习功能可以通过一个MOSFET开关电路为其供电仅在需要学习时上电。这个“Simple IR Remote Project”从看似简单的需求出发深入到了硬件电路设计、微控制器编程、通信协议解析和系统调试等多个层面。它完美地诠释了“麻雀虽小五脏俱全”的嵌入式项目特点。当你亲手制作的红外遥控器成功控制客厅电视的那一刻所获得的成就感远非购买一个成品可比。更重要的是通过这个过程积累的调试经验和对底层通信的理解将成为你应对更复杂电子项目的宝贵财富。你可以在此基础上增加显示屏来显示指令名称添加旋转编码器来浏览菜单甚至打造一个完全由你自己定义交互逻辑的智能家居控制面板。
延伸阅读

更多相关文章

2026/10/1 8:16:03

从零DIY生态水族缸:超白玻璃选材、结构计算与粘缸实战指南

1. 从零开始:为什么选择自己动手造一个“缸”“DIY tank”这个标题,听起来有点硬核,甚至带点军事或工业的意味。但别急着划走,这里的“tank”其实可以非常多元。它可能是一个水族爱好者梦寐以求的生态水族缸,一个极客用…

2026/10/7 6:16:53

基于Neopixel的巨型七段数码管DIY:从电路设计到编程映射全解析

1. 项目缘起:从“小方块”到“大像素”的执念几年前,我在一个创客展上看到一个用LED灯带做的巨型数字时钟,每个数字差不多有半人高,在昏暗的展位上显得格外酷炫。当时我就想,这玩意儿原理应该不复杂,不就是…

2026/10/2 13:32:02

基于Wio Terminal的USB HMI设计:为嵌入式Linux打造高效图形外设

1. 项目缘起:为什么需要一块USB HMI? 在嵌入式开发的世界里,我们常常面临一个尴尬的局面:手头的核心板(比如BeagleBoard这类功能强大的单板计算机)性能强劲,但“面子”上却有点寒酸。它可能没有…

2026/10/7 6:15:21

AI Agent工程化落地:架构、选型与实战指南

做AI应用方向这几年,我有个挺深的感受:行业最热闹的时候,不是某个模型发布的那天,而是大量开发者开始讨论“怎么把它真正用起来”的那天。2026年9月22日这天的热搜关键词里,“AI Agent”和“AI应用开发”同时挂在头部&…

2026/10/7 6:15:21

SSM图书管理系统实战:分层架构、事务控制与SQL优化

简介:这是一套面向计算机专业本科生及Java初学者的毕业设计级实战项目资源,聚焦图书管理业务场景,基于SSM(SpringSpringMVCMyBatis)主流框架完整实现前后端功能,解决课程设计、期末大作业与毕业设计中系统开…

2026/10/7 6:15:21

Servlet+JSP学生选课系统:从部署到改造的JavaWeb项目实战

简介:这是一套基于JavaWeb技术栈实现的学生选课系统,面向计算机相关专业毕业设计学生及需要项目实战的Java学习者,可解决课程管理、选课、成绩录入等常见业务场景的完整开发需求。系统采用Servlet与JSP及MySQL架构,前端结合Bootst…

2026/10/7 6:15:21

LSTM时间序列预测实战:实例代码解析与避坑指南

简介:面向机器学习初学者与时间序列预测开发者的LSTM入门实例,以房地产价格预测为场景,完整演示长短期记忆网络从数据预处理到权重更新的实现过程。LSTM通过输入门、遗忘门、输出门与细胞状态协同解决传统RNN的梯度消失问题,该实例…

2026/10/7 6:15:21

上下文工程与Agent Harness:AI编码代理10x效率实践指南

这两年AI编码代理的讨论热度一直在涨,但绝大多数人的用法还停留在“开个对话窗口、把报错贴进去”的阶段。真正拉开差距的,其实不是模型选谁、参数多大,而是两件常常被忽略的事:Context Engineering(上下文工程&#x…

2026/10/7 6:10:21

AI编程工作流实战:三个可立刻复用的高效开发流程

1. 为什么“能立刻复用”比“功能强大”更重要我见过太多人收藏了几百个AI编程工具,从代码补全到Agent框架,硬盘里塞满了各种教程和配置,但真正每天在用的工作流,掰着手指头数不超过三个。问题出在哪?不是工具不够好&a…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/6 4:01:51

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/6 17:46:51

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

ESP32免重刷固件:浏览器直接修改NVS键值实现WiFi配置更新

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

SAP HANA查询结果导出CSV:避开乱码、性能与权限的实用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

数字后端Placement阶段Density与Congestion控制实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑