首页/新闻资讯/正文详情

微信小程序分享海报生成:Canvas绘图与二维码封装实战解析

发布时间:2026/9/25 1:11:23 来源:云帆数科 栏目:资讯中心
微信小程序分享海报生成:Canvas绘图与二维码封装实战解析
简介一套面向微信小程序开发者的分享海报生成方案适用于产品推广、活动裂变等社交场景可快速解决自定义海报与二维码组合的常见需求对刚接触小程序绘图和媒体接口的开发者尤为友好。资源包共含5个文件覆盖逻辑脚本、页面结构、样式表、页面配置及使用说明整体仅4KB轻量紧凑说明文档对图片获取、文件下载、画布绘制、保存相册等接口的调用顺序做了梳理。通过这份代码开发者能掌握从拉取网络图片、转存本地临时文件到生成二维码、画布叠加海报并导出最终写入系统相册的完整流程页面模板也可直接复用按品牌视觉二次修改。已有2651人学习下载适合需要快速上线分享功能或深入理解小程序媒体能力的前端工程师查看。1. 微信小程序生成分享海报一份把二维码生成和 Canvas 绘图封装好的源码包在电商、餐饮、知识付费类小程序里最省钱的拉新手段就是用户生成分享海报点一下按钮本机合成一张带昵称、头像、商品图和二维码的图片用户长按保存或转发到微信群。这个 rar 包解决的是这条链路最麻烦的两段——二维码生成和海报绘制。它不依赖后端出图服务所有绘制都在本地 Canvas 完成适合刚起步的小程序团队也适合给 uniapp 或原生项目补一个稳定的分享海报模块。需要先对齐预期这份资源不是教你设计海报而是把微信小程序里的动态数据、远程图片、二维码三者准确定位到一张画布上并稳定导出保存。后面五章按选型、接入、二维码参数、故障、进阶顺序展开全程可复现。适不适合你看它怎么解决“旧版 Canvas 经常白屏”和“导出图片发虚”这两个老问题就知道。2. 海报生成的底层选型Canvas 2D、二维码库和动态数据各负责哪一层2.1 为什么海报必须在前端画而不是后端合成先算一笔服务器成本账。假设一个日活过万的电商小程序海报入口放在商品详情页。每次用户点击“生成海报”后端都要接一个请求把用户昵称、头像、商品图、实时二维码渲染成一张大图。并发一上来一张 750×1334 的图从渲染到回传至少 300ms带宽和 CPU 双消耗。更麻烦的是二维码内容往往带邀请人 ID属于强动态数据后端缓存基本没法复用。前端 Canvas 绘制把这整套逻辑搬到了用户手机本地。它只消耗本机内存和 GPU不占服务器流量出图时间取决于图片加载速度通常能压到 1 秒以内。微信小程序从基础库 2.9.0 开始支持 Canvas 2D 接口可以通过 SelectorQuery 拿到节点再调用 getContext(2d) 获得标准绘图上下文。这套接口和浏览器行为几乎一致可以处理圆角头像、渐变文字、富文本排版等复杂效果。而旧版 wx.createCanvasContext 接口虽然能用但它是小程序早期提供的写法绘制方法链式调用不支持标准 canvas 节点对象也不方便在组件内部做多画布管理。很多老项目里的海报代码还在用这种写法常见问题包括组件层级冲突、绘制层级错乱、画布无法局部更新。所以拿到这份资源后第一件事就是确认组件有没有使用 type2d。多说一句这里的选择和“微信小程序顶部导航栏高度”没有关系海报通常以全屏弹层或独立页面呈现遇到导航栏挡图大多是组件定位问题而不是画布尺寸问题。真机上画布只占可见区域的一部分导出图片时用的是画布自身宽高不依赖页面滚动位置。把这些边界想清楚后面调样式时才不会被导航栏、tabBar 这些因素带偏。2.2 代码包里的模块布局和绘制顺序一个能直接复用的分享海报组件目录上一般长这样具体命名以你解压出来的为准文件/目录职责关键点components/share-poster/index.js海报绘制主逻辑图片加载顺序、绘制顺序components/share-poster/index.wxml画布容器canvas 标签必须加 type2dcomponents/share-poster/index.wxss画布样式用 flex 居中避免百分比高度utils/weapp-qrcode.js二维码生成器纯 JS 矩阵生成无网络依赖绘制顺序从上到下只有一条硬规则先画底图再画头像和昵称再画商品图与文案二维码放最后。Canvas 是即时绘制模式后画的元素会盖住先画的元素。如果把二维码放在中间画后面被昵称或装饰条覆盖扫不出来时根本不知道是生成坏了还是被盖了。初始化画布的核心代码和旧版接口的区别就在这里// components/share-poster/index.js Component({ lifetimes: { ready() { this.initCanvas(); } }, methods: { initCanvas() { const query this.createSelectorQuery(); query.select(#posterCanvas).fields({ node: true, size: true }) .exec((res) { const canvas res[0].node; const ctx canvas.getContext(2d); // 取设备像素比不能写死 const dpr wx.getWindowInfo().pixelRatio; // 画布逻辑尺寸按设计稿 750 定宽 canvas.width 750; canvas.height 1334; ctx.scale(dpr, dpr); this.canvas canvas; this.ctx ctx; }); } } });逻辑说明先用 SelectorQuery 拿到 Canvas 节点再把画布宽高设为设计稿数值。注意 Canvas 2D 的节点宽高和 CSS 宽高是两套体系代码里设置 width/height 决定的是绘图缓冲区的物理尺寸WXSS 里的 width/height 决定它在页面上的展示尺寸。两者不一样绘制坐标按节点宽高来思考就不会乱。参数说明ctx.scale(dpr, dpr) 是防止导出模糊的最关键一行。比如 iPhone 的 dpr 是 3画布缓冲区会被放大到 2250×4002但你写坐标时仍然按 750 的逻辑尺寸来。导出时微信把缓冲区内容按实际像素输出海报才清晰。wx.getWindowInfo() 是新版 API如果你的基础库比较旧可以用 wx.getSystemInfoSync() 代替两者都能拿到 pixelRatio。如果你是用 uniapp 开发微信小程序这套思路同样成立。uni.createCanvasContext 封装的是旧版接口完成后要手动调用 draw()而用 uni.createSelectorQuery 拿到 node 后走 canvas.getContext(2d)逻辑和原生小程序一致。差别只在图片对象上uniapp 里生成图片用 uni.getImageInfo回调名称和事件参数与原生略有差异。建议先按原生小程序跑通再平移过去。海报的绘制时长其实是由图片加载和绘图调用次数决定的不是被 Canvas 本身决定的。一次场景绘制如果超过几百个 fillRect导出一样会慢。这套资源里常见的优化是在组件里用一个全局缓存对象把商品图和头像临时路径存下来同一用户第二次生成海报时getImageInfo 直接从本地缓存命中省掉整个网络下载。另外绘制时不要频繁调用 ctx.save 和 ctx.restore每调用一次都会切换状态栈循环里会明显拖慢速度。把需要裁剪的圆角头像集中画完再一次恢复就好。3. 把组件接进项目页面注册、数据入参与导出临时图片的完整链路3.1 在页面 JSON 里注册组件并按需传入海报数据接入这套组件页面侧只需要三步把组件目录放进项目在页面 JSON 里声明然后在 WXML 里传入数据。组件内部把画布、二维码生成、图片导出都封装好页面本身不碰任何 Canvas API。这是资源落地最好的方式因为易错的部分全收在一处。页面 JSON 的注册写法{ usingComponents: { share-poster: /components/share-poster/index } }这里要注意路径必须写绝对路径并以 / 开头。很多刚上手的人写成了相对路径导致预览报错“组件未找到”。这是低级错误但排查起来很烦因为编译时并不报红只有真机预览才会提示找不到。WXML 部分触发按钮和弹层里放组件view classposter-page button typeprimary bindtaponShareTap生成分享海报/button share-poster show{{showPoster}} share-data{{posterData}} bind:poster-readyonPosterReady / /view组件的输入数据全靠 share-data 一个字段这样设计的好处是页面侧不用关心组件内部怎么画只负责把真实数据准备好。show 字段用来控制海报弹层是否显示poster-ready 是组件画完以后通知页面的事件事件详情里会带上 posterUrl 临时图片路径。3.2 页面侧组装数据二维码文本怎么拼才能分享后有效share-data 里的值决定海报的最终效果。正常的组装逻辑// pages/index/index.js Page({ data: { posterData: null, showPoster: false }, onShareTap() { const userInfo app.globalData.userInfo; const goods this.data.currentGoods; this.setData({ showPoster: true, posterData: { shopName: goods.shopName, userAvatar: userInfo.avatarUrl, goodsImage: goods.imageUrl, goodsName: goods.title, qrCodeText: pages/goods/detail?id goods.id inviter userInfo.id } }); }, onPosterReady(e) { // 组件导出完成拿到临时图片 this.posterPath e.detail.posterUrl; } });二维码文本 qrCodeText 是海报能否带来转化的核心。微信小程序里能扫出小程序页面的二维码其内容必须是页面路径例如 pages/goods/detail?id123而不是一个完整的 http:// 链接。如果你在二维码里放了外链扫码后只会进入无法预料的页面微信对这类链接的拦截率也高。二维码文本长度控制在 120 字符以内越短识别越快字段多了建议只保留 id 和 inviter。在进阶章节我会展开讲 inviter 参数怎么在 App.onLaunch 里取出来。这里先记住分享海报里的二维码本质是一个带参数的小程序页面路径不是一张普通图片链接。3.3 远程图片异步加载先取路径后绘制失败给占位图组件拿到 share-data 后不能立刻把远程图片画到画布上。Canvas 的 drawImage 只接受本地路径或临时文件路径网络 url 会被忽略且不产生任何报错。所以必须先用 wx.getImageInfo 把网络图转成本地临时路径。一次完整的取图逻辑// components/share-poster/index.js function fetchImage(url) { return new Promise((resolve, reject) { wx.getImageInfo({ src: url, success: res resolve(res.path), fail: err reject(err) }); }); } async function drawAll() { const d this.data.shareData; try { const [avatar, goods] await Promise.all([ fetchImage(d.userAvatar), fetchImage(d.goodsImage) ]); // 到这里才真正开始往画布上画 this.ctx.drawImage(avatar, 60, 80, 128, 128); this.ctx.drawImage(goods, 60, 280, 630, 630); } catch (e) { // 图片失败时给一张本地占位图不影响整张海报导出 this.ctx.drawImage(/assets/poster_default.png, 60, 280, 630, 630); } }用 Promise.all 并行加载比串行一张张加载快很多尤其海报里同时有人头像、商品图、优惠券三个远程图时。失败时使用本地占位图而不是直接中断是生产环境的一个习惯宁可海报缺个头像也不能让用户点了按钮后弹出“生成失败”的错。参数说明wx.getImageInfo 对已下载过的相同 URL 有内部缓存一般正常但在 iOS 机型上偶发第一次调用慢或者失败常见原因是图片服务器对 HTTPS 证书或 HTTP/2 支持不完整。排查时直接看 Network 面板里图片请求的状态重点确认协议和证书而不是在小程序代码里反复试。提示远程图片域名必须配到小程序后台的 downloadFile 合法域名否则开发工具关掉“不校验合法域名”后真机直接白图。4. 二维码参数实战size、correctLevel、留白和贴图顺序的配合4.1 二维码生成器参数与调用示例资源包里二维码部分不是调后端接口而是纯前端矩阵生成弱网下仍然能出图。挑库时要留意很多流行的二维码库依赖 document.createElement小程序没有 DOM直接 require 会报错。封装思路是只取矩阵计算部分然后按 Canvas 像素逐个画点。二维码生成函数参考// utils/qrcode.js const QRCode require(./weapp-qrcode.js); function generateQrCode(content, canvas, size 220, level M) { const qr QRCode.create(content, { correctLevel: QRCode.CorrectLevel[level] }); const ctx canvas.getContext(2d); ctx.fillStyle #ffffff; ctx.fillRect(0, 0, size, size); const moduleCount qr.getModuleCount(); const cellSize size / moduleCount; ctx.fillStyle #000000; for (let row 0; row moduleCount; row) { for (let col 0; col moduleCount; col) { if (qr.isDark(row, col)) { ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize); } } } }逻辑说明先创建一个二维码矩阵对象然后遍历行列把要涂黑的位置逐个填充。实现里先铺一层白底是为了保证扫描时有足够对比度。生成后的对象依然是一个 Canvas 图形再通过 wx.canvasToTempFilePath 导出为临时图片方便后续贴到主海报上。参数说明correctLevel 代表二维码容错率也就是允许被遮挡、模糊、残缺区域的百分比。容错级别可纠错比例适用场景L约 7%印刷质量高、无遮挡的海报M约 15%大部分线上图片扫码Q约 25%海报上有品牌水印、装饰条H约 30%压缩率高或会被二次转发在分享海报场景至少选 M如果二维码边缘靠近用户头像建议改 Q。H 级并不是越高级越好因为级别每升一档模块密度越高单位像素对应信息越多扫码需要的对焦精度也更高。所以很多代码里把 H 当成默认值反而让扫码变慢。再补一句边界方案只适用于普通二维码。如果你在项目里用的是小程序码也就是带圆角、带 logo 的那种官方码必须走后端接口生成跟这套纯前端方案是两回事。两者不要混用不然测试时扫不出来会浪费很多时间。4.2 二维码贴上海报坐标计算和四周留白规则二维码在整个海报里的位置不能由“看起来差不多”决定要照顾扫码动作。常见构图是海报下方居中二维码下方留一行提示文案周围留白至少 4 个模块宽度。假设设计稿里二维码占位 220×220画布宽度 750那中心的 x 坐标是 (750 - 220) / 2 265y 坐标一般取海报总高减去提示区域高度再加偏移。// pages/goods/index.js 节选 const qrImg await this.getQrTempFile(); this.ctx.drawImage(qrImg, 265, 950, 220, 220); this.ctx.fillStyle #999999; this.ctx.font 24px sans-serif; this.ctx.textAlign center; this.ctx.fillText(长按识别查看详情, 375, 1210);这段代码把二维码画在 (265, 950) 位置下方用 fillText 画提示文字。注意 fillText 也是 Canvas 绘制内容绘制完成后不会自动换行文案包含特殊符号时要提前拆句。绘制坐标用逻辑单位即可。真机展示时文字和二维码的尺寸由 Canvas 的宽高和 dpr 自动换算只要你不把导出图再人为放大一倍iPhone 和安卓上不会有太大误差。另外一个隐蔽坑二维码前景色和背景色都是浅色或者背景是蓝色渐变扫码器会因对比度不足失败。建议始终黑码白底。如果你非要彩色二维码背景必须纯白二维码的三个“码眼”绝对不能改色。4.3 导出临时图片后二次叠加的两种方式二维码生成后到底先导出再叠加还是直接在同一个 Canvas 上继续画常见做法是先 wx.canvasToTempFilePath 导出二维码临时图再通过 wx.getImageInfo 拿到临时图贴到主画布。中间每一步都要等待图片解码不能直接用同一个 Canvas 上下文里的绘制结果。这样调试时可以把二维码单独截出来检查也方便把二维码整体替换成带 logo 的版本。提示导出临时图片时注意 wx.canvasToTempFilePath 在组件内部使用要传入第二个参数 this指向当前组件实例否则真机上会报 canvas 未找到。5. 避坑清单海报发虚、二维码扫不出、保存失败三类高频故障5.1 现象海报文字发虚放大后全是锯齿原因九成是初始化 Canvas 时没有设置 ctx.scale(dpr, dpr)。画布的物理像素和导出尺寸不一致设计稿里明明很清楚导出图片即糊。解决初始化时按 wx.getSystemInfoSync().pixelRatio 放大画布调 wx.canvasToTempFilePath 时把 destWidth 和 destHeight 传到设计稿的 2 倍。设计稿宽 750导出至少 1500如果海报很大直接按 3 倍出图体积控制在 500KB 以内。5.2 现象二维码扫不出来或识别率极低原因correctLevel 设置成了 L或者二维码周围留白不足又或者海报背景是深色而二维码只画了黑色块。二维码的“码眼”与背景对比度不够微信扫码会一直读不出。解决把级别改到 M 或 Q。绘制时先铺一块不小于二维码边长十分之一的白色底再画黑色模块。白底黑码是最稳妥组合如果非要在彩底上放码底部必须加白色圆角矩形垫一层。5.3 现象用户点击保存提示保存失败或没有任何反应原因wx.saveImageToPhotosAlbum 需要用户授权 scope.writePhotosAlbum用户第一次拒绝后后续调用不再弹窗另外基础库版本太低也不支持该接口。解决保存前先 wx.getSetting 检查授权状态。如果拒绝过用 wx.openSetting 引导用户打开相册权限如果基础库低于 2.3.0降级到提示用户长按图片保存。5.4 现象测试环境海报正常发布线上后头像或商品图缺失原因图片来源域名没配到小程序后台的 downloadFile 合法域名。开发调试时可以关校验线上则必须把 https 域名加白名单。另一个可能是对象存储签名 URL 过期导致下载失败。解决在微信公众平台后台添加图片来源域名到 downloadFile 合法域名同时把远程图片加载设置超时避免个别慢图拖垮整张海报。日志里重点记 getImageInfo 的 fail 回调哪个 URL 失败直接看后台。5.5 现象同一张海报在 iPhone 和安卓上布局不一致原因不同机型 pixelRatio 不同iPhone 多为 3中端安卓从 2.75 到 3 不等代码写死 2 就会模糊或错位。更隐蔽的是 Canvas 2D 节点在不同系统上对 style 的解析略有差异个别安卓 WebView 对 ctx.scale 的顺序敏感。解决不写死 dpr统一用 wx.getWindowInfo().pixelRatio绘制前先 ctx.save()海报主体画完再 ctx.restore()。如果尺寸仍偏差把 Canvas 的宽高按设计稿固定不要依赖 WXSS 高度自动撑开。6. 进阶技巧在海报二维码里加来源参数让扫码回跳带上下文6.1 二维码内容里塞路径参数而不是链接海报二维码的最终内容应该是小程序页面路径生成时把来源参数拼进路径pages/goods/detail?id10086inviterUID_9527这个路径被用户扫码后微信会把 query 原样带到启动参数里。但两种情况要区分用户直接分享海报文件到聊天别人长按识别进入走的是 options.query用户在朋友圈长按识别进入走的是 options.scene而且 scene 里的内容会做 URL 编码必须 decode 后才能拿到 inviter。如果你只是验证二维码内容对不对不需要重新生成小程序码把路径丢进草料二维码这类在线生成器就能直接预览。6.2 在 App.onLaunch 里统一解析 scene 与 query在 app.js 里统一处理来源参数最省事也最不容易漏// app.js App({ onLaunch(options) { let raw options.query ? options.query.inviter : ; if (!raw options.scene) { const sceneParams decodeURIComponent(options.scene); const match sceneParams.match(/inviter([^]*)/); raw match ? match[1] : ; } this.globalData.inviter raw; // 把来源 ID 存入缓存供商品页随时取用 if (raw) { wx.setStorageSync(inviter, raw); } } });这段代码兼容了“分享到群聊后扫码进入”和“扫小程序码进入”两种场景。options.query 是页面路径正常携带参数时的入口options.scene 是扫小程序码时微信传给开发者的字段它把所有 query 拼在一个字符串里。拿到 inviter 后商品页在 onLoad 里读缓存即可。从海报保存这个细节说起每次生成海报后记得把 posterUrl 临时缓存 5 分钟很多用户会先预览、再返回聊天页、再回来保存不缓存就要重新绘制一遍。我把这套流程固化成了一个习惯功能代码提交前先强制扫一遍黑白两色二维码确认两个机型扫码成功再放到提测队列里。从那以后我每次做海报类功能都会强制走这样的回归出一张不含任何动态数据的静态海报再出一张含邀请码和头像的动态海报两张图在真机上分别扫码和保存全部通过才提测。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

