SkiaSharp.Views.Gtk3 的 SKDrawingArea:在 GTK3 桌面应用中集成 SkiaSharp 绘图画布
图形学图像处理跨平台【免费下载链接】SkiaSharpSkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Googles Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.项目地址https://gitcode.com/gh_mirrors/sk/SkiaSharp点击查看免费下载本指南以 SkiaSharp 1.68.1 版本中SkiaSharp.Views.Gtk3.dll的 API 变更记录为线索系统讲解SkiaSharp.Views.Gtk.SKDrawingArea这一 GTK3 绘图控件的完整用法包括控件类签名、PaintSurface事件与OnPaintSurface两种绘制入口、CanvasSize的像素语义以及它与 Cairo 渲染管线之间的桥接原理。阅读本文后你将能够在 GtkSharp 3 桌面程序中直接嵌入 SkiaSharp 画布绘制矢量图形、位图与文本并借助类型转换扩展在 SkiaSharp 与 GTK 数据类型之间自由切换。一、背景1.68.1 版本中的新命名空间与新类型在 SkiaSharp 1.68.1 的发布说明API diff: SkiaSharp.Views.Gtk3.dll中SkiaSharp.Views.Gtk3.dll程序集版本由0.0.0.0变更为1.68.0.0并新增了一个命名空间新命名空间SkiaSharp.Views.Gtk新类型SkiaSharp.Views.Gtk.SKDrawingArea这意味着从该版本起GTK3 桌面应用获得了官方的一等公民绘图控件SKDrawingArea。它继承自 GtkSharp 的标准绘图控件Gtk.DrawingArea同时通过实现Atk.IImplementor、GLib.IWrapper、System.IDisposable等接口融入 GTK 对象体系其源码实现位于 source/SkiaSharp.Views/SkiaSharp.Views.Gtk3/SKDrawingArea.cs。public class SKDrawingArea : Gtk.DrawingArea, Atk.IImplementor, GLib.IWrapper, System.IDisposable { // constructors public SKDrawingArea (); // properties public SkiaSharp.SKSize CanvasSize { get; } // events public event System.EventHandlerSkiaSharp.Views.Desktop.SKPaintSurfaceEventArgs PaintSurface; // methods protected override void Dispose (bool disposing); protected override bool OnDrawn (Cairo.Context cr); protected virtual void OnPaintSurface (SkiaSharp.Views.Desktop.SKPaintSurfaceEventArgs e); }该控件位于独立的程序集SkiaSharp.Views.Gtk3RootNamespace为SkiaSharp.Views.Gtk其工程文件见 source/SkiaSharp.Views/SkiaSharp.Views.Gtk3/SkiaSharp.Views.Gtk3.csproj。工程通过PackageReference引用GtkSharp 3.24.24.95并依赖核心库SkiaSharp与桌面公共程序集SkiaSharp.Views.Desktop.Common因此绘制事件参数SKPaintSurfaceEventArgs来自SkiaSharp.Views.Desktop命名空间。二、项目引用与运行环境要在自己的 GTK3 应用中启用该控件需要准备三部分依赖GtkSharp 3通过 NuGet 包GtkSharp 3.24.24.95引入该版本号与 SkiaSharp.Views.Gtk3 工程锁定的版本一致。SkiaSharp 核心库提供SKSurface、SKCanvas、SKImageInfo等绘图基础设施。SkiaSharp.Views.Gtk3 程序集即本文主角同时会连带引入SkiaSharp.Views.Desktop.Common。仓库中的示例工程 samples/Basic/Gtk3/SkiaSharpSample/SkiaSharpSample.csproj 展示了完整的引用方式OutputType为WinExe目标框架为net10.0直接以ProjectReference引用上述程序集并通过IncludeNativeAssets.SkiaSharp.targets见 binding/IncludeNativeAssets.SkiaSharp.targets将对应平台的libSkiaSharp原生库复制到输出目录。运行还需要系统级 GTK3 开发库macOSbrew install gtk3Ubuntu/Debiansudo apt-get install libgtk-3-devFedorasudo dnf install gtk3-develLinux 下构建运行示例dotnet run --project SkiaSharpSample/SkiaSharpSample.csproj说明GTK 3 不提供 GPU 加速的 SkiaSharp 视图SKDrawingArea走的是 CPU 软件渲染路径这一点在 samples/Basic/Gtk3/README.md 中有明确说明。三、SKDrawingArea 的两种绘制方式SKDrawingArea源码的 XML 文档明确指出在该画布上绘图有两种等价的方式见 SKDrawingArea.cs订阅PaintSurface事件适用于大多数场景尤其是 UI 代码与绘制逻辑分离时。重写OnPaintSurface虚方法适用于子类化控件、封装自定义绘制逻辑的场景。PaintSurface事件类型为EventHandlerSKPaintSurfaceEventArgs其中SKPaintSurfaceEventArgs定义在 source/SkiaSharp.Views/SkiaSharp.Views.Shared/SKPaintSurfaceEventArgs.cs提供三个关键成员成员类型含义SurfaceSKSurface当前正在绘制的 SkiaSharp 表面通过Surface.Canvas获得画布InfoSKImageInfo表面信息Info.Width/Info.Height即画布像素尺寸RawInfoSKImageInfo底层表面的原始图像信息实际尺寸与颜色类型事件方式的最小绘制模板myView.PaintSurface (sender, e) { var surface e.Surface; var surfaceWidth e.Info.Width; var surfaceHeight e.Info.Height; var canvas surface.Canvas; // draw on the canvas canvas.Flush (); };重写OnPaintSurface的方式protected override void OnPaintSurface (SKPaintSurfaceEventArgs e) { // call the base method base.OnPaintSurface (e); var surface e.Surface; var surfaceWidth e.Info.Width; var surfaceHeight e.Info.Height; var canvas surface.Canvas; // draw on the canvas canvas.Flush (); }[!IMPORTANT] 如果重写了OnPaintSurface必须调用base.OnPaintSurface(e)否则PaintSurface事件将不会被触发。这一点在控件源码的 XML 注释中被标记为重要注意事项SKDrawingArea.cs其实现也印证了调用关系OnPaintSurface的默认实现就是PaintSurface?.Invoke(this, e)SKDrawingArea.cs。两种方式都在 GTK 控件重绘时被调用即由OnDrawn(Cairo.Context cr)驱动下文详述因此绘制是按需触发的——当窗口尺寸变化、被遮挡后重新显示或显式调用QueueDraw()时GTK 会请求重绘进而触发绘制回调。四、CanvasSize 属性与像素密度语义CanvasSize返回类型为SkiaSharp.SKSize其文档注释说明了关键语义SKDrawingArea.csThe canvas size may be different to the view size as a result of the current devices pixel density.即画布尺寸像素可能与控件尺寸逻辑单位不一致因为高分屏HiDPI下存在像素密度换算。源码实现为public SKSize CanvasSize pix null ? SKSize.Empty : new SKSize(pix.Width, pix.Height);其中pix是内部持有的 CairoImageSurface。当控件尚未完成首次分配pix为null时返回SKSize.Empty否则返回底层像素缓冲的实际宽高。在示例 DrawingPage.cs 中可以看到处理这一差异的典型手法——用e.Info.Width / skiaView.AllocatedWidth计算缩放系数并canvas.Scale(sx, sy)从而让逻辑坐标与控件坐标保持一致。五、渲染管线从 GTK Drawn 事件到 SkiaSharp 表面SKDrawingArea的核心是把 SkiaSharp 的绘制结果呈现到 GTK3 的 Cairo 上下文中。理解整个流程有助于调试像素格式、HiDPI 等问题。关键路径在OnDrawn(Cairo.Context cr)SKDrawingArea.cs获取控件分配区域通过AllocationGdk.Rectangle得到控件的实际宽高。构建SKImageInfonew SKImageInfo(w, h, SKImageInfo.PlatformColorType, SKAlphaType.Premul)即使用平台默认颜色类型、预乘 alpha。创建或复用绘图对象CreateDrawingObjects()按需创建 CairoImageSurface(Format.Argb32, w, h)并通过SKSurface.Create(imgInfo, pix.DataPtr, imgInfo.RowBytes)让 SkiaSharp 直接包装 Cairo 的像素缓冲——两者共享同一块内存避免了拷贝SKDrawingArea.cs。当尺寸变化时旧的 surface 与 pixbuf 会被释放重建。触发绘制回调用SKAutoCanvasRestore包裹OnPaintSurface(new SKPaintSurfaceEventArgs(surface, imgInfo))保证绘制结束后画布状态自动恢复SKDrawingArea.cs。刷新与标记脏surface.Canvas.Flush()将 SkiaSharp 的绘制命令实际写入共享像素缓冲随后pix.MarkDirty()通知 Cairo 缓冲内容已变化。通道顺序修正RGBA ↔ ARGB32当颜色类型为Rgba8888时用SKSwizzle.SwapRedBlue交换 R/B 通道使 SkiaSharp 的 RGBA 布局适配 Cairo 的 ARGB32 内存布局SKDrawingArea.cs。呈现到 GTK 窗口cr.SetSourceSurface(pix, 0, 0)cr.Paint()把 pixbuf 画到 GTK 的 Cairo 上下文返回true表示事件已处理。注意OnDrawn收到的Cairo.Context在方法返回后即被释放因此不应在方法作用域之外持有该 Cairo 上下文的引用SKDrawingArea.cs。Dispose(bool)重写则在释放托管资源时调用FreeDrawingObjects()同步释放 Cairo pixbuf 与 SkiaSharp surfaceSKDrawingArea.cs避免原生资源泄漏。六、类型转换扩展GTKExtensions与控件同程序集的 GTKExtensions.cs 提供了一组静态扩展方法用于在 SkiaSharp 与 GTK/GDK 类型间互相转换按用途可分为四类1. 几何类型转换扩展方法方向说明Point.ToSKPointI()/SKPointI.ToPoint()Gdk.Point ↔ SKPointI点Rectangle.ToSKRectI()/SKRectI.ToRect()Gdk.Rectangle ↔ SKRectI矩形Size.ToSKSizeI()/SKSizeI.ToSize()Gdk.Size ↔ SKSizeI尺寸2. 颜色转换Color.ToSKColor()将Gdk.Color分量范围为 0~65535按65535f / 255f缩放为 0~255 的SKColor字节分量SKColor.ToColor()反向转换回Gdk.Color。3. 图像转换SK → Gdk.PixbufSKPicture.ToPixbuf(SKSizeI dimensions)先把图片栅格化为指定尺寸的SKImage再转 pixbufSKImage.ToPixbuf()创建Colorspace.Rgb、带 alpha、每像素 8 位的Pixbuf通过SKPixmap读取像素若颜色类型为Bgra8888同样执行SwapRedBlue修正通道顺序SKBitmap.ToPixbuf()与SKPixmap.ToPixbuf()经由SKImage.FromPixels中转。4. 图像转换Gdk.Pixbuf → SKPixbuf.ToSKBitmap()/Pixbuf.ToSKImage()将 GDK 像素缓冲包装为 SkiaSharp 位图或图像Pixbuf.ToSKPixmap(SKPixmap)直接把 pixbuf 像素数据拷贝进已有 pixmap并在Bgra8888下交换 R/B。这些扩展让开发者可以在 GTK 控件如Gtk.Image、Gdk.Pixbuf相关控件与 SkiaSharp 之间无缝搬运图像数据是集成时的高频工具。七、实战示例在 GTK3 应用中嵌入 SKDrawingArea仓库中的 Basic/Gtk3 示例是完整的可运行参考包含CPU 画布与自由绘画两个页面其 UI 布局由 Glade.glade文件定义、以嵌入资源方式编译见 SkiaSharpSample.csproj。由于SKDrawingArea不是 Glade 内置控件示例采用占位容器 代码注入的模式// Load layout from Glade — provides the placeholder container var builder MainWindow.LoadBuilder(SkiaSharpSample.CpuPage.glade); var page (Box)builder.GetObject(cpuPage); // Insert SKDrawingArea (custom widget not available in Glade) var skiaView new SKDrawingArea(); skiaView.PaintSurface OnPaintSurface; page.PackStart(skiaView, true, true, 0);完整代码见 samples/Basic/Gtk3/SkiaSharpSample/CpuPage.cs场景一静态场景渲染CpuPage演示了典型的事件订阅 SkiaSharp 绘制流程CpuPage.cscanvas.Clear(SKColors.White)清空画布SKShader.CreateRadialGradient创建径向渐变背景配合SKPaint { IsAntialias true, Shader shader }绘制全屏矩形canvas.DrawCircle绘制一组半透明彩色圆canvas.DrawText(SkiaSharp, ..., SKTextAlign.Center, font, textPaint)居中绘制文字。这展示了在 GTK3 中完成渐变 几何图形 文本组合渲染的最小闭环。场景二交互式自由绘画DrawingPage更进一步演示了如何让SKDrawingArea响应鼠标输入并增量重绘DrawingPage.cs注册事件掩码skiaView.AddEvents(EventMask.ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ScrollMask | EnterNotifyMask | LeaveNotifyMask)并订阅ButtonPressEvent、ButtonReleaseEvent、MotionNotifyEvent、ScrollEvent等 GTK 事件记录笔画按下时创建SKPathBuilder并MoveTo移动时LineTo抬起时将路径快照存入笔画列表按需重绘每次状态变化调用skiaView.QueueDraw()请求 GTK 重绘触发OnDrawn→PaintSurface的渲染管线绘制回放在OnPaintSurface中先清空画布再逐条回放strokes列表中的路径实现笔画在改色、调粗细后依然保留。其中e.Info.Width / skiaView.AllocatedWidth的坐标缩放、滚动滚轮调整笔刷大小ScrollDirection.Smooth与DeltaY判断都是 GTK3 SkiaSharp 集成中的实用细节。八、版本与适用前提小结项目值程序集SkiaSharp.Views.Gtk3.dll1.68.1 起程序集版本 1.68.0.0命名空间SkiaSharp.Views.Gtk依赖GtkSharp 3.24.24.95、SkiaSharp、SkiaSharp.Views.Desktop.Common渲染方式CPU 软件渲染GTK3 无 GPU 加速视图参考示例samples/Basic/Gtk3相关测试GTK 4 版本存在对应的SKDrawingAreaTest与GTKExtensionsTest见 tests/SkiaSharp.Views.Gtk4.Tests可作行为参考从 API 变更记录到源码实现再到示例工程SKDrawingArea构成了 GTK3 桌面应用接入 SkiaSharp 的完整链路Gtk.DrawingArea负责窗口集成与事件分发CairoImageSurface提供共享像素缓冲SKSurface提供高性能的 2D 绘制 APIGTKExtensions则打通了两套类型系统。掌握这些机制后你便可以在 GTK3 应用中自由使用 SkiaSharp 的完整绘图能力。赞分享图形学图像处理跨平台【免费下载链接】SkiaSharpSkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Googles Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.项目地址https://gitcode.com/gh_mirrors/sk/SkiaSharp点击查看免费下载相关推荐解读 SkiaSharp GTK 视图的完整 API 面从 1.60.0 引入的 SKWidget 到现代 SKDrawingArea解读 SkiaSharp GTK 视图的完整 API 面从 1.60.0 引入的 SKWidget 到现代 SKDrawingArea 本篇基于 SkiaSh图形学图像处理跨平台Synergy-core与云桌面集成在远程桌面环境中的应用Synergy core与云桌面集成在远程桌面环境中的应用 Synergy core作为开源的跨平台键盘鼠标共享工具正在成为云桌面和远程办公环境中的重要技术桌面应用网络通信Anime.js桌面应用Electron集成动画方案Anime.js桌面应用Electron集成动画方案 痛点桌面应用动画开发的挑战 你是否在为桌面应用寻找高性能的动画解决方案传统Web动画在Electro前端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Linux命名管道FIFO实战:原理、阻塞机制与常见坑

