RRT路径规划算法从原理到实战:Python实现与工程优化指南
简介本资源面向机器人路径规划方向的学习者与开发者提供一套基于RRT快速扩展随机树算法的C实现方案并结合MFC框架搭建可视化交互界面帮助理解随机采样、最近邻搜索与路径收敛等核心机制。压缩包共49个文件约32.78MB以cpp、h源码文件承载算法与视图逻辑配合sln、vcxproj等工程文件可直接编译运行另有exe可执行程序、txt数据文件及日志、图标等辅助资源目录结构完整。已有689人学习下载。资源涵盖节点管理、障碍物与参考路径绘制、随机数生成、插值计算及图形实时更新等模块读者可借此观察路径逐步优化并避开障碍物的全过程既能加深对RRT算法原理的理解也能提升C与MFC图形界面编程的实战能力适合作为课程设计或自学参考。1. 从一份压缩包说起RRT 路径规划到底能解决什么很多人第一次拿到「基于rrt算法的路径规划.rar」这类压缩包心里其实是打鼓的里面大概率是一份课程设计或者某个仿真 Demo代码能跑但换个地图就翻车。我当年也是这么过来的。RRTRapidly-exploring Random Tree快速扩展随机树本质上是一种采样式路径规划算法它不靠栅格穷举而是靠在地图里随机撒点、朝目标方向生长一棵树最终找到一条从起点到终点的可行路径。它最擅长的是高维空间和复杂障碍物场景比如机械臂关节空间规划、无人机三维避障、自动驾驶的局部路径生成。如果你手头正好有这么一个压缩包或者你正准备自己写一个这篇文章会带你从原理到代码、从参数到踩坑完整走一遍。适合刚接触路径规划的学生、需要快速搭原型的工程师以及被 A* 在高维场景下内存爆炸折磨过的从业者。2. RRT 的核心机制与最小可跑通实现2.1 为什么选 RRT 而不是 A* 或 DijkstraA* 和 Dijkstra 是图搜索算法它们要求先把连续空间离散成栅格或路网。在二维地图上这没问题但一旦维度升到六轴机械臂的关节空间栅格数量会指数级膨胀内存和计算时间直接爆炸。RRT 的聪明之处在于它不做全局离散而是用随机采样去“试探”空间。它的概率完备性意味着只要路径存在采样次数足够多它就能找到。代价是路径通常不是最优的拐弯多、不够平滑。所以常见做法是RRT 负责快速找到可行解后续再用 shortcut 或优化算法去拉直。我一般会在项目里把 RRT 当作“第一响应者”先保证有路可走再谈好不好看。2.2 一棵树是怎么长出来的采样、最近邻、扩展、碰撞检测RRT 的每一次迭代只做四件事。第一在地图范围内随机采一个点q_rand。第二在当前树的所有节点里找到离q_rand最近的节点q_near。第三从q_near朝q_rand方向走一小步step_size得到新节点q_new。第四检查q_near到q_new这条线段是否穿过障碍物如果没穿过就把q_new加入树并记录父节点。重复这个过程直到某个新节点离目标点足够近或者直接连到目标点。这里的关键参数是step_size太小则树长得慢太大则容易撞障碍物导致扩展失败。常见取值是地图对角线长度的 1% 到 5%具体要看障碍物密度。2.3 用 Python 写一个 50 行以内的 RRT 最小原型下面这段代码不依赖任何第三方规划库只用 numpy 和 matplotlib方便你直接复制到本地跑通。地图用一个 100x100 的二维区域障碍物用矩形表示。import numpy as np import matplotlib.pyplot as plt # 地图尺寸和障碍物列表每个障碍物是 (xmin, ymin, xmax, ymax) MAP_W, MAP_H 100, 100 OBSTACLES [(20, 20, 40, 60), (60, 30, 80, 70), (45, 70, 55, 90)] START (10, 10) GOAL (90, 90) STEP_SIZE 5.0 MAX_ITER 5000 GOAL_BIAS 0.1 # 有 10% 的概率直接采样目标点加速收敛 class Node: def __init__(self, pos, parentNone): self.pos np.array(pos, dtypefloat) self.parent parent def collision_free(p1, p2): 检查线段 p1-p2 是否与任何障碍物相交 for (xmin, ymin, xmax, ymax) in OBSTACLES: # 简单采样法在线段上取若干点判断是否在障碍物内 for t in np.linspace(0, 1, 20): pt p1 t * (p2 - p1) if xmin pt[0] xmax and ymin pt[1] ymax: return False return True def nearest_node(tree, q_rand): 找到树中离 q_rand 最近的节点 dists [np.linalg.norm(node.pos - q_rand) for node in tree] return tree[int(np.argmin(dists))] def steer(q_near, q_rand, step_size): 从 q_near 朝 q_rand 方向走一步 direction q_rand - q_near.pos length np.linalg.norm(direction) if length step_size: return q_rand return q_near.pos (direction / length) * step_size def rrt_plan(): tree [Node(START)] for i in range(MAX_ITER): # 目标偏置有一定概率直接采样目标点 if np.random.rand() GOAL_BIAS: q_rand np.array(GOAL, dtypefloat) else: q_rand np.array([np.random.uniform(0, MAP_W), np.random.uniform(0, MAP_H)]) q_near nearest_node(tree, q_rand) q_new_pos steer(q_near, q_rand, STEP_SIZE) if collision_free(q_near.pos, q_new_pos): q_new Node(q_new_pos, parentq_near) tree.append(q_new) # 如果新节点离目标足够近尝试直接连到目标 if np.linalg.norm(q_new.pos - np.array(GOAL)) STEP_SIZE: if collision_free(q_new.pos, np.array(GOAL)): tree.append(Node(GOAL, parentq_new)) return tree return tree def extract_path(tree): 从最后一个节点回溯到根节点 path [] node tree[-1] while node is not None: path.append(node.pos) node node.parent return path[::-1] if __name__ __main__: tree rrt_plan() path extract_path(tree) # 画图 fig, ax plt.subplots(figsize(6, 6)) for (xmin, ymin, xmax, ymax) in OBSTACLES: ax.add_patch(plt.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, colorgray, alpha0.6)) for node in tree: if node.parent is not None: ax.plot([node.pos[0], node.parent.pos[0]], [node.pos[1], node.parent.pos[1]], c-, lw0.5) ax.plot([p[0] for p in path], [p[1] for p in path], r-, lw2) ax.plot(START[0], START[1], go, markersize10) ax.plot(GOAL[0], GOAL[1], bo, markersize10) ax.set_xlim(0, MAP_W) ax.set_ylim(0, MAP_H) ax.set_aspect(equal) plt.show()这段代码的逻辑很直白rrt_plan是主循环每次迭代先决定采样点然后找最近邻、扩展、碰撞检测、加节点。GOAL_BIAS这个参数很关键它让算法有概率直接朝目标采样而不是纯随机能显著减少迭代次数。STEP_SIZE控制树的生长粒度我一般设成地图宽度的 3% 到 5%。collision_free用的是线段采样法简单但够用如果障碍物形状复杂可以换成解析几何求交。跑完之后extract_path从最后一个节点回溯得到从起点到终点的节点序列。注意这个原型没有做路径平滑所以看起来会有点锯齿这是正常的。2.4 参数怎么调step_size、goal_bias、max_iter 的取值经验step_size是最影响成败的参数。太小比如 1.0树长得极慢5000 次迭代可能还没摸到目标太大比如 20.0每次扩展都容易撞障碍物导致大量失败尝试。我的经验是先看地图里最窄通道的宽度step_size不要超过这个宽度的一半。goal_bias通常设在 0.05 到 0.2 之间障碍物越密集这个值可以稍微调高但太高会让树过早朝目标方向生长反而在复杂障碍物前卡住。max_iter是保险丝设成 5000 到 20000 都合理但如果你的算法在 10000 次内还找不到路大概率是step_size或地图设置有问题别硬等。3. 从 Demo 到可用工程化改造与性能优化3.1 把碰撞检测从采样法换成解析法上面代码里的collision_free用的是在线段上取 20 个点判断这在障碍物少的时候没问题但当地图里有几十个障碍物、每次迭代都要检查时性能会明显下降。更稳的做法是解析法把线段和障碍物矩形求交用参数方程判断是否有交点。对于矩形障碍物可以分别检查线段与四条边的交点再判断交点是否在线段和边的范围内。这样一次计算就能得出确定结果没有采样漏检的风险。如果障碍物是圆形就解线段到圆心的距离方程。工程里我一般会写一个GeometryChecker类把不同形状的碰撞检测封装起来主循环只调接口。3.2 用 KD-Tree 加速最近邻搜索nearest_node现在是线性遍历整棵树复杂度 O(n)。当树有几千个节点时每次迭代都要算几千次距离累积起来很可观。常见做法是引入 KD-Tree 或 Ball Tree把最近邻查询降到 O(log n)。Python 里可以用scipy.spatial.cKDTree每次加入新节点后重建或者增量更新。如果不想引入 scipy也可以自己写一个简单的网格哈希把地图划分成固定大小的格子每个格子记录落在里面的节点查询时只检查附近格子。我在一个 5000 节点的场景里对比过线性搜索平均每次迭代 0.8msKD-Tree 降到 0.05ms整体规划时间从 4 秒降到 0.3 秒。3.3 路径平滑shortcut 与 B 样条拟合RRT 原始路径是折线拐点尖锐实际机器人或车辆无法直接跟踪。最常用的后处理是 shortcut随机选路径上两个不相邻的点如果它们之间的直线不撞障碍物就把中间那段替换成直线。重复几十次路径会明显变短变直。另一种是 B 样条拟合把路径点当作控制点生成光滑曲线。shortcut 实现简单适合快速出效果B 样条更平滑但需要调参数而且可能偏离原始可行路径需要额外做碰撞校验。我一般先用 shortcut 拉直再用三次 B 样条做微调最后再跑一次碰撞检测确认安全。3.4 在压缩包项目里加一个可视化调试面板如果你拿到的压缩包只有算法代码没有可视化调试会很痛苦。建议加一个 matplotlib 的交互面板把树、路径、障碍物、起点终点都画出来并且支持暂停和单步。具体做法是在主循环里每隔 N 次迭代调用一次plt.pause(0.001)同时用plt.cla()清空重绘。这样你能直观看到树是怎么生长的哪一步卡住了哪个参数导致树偏向一边。我还会把每次迭代的采样点用浅色散点画出来方便判断goal_bias是否合理。这个面板不需要多漂亮能看清就行但能省下大量盲猜的时间。4. 避坑与常见问题排查4.1 树一直朝一个方向长目标点根本够不着现象可视化里树像一根长矛一样朝某个方向直直伸出去完全不探索其他区域。原因通常是goal_bias设得太高比如 0.5 以上导致大部分采样都落在目标点附近树被“吸”过去了。解决把goal_bias降回 0.1 左右或者改成动态偏置——前期低偏置鼓励探索后期高偏置加速收敛。另一个可能原因是step_size太大每次扩展都朝目标方向猛冲忽略了侧向空间。4.2 路径贴着障碍物边缘走实际执行时撞上去了现象规划出的路径在可视化里看起来没撞但实际机器人跑的时候擦到了障碍物。原因是碰撞检测只检查了线段中心线没有考虑机器人的物理尺寸。解决在碰撞检测时给障碍物加一个膨胀半径或者把机器人当作一个圆检查线段到障碍物的距离是否小于安全半径。常见做法是把障碍物矩形向外扩展机器人半径的一半这样规划出的路径天然保持安全距离。4.3 迭代次数够了但返回的树没有连到目标现象rrt_plan跑完MAX_ITER次返回的树最后一个节点离目标还很远。原因可能是目标点被障碍物包围或者step_size太小导致树长得太慢。解决先检查目标点是否在障碍物内部如果是算法永远找不到。如果目标点自由尝试增大step_size或提高goal_bias。还有一个隐蔽原因是collision_free的采样精度不够把可行线段误判为碰撞导致树无法穿过窄通道。把采样点数从 20 提高到 100 试试。4.4 每次运行结果都不一样路径质量忽好忽坏现象同一个地图跑十次得到十条完全不同的路径有的很直有的绕一大圈。这是 RRT 的随机性决定的不是 bug。解决如果要求稳定可以固定随机种子比如np.random.seed(42)这样每次采样序列一致结果可复现。但固定种子只是让结果可重复不保证最优。更好的做法是跑多次取最短路径或者换用 RRT* 算法它在理论上能渐近最优代价是计算量更大。4.5 三维场景下代码直接崩了或者慢到无法接受现象把二维代码改成三维后最近邻搜索和碰撞检测都变得极慢。原因是维度升高后线性搜索的节点数量爆炸而且三维线段与障碍物的求交更复杂。解决最近邻必须上 KD-Tree三维的scipy.spatial.cKDTree同样可用。碰撞检测可以把三维障碍物简化成轴对齐包围盒AABB用 slab 方法快速判断线段是否与盒子相交。另外三维场景下step_size要适当放大因为空间体积大了小步长会导致树覆盖效率极低。5. 进阶RRT* 的渐近最优与一个实用调参技巧RRT* 和 RRT 的核心区别在“重选父节点”和“重连”两步。每次加入新节点q_new后RRT* 会在q_new附近一个半径为r的邻域内找所有节点检查是否可以通过q_new以更短的累计代价到达它们。如果是就把这些节点的父节点改成q_new。同时q_new自己也会在邻域内选一个累计代价最小的节点作为父节点而不是简单地选最近邻。这两步让 RRT* 随着采样增多路径代价逐渐逼近最优。代价是每次迭代要多做一圈邻域查询和代价比较计算量比 RRT 大不少。邻域半径r的取值有个经验公式r min(gamma * (log(n) / n)^(1/d), step_size)其中n是当前节点数d是空间维度gamma是一个常数通常取略大于地图对角线长度的值。这个公式保证在节点少时邻域大、节点多时邻域小平衡探索和优化。我实际调的时候会把gamma从 1.0 开始试如果路径优化不明显就加大到 2.0如果计算太慢就降到 0.5。下面是一个 RRT* 重选父节点的核心片段接在 RRT 的扩展逻辑之后def rewire(tree, q_new, radius, cost): RRT* 的重选父节点与重连 neighbors [node for node in tree if np.linalg.norm(node.pos - q_new.pos) radius and node is not q_new.parent] # 重选父节点找累计代价最小的邻居作为新父节点 best_parent q_new.parent best_cost cost[q_new.parent] np.linalg.norm(q_new.pos - q_new.parent.pos) for node in neighbors: new_cost cost[node] np.linalg.norm(q_new.pos - node.pos) if new_cost best_cost and collision_free(node.pos, q_new.pos): best_parent node best_cost new_cost q_new.parent best_parent cost[q_new] best_cost # 重连检查邻居是否可以通过 q_new 获得更短路径 for node in neighbors: new_cost cost[q_new] np.linalg.norm(node.pos - q_new.pos) if new_cost cost[node] and collision_free(q_new.pos, node.pos): node.parent q_new cost[node] new_cost这段代码里cost是一个字典记录每个节点从根节点出发的累计路径长度。rewire先找邻域内所有节点然后做两件事给q_new选一个累计代价最小的父节点以及把邻域内那些“绕远路”的节点的父节点改成q_new。注意每次改父节点前都要做碰撞检测否则可能引入穿过障碍物的边。这个函数在每次成功扩展后调用radius用上面公式动态计算。跑起来之后你会发现前期路径还是很乱但采样到几千个节点后路径会明显比原始 RRT 更短更直。我自己的习惯是如果项目对路径质量要求不高、只求快速有解就用 RRT 加 shortcut如果要求接近最优且计算资源允许就上 RRT*并且把max_iter设到 20000 以上同时用 KD-Tree 加速邻域查询。最后提醒一句RRT* 的“渐近最优”是理论保证实际跑的时候如果gamma设得太小邻域太窄优化效果会大打折扣这个参数值得多花时间调。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