ASP.NET+SQL Server构建内部项目管理系统:从Gridview到部署避坑
ASP.NET+SQL Server构建内部项目管理系统:从Gridview到部署避坑

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

高频变压器三明治绕法:漏感控制与EMI优化实战指南
高频变压器三明治绕法:漏感控制与EMI优化实战指南

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

从编译到烧录:ARM MCU工程搭建与调试全流程解析
从编译到烧录:ARM MCU工程搭建与调试全流程解析

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

编带烧录机维护与故障排查实战指南
编带烧录机维护与故障排查实战指南

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

GPS模块调试避坑指南:UBX与NMEA协议选型及u-center配置实战
GPS模块调试避坑指南:UBX与NMEA协议选型及u-center配置实战

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

第二十二:Cursor 模型设置里接 TaoToken 的 config.toml 骨架
第二十二:Cursor 模型设置里接 TaoToken 的 config.toml 骨架

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

EBus7.0上位机软件:基于Qt架构的工业总线调试实战解析
EBus7.0上位机软件:基于Qt架构的工业总线调试实战解析

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

恶意加密包与静默上传暗门:313MB样本的分析与防御
恶意加密包与静默上传暗门:313MB样本的分析与防御

我上周刚处理了一个很有代表性的样本,名字有点唬人,实际上就是一个 313MB 的加密包。如果只看文件体积,恐怕很多同事都会以为这是正儿八经的资源包,但真正让它露出马脚的,是在隔离环境里跑完之后的“静默上传”行为。这… · 2026/9/25 1:48:48

STM32+IRF640自制300mA可调恒流源:原理、计算与实测全解析
STM32+IRF640自制300mA可调恒流源:原理、计算与实测全解析

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

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

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

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

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

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

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

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码