Linux命名管道FIFO实战:原理、阻塞机制与常见坑

进程间通信:命名管道实战笔记 搞Linux开发的人应该都有个体会:进程间通信(IPC)这块,理论书翻得再熟,真到写代码的时候还是会卡壳。特别是命名管道(FIFO),看起来不就是往文…

2026/10/14 16:34:44 阅读更多 →
Qt Widgets数独实战:从盘面绘制到回溯出题与位运算求解

Qt Widgets数独实战:从盘面绘制到回溯出题与位运算求解

简介:这是一份基于Qt框架开发的数独游戏完整工程源码,面向Qt初学者、计算机专业学生及游戏开发入门者,提供从零搭建一个跨平台数独应用的完整参考。资源共141个文件,压缩包约25.6MB,包括21个C源文件、15个头文件、8个界…

2026/10/14 16:33:43 阅读更多 →
基于pdf.js的浏览器PDF阅读器插件开发:渲染、搜索高亮与交互实现

基于pdf.js的浏览器PDF阅读器插件开发:渲染、搜索高亮与交互实现

简介:一套基于 pdf.js 开发的在线文档处理插件,专为 Web 开发者提供可直接部署的 PDF 查看解决方案,核心功能包括全文搜索、结果高亮、区分大小写匹配、分页浏览、放大缩小、下载与打印,可通过 viewer.html?file 方式快速加载指定…