DeepSeek教育机器人技术拆解:多模态感知与对话系统工程落地

DeepSeek教育机器人技术拆解:多模态感知与对话系统工程落地

简介:这是一份面向教育机器人研发、多模态人工智能应用与产品设计人员的技术方案型PDF文档,聚焦教育场景理解与自然对话交互系统的落地实现。整套文档共909页、51个大章节,单文件约18.68MB,支持目录跳转和书签大纲快速定位&#x…

2026/10/15 21:34:59 阅读更多 →
HashMap源码深度解析:结构、扩容与并发调优

HashMap源码深度解析:结构、扩容与并发调优

1. 项目概述:HashMap 源码到底该读哪些地方前两天一位同事踩了个很典型的坑:他在一个模拟项目X里用 HashMap 做缓存,提前给了一个偏大的初始容量,结果内存占用一直很高。他问我,HashMap 源码里不是明明写了扩容规则吗&…

2026/10/15 21:35:13 阅读更多 →
200行Qt贪吃蛇实战:QPainter自绘与QTimer驱动

200行Qt贪吃蛇实战:QPainter自绘与QTimer驱动

简介:面向 Qt 初学者的完整贪吃蛇项目,演示如何用约 200 行代码搭建一个界面版游戏。项目覆盖 QWidget 主窗口、QGraphicsView/Scene 图形渲染、定时器驱动的蛇移动、键盘方向控制以及食物生成与碰撞检测等核心机制,适合刚接触 Qt 的学生或开…