2026/10/14 16:33:43 阅读更多 →

最新新闻

TreeTycoon开发实战:从数据模型到经济平衡的完整指南

TreeTycoon开发实战:从数据模型到经济平衡的完整指南

简介:TreeTycoon 是一套基于 C17 与 SFML 开发的 2D 模拟经营游戏源码项目,面向具备一定 C 基础、希望研究游戏引擎架构与经营类玩法实现的开发者。项目围绕树木种植与经营主题,采用自定义游戏框架,涵盖场景栈管理、资源加载、音频…

2026/10/14 17:18:41 阅读更多 →
考研复试数据结构全攻略:机试面试高频考点与实战思路

考研复试数据结构全攻略:机试面试高频考点与实战思路

先说结论:考研复试里的数据结构,和初试完全是两码事。初试你可以在试卷上慢慢推演,复试要么是机试限时AC,要么是面试官盯着你手写代码、追问复杂度,你脑子里那点“背下来的知识点”根本不够用。这篇文章就是一次梳理&a…

2026/10/14 17:18:41 阅读更多 →
半导体金相显微镜核心技术与应用解析

半导体金相显微镜核心技术与应用解析

1. 金相显微镜在半导体行业的独特价值第一次接触半导体材料分析时,我被车间工程师的一句话点醒:"看得到缺陷才能解决缺陷"。金相显微镜正是半导体制造的"工业CT",它能将抽象的晶格缺陷、掺杂分布转化为可视化的微观图像。…