2026/10/15 21:35:29 阅读更多 →

最新新闻

3PAR存储配置实战:HOST/CPG/VV/WWN五步操作与排错指南

3PAR存储配置实战:HOST/CPG/VV/WWN五步操作与排错指南

简介:《HP 3PAR操作手册》是一份面向存储管理员与运维人员的实用指南,围绕HP 3PAR存储的初始化部署与配置展开,重点介绍管理控制台的登录使用、主机创建、CPG存储池搭建、VV虚拟卷创建及分配等核心操作,适用于数据中心、云计算、虚…

2026/10/16 0:15:13 阅读更多 →
PixiJS跳棋实战:图形渲染、交互与性能优化全链路解析

PixiJS跳棋实战:图形渲染、交互与性能优化全链路解析

1. 为什么选跳棋作为PixiJS入门项目:一个被低估的“图形引擎压力测试场”很多人第一次接触PixiJS,习惯性地从“加载一张图、让它动起来”开始——这没错,但容易陷入“能跑通会用”的幻觉。我带过几期前端图形开发小课,发现一个规律…

2026/10/16 0:15:13 阅读更多 →
使用 MCP 自动化 JxBrowser:把 MCP 配置改到 TaoToken 的完整实践

使用 MCP 自动化 JxBrowser:把 MCP 配置改到 TaoToken 的完整实践

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

2026/10/16 0:15:13 阅读更多 →
5款AI PPT工具横评:提升职场效率的智能选择

5款AI PPT工具横评:提升职场效率的智能选择

1. 为什么我们需要AI PPT工具?作为一名经常需要制作演示文稿的职场人,我深刻理解传统PPT制作过程中的痛点。每次接到新项目,光是构思内容框架就要耗费大量时间,更别提排版设计、配色搭配这些细节工作了。直到去年开始接触AI PPT工…

2026/10/16 0:15:13 阅读更多 →
Spring Boot多数据源路由原理与分库分表实战避坑指南

Spring Boot多数据源路由原理与分库分表实战避坑指南

简介:本资源是一份面向Java后端开发者与数据库架构学习者的分库分表实战项目包,聚焦高并发、大数据量场景下的数据库水平扩展难题,以Sharding-JDBC为核心框架实现多数据源动态切换与透明化分片。资源包含73个文件,主体为18个Java业…

2026/10/16 0:15:13 阅读更多 →
云服务器上部署Nacos单机版:测试环境微服务注册与配置管理实战

云服务器上部署Nacos单机版:测试环境微服务注册与配置管理实战

1. 为什么单机版Nacos依然是测试环境的刚需做过微服务架构的朋友都清楚,服务注册与配置管理是整个体系的神经中枢。Nacos作为这个中枢的国产开源实现,这几年在中小团队里的普及速度非常快。但很多人第一次在云服务器上装Nacos的时候,会陷入一…

2026/10/16 0:14:12 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

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

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/15 9:49:14 阅读更多 →