2026/10/14 17:18:41 阅读更多 →
Windows 安装 OpenClaw 常见问题,路径、杀毒、Gateway 离线排查

Windows 安装 OpenClaw 常见问题,路径、杀毒、Gateway 离线排查

Windows 一键部署 OpenClaw 教程|5 分钟搭建本地 AI 智能体,告别复杂环境配置 前言 2026 年开源圈爆火的「数字员工」OpenClaw(昵称小龙虾),GitHub 星标狂揽 28 万 。它区别于普通对话型 AI,是一套可以直…

2026/10/14 17:18:41 阅读更多 →
全栈记账系统开发实战:React+NestJS+PostgreSQL技术解析

全栈记账系统开发实战:React+NestJS+PostgreSQL技术解析

1. 项目背景与核心价值记账这件事,我从大学时代就开始折腾了。最早用Excel手工记录,后来试过各种记账App,但总感觉差点意思——要么功能太简单,要么数据不透明,要么就是突然收费。去年终于下定决心,自己开发…

2026/10/14 17:18:41 阅读更多 →
NSGA-II多目标优化MATLAB实现:从核心机制到避坑指南

NSGA-II多目标优化MATLAB实现:从核心机制到避坑指南

简介:这份资源是NSGA-II多目标优化算法的MATLAB实现代码,面向正在学习或研究多目标进化算法的本科生、研究生及科研人员,帮助读者理解非支配排序、拥挤距离、精英策略与基因操作等核心机制,并可直接用于测试函数实验。压缩包共27个…

2026/10/14 17:17:41 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →