[{"data":1,"prerenderedAt":849},["ShallowReactive",2],{"page-/post/zzao/copy-md-styles-to-wx":3,"surrounding-page":840},{"id":4,"title":5,"author":6,"body":7,"date":825,"description":826,"extension":827,"group":6,"lastmod":828,"meta":829,"navigation":830,"path":831,"rawbody":832,"seo":833,"showTitle":834,"stem":835,"tags":836,"versions":6,"__hash__":839},"content/post/zzao/copy-md-styles-to-wx.md","基于原生 DOM 实现Markdown复制样式到公众号",null,{"type":8,"value":9,"toc":819},"minimark",[10,19,22,34,41,44,51,58,61,65,68,71,78,81,88,91,96,103,109,116,123,215,221,227,246,253,259,262,269,272,286,289,302,309,322,325,331,348,351,369,372,378,381,384,533,539,667,674,683,689,692,695,702,705,718,724,727,730,733,792,800,803,806,809,812,815],[11,12,13,14,18],"p",{},"很多人选择了",[15,16,17],"code",{},"markdown","语法来写文章，因为它可以在纯文字的基础上添加少量的语法，就能渲染出更美观的样式，并且可以自己扩展样式。",[11,20,21],{},"更重要的是它的生态十分丰富，基本上所有平台、框架都支持markdown语法，再加上开源插件的协助，可以满足绝大部分展示需求。",[11,23,24,25,29,30,33],{},"以前我写文章的流程是这样的：先在本地的",[26,27,28],"strong",{},"某个写作App","上把文章写完，确认没问题后，再打开",[26,31,32],{},"某个自己还算信赖的markdown转换网站/App","，一键复制内容，然后打开目标平台编辑器，粘贴进去，看看样式有没有问题，然后点击预览、发布。",[11,35,36,37,40],{},"直接有几次半夜写完了文章想发布的时候，",[26,38,39],{},"markdown转换失败了","，要么是代码丢了高亮，要么是有些样式错乱了。而我别无他法，只能再找另一个网站去转换，但是经常写文章的话，某些样式可能是自己自定义的，某些主题别的网站可能还没集成，非常无奈。",[11,42,43],{},"我一直觉得这是个问题，但碍于我也没解决，讲出来顶多是大家一起吐槽一下，所以就不了了之。",[11,45,46,47,50],{},"直到前一阵，我又又又开始自建博客站，这次没有找现成的模板，因为我想",[26,48,49],{},"基于本地文件直接生成博客文章","，之前搬家搬的真的累，这次要一举让我写文章这个工作流达到完美。",[11,52,53,54,57],{},"基于本地文件生成文章，我用",[15,55,56],{},"nuxt/content","实现了，于是问题来到「如何一键复制到公众号」上。",[11,59,60],{},"要实现这个功能，有几点需要明确，思路才能理顺。",[62,63,64],"h2",{"id":64},"复制的内容是什么",[11,66,67],{},"我要想保持各平台样式一致，肯定是从自己博客上复制内容+样式，然后到其他平台上。",[11,69,70],{},"所以，那些markdown转换的网站，他们复制的是什么内容？为什么可以带有样式？",[11,72,73,74,77],{},"打开以前用过的网站，写一段markdown，然后点复制。粘贴进",[15,75,76],{},"VSCODE","看看到底是啥。",[11,79,80],{},"如果能正常粘贴出来的话，你可能会看到如下内容（以下是我在一个mdx编辑器内粘贴出来的内容）：",[11,82,83],{},[84,85],"img",{"alt":86,"src":87},"","https://img.zzstudio.cn/1-img-20241104141167.png",[11,89,90],{},"而这是它的渲染结果是这样：",[11,92,93],{},[84,94],{"alt":86,"src":95},"https://img.zzstudio.cn/1-img-20241103171168.png",[11,97,98,99,102],{},"所以粘贴进其他编辑器的内容是什么？ ",[26,100,101],{},"html","！",[11,104,105,106,102],{},"更准确的说是",[26,107,108],{},"具有内联样式的html",[11,110,111,112,115],{},"那为什么可能你复制完再去粘贴，可能看不到这个",[26,113,114],{},"html内容","？",[11,117,118,119,122],{},"这是因为",[15,120,121],{},"navigator.clipboard"," 同时设置了两种类型的文本，你在支持富文本的编辑器内粘贴，就会使用html内容，你在不支持富文本的文件内粘贴，就会只保留文本。",[124,125,129],"pre",{"className":126,"code":127,"language":128,"meta":86,"style":86},"language-typescript shiki shiki-themes github-light","const htmlData = new Blob([yourHTML], { type: 'text/html' })\nconst textData = new Blob([yourText], { type: 'text/plain' })\nconst clipboardItem = new ClipboardItem({ 'text/html': htmlData, 'text/plain': textData})\n","typescript",[15,130,131,165,187],{"__ignoreMap":86},[132,133,136,140,144,147,150,154,158,162],"span",{"class":134,"line":135},"line",1,[132,137,139],{"class":138},"sD7c4","const",[132,141,143],{"class":142},"sYu0t"," htmlData",[132,145,146],{"class":138}," =",[132,148,149],{"class":138}," new",[132,151,153],{"class":152},"s7eDp"," Blob",[132,155,157],{"class":156},"sgsFI","([yourHTML], { type: ",[132,159,161],{"class":160},"sYBdl","'text/html'",[132,163,164],{"class":156}," })\n",[132,166,168,170,173,175,177,179,182,185],{"class":134,"line":167},2,[132,169,139],{"class":138},[132,171,172],{"class":142}," textData",[132,174,146],{"class":138},[132,176,149],{"class":138},[132,178,153],{"class":152},[132,180,181],{"class":156},"([yourText], { type: ",[132,183,184],{"class":160},"'text/plain'",[132,186,164],{"class":156},[132,188,190,192,195,197,199,202,205,207,210,212],{"class":134,"line":189},3,[132,191,139],{"class":138},[132,193,194],{"class":142}," clipboardItem",[132,196,146],{"class":138},[132,198,149],{"class":138},[132,200,201],{"class":152}," ClipboardItem",[132,203,204],{"class":156},"({ ",[132,206,161],{"class":160},[132,208,209],{"class":156},": htmlData, ",[132,211,184],{"class":160},[132,213,214],{"class":156},": textData})\n",[11,216,217,218],{},"以上都是",[26,219,220],{},"浏览器原生对象",[11,222,223,224,226],{},"然后使用",[15,225,121],{},"写入到粘贴板：",[124,228,230],{"className":126,"code":229,"language":128,"meta":86,"style":86},"await navigator.clipboard.write([clipboardItem])\n",[15,231,232],{"__ignoreMap":86},[132,233,234,237,240,243],{"class":134,"line":135},[132,235,236],{"class":138},"await",[132,238,239],{"class":156}," navigator.clipboard.",[132,241,242],{"class":152},"write",[132,244,245],{"class":156},"([clipboardItem])\n",[11,247,248,249,252],{},"从使用技术手段实现的角度：",[15,250,251],{},"navigator.clipboard.write","可以把带有内联样式的html代码写入到粘贴板，目标编辑器就可以粘贴出带有样式的内容。",[11,254,255,256],{},"所以问题变成了：",[26,257,258],{},"怎么把自己的博客里的文章转换为带有内联样式的html代码",[62,260,261],{"id":261},"如何获取到文章的样式",[11,263,264,265,268],{},"获取文章样式这个操作，让任何一个前端都能写出来，但这里明显不能用简单的",[15,266,267],{},"style属性","。",[11,270,271],{},"因为影响样式的css，可能是内联样式，也可能是通过外部引入的css。",[11,273,274,275,278,279,282,283,268],{},"所以这里我用了 ",[15,276,277],{},"getComputedStyle"," 这个方法，传入",[15,280,281],{},"DOM","，它可以获取到",[26,284,285],{},"DOM元素最终的样式",[11,287,288],{},"什么意思？ 意思就是仅靠这一个方法就可能实现这个功能！",[11,290,291,292,294,295,298,299,301],{},"所以方案就很明确了：用",[15,293,277],{},"获取到样式，转换为",[15,296,297],{},"style=\"xxx\""," 这样的内联样式，插入到原有的",[15,300,101],{},"中。",[11,303,304,305,308],{},"这一步转换有没有插件？有，我搜罗了几个开源项目，基本都是使用了 ",[15,306,307],{},"juice"," 这个插件。",[11,310,311,312,314,315,318,319,321],{},"它可以让你传入",[15,313,101],{},"，在传入",[15,316,317],{},"css","，然后帮你拼接成",[15,320,108],{},"。所以它适合在你知道了自己的css在哪里的场景，也就是一个在线的markdown编辑器里。",[11,323,324],{},"我要是解决的就是脱离在线编辑器，所以肯定是不能走这个路子。",[11,326,327,328,330],{},"虽然",[15,329,277],{},"获取到样式有几百个至多，而又有那么多的元素，直接原封不动的拼接，内容肯定是太多太大了。",[11,332,333,334,337,338,337,341,337,344,347],{},"但好在用markdown写文章的人，一般追求的都是",[26,335,336],{},"简洁","、",[26,339,340],{},"大气",[26,342,343],{},"低调",[26,345,346],{},"极客","，对吧？彦祖。",[11,349,350],{},"所以平时用到的markdow语法，其实也是有限的几种。",[11,352,353,354,337,356,337,359,337,361,337,364,337,366,368],{},"而渲染后的文章，通常也只有这几个元素：",[15,355,11],{},[15,357,358],{},"a",[15,360,132],{},[15,362,363],{},"blockquote",[15,365,26],{},[15,367,15],{},"等。",[11,370,371],{},"它们分别对应了：段落、超链接、代码块、标注、加粗等。",[11,373,374,375,377],{},"所以只需要把影响样式的样式属性限制一下，从",[15,376,277],{},"里只取这几个！",[62,379,380],{"id":380},"通过调试得到样式的全部覆盖",[11,382,383],{},"先思考一下那些样式影响了文章的样式，列出来：",[124,385,387],{"className":126,"code":386,"language":128,"meta":86,"style":86},"// 对元素有影响的属性\nexport const EffectCssAttrs = [\n  // 'fontFamily',\n  'fontSize',\n  'fontWeight',\n  'color',\n  'textAlign',\n  'lineHeight',\n  'whiteSpace',\n  'textSizeAdjust',\n  'overflowX',\n  'padding',\n  'paddingTop',\n  'paddingBottom',\n  'paddingLeft',\n  'paddingRight',\n  ...\n]\n",[15,388,389,395,411,416,425,433,441,449,457,465,473,481,489,497,505,513,521,527],{"__ignoreMap":86},[132,390,391],{"class":134,"line":135},[132,392,394],{"class":393},"sAwPA","// 对元素有影响的属性\n",[132,396,397,400,403,406,408],{"class":134,"line":167},[132,398,399],{"class":138},"export",[132,401,402],{"class":138}," const",[132,404,405],{"class":142}," EffectCssAttrs",[132,407,146],{"class":138},[132,409,410],{"class":156}," [\n",[132,412,413],{"class":134,"line":189},[132,414,415],{"class":393},"  // 'fontFamily',\n",[132,417,419,422],{"class":134,"line":418},4,[132,420,421],{"class":160},"  'fontSize'",[132,423,424],{"class":156},",\n",[132,426,428,431],{"class":134,"line":427},5,[132,429,430],{"class":160},"  'fontWeight'",[132,432,424],{"class":156},[132,434,436,439],{"class":134,"line":435},6,[132,437,438],{"class":160},"  'color'",[132,440,424],{"class":156},[132,442,444,447],{"class":134,"line":443},7,[132,445,446],{"class":160},"  'textAlign'",[132,448,424],{"class":156},[132,450,452,455],{"class":134,"line":451},8,[132,453,454],{"class":160},"  'lineHeight'",[132,456,424],{"class":156},[132,458,460,463],{"class":134,"line":459},9,[132,461,462],{"class":160},"  'whiteSpace'",[132,464,424],{"class":156},[132,466,468,471],{"class":134,"line":467},10,[132,469,470],{"class":160},"  'textSizeAdjust'",[132,472,424],{"class":156},[132,474,476,479],{"class":134,"line":475},11,[132,477,478],{"class":160},"  'overflowX'",[132,480,424],{"class":156},[132,482,484,487],{"class":134,"line":483},12,[132,485,486],{"class":160},"  'padding'",[132,488,424],{"class":156},[132,490,492,495],{"class":134,"line":491},13,[132,493,494],{"class":160},"  'paddingTop'",[132,496,424],{"class":156},[132,498,500,503],{"class":134,"line":499},14,[132,501,502],{"class":160},"  'paddingBottom'",[132,504,424],{"class":156},[132,506,508,511],{"class":134,"line":507},15,[132,509,510],{"class":160},"  'paddingLeft'",[132,512,424],{"class":156},[132,514,516,519],{"class":134,"line":515},16,[132,517,518],{"class":160},"  'paddingRight'",[132,520,424],{"class":156},[132,522,524],{"class":134,"line":523},17,[132,525,526],{"class":138},"  ...\n",[132,528,530],{"class":134,"line":529},18,[132,531,532],{"class":156},"]\n",[11,534,535,536,538],{},"然后在通过 ",[15,537,277],{}," 获取到dom的全部样式时，使用此列表过滤：",[124,540,542],{"className":126,"code":541,"language":128,"meta":86,"style":86},"    const computedCssStyles = getComputedStyle(childDom, null)\n    // console.log(`computedCssStyles`, computedCssStyles)\n    const _effectCssAttrs = pointCssAttrs.length > 0 ? pointCssAttrs : EffectCssAttrs\n    _effectCssAttrs.forEach( cssAttr => {\n          const value = computedCssStyles[cssAttr]\n          if (value) {\n            curCssStyles[cssAttr] = value\n          }\n        })\n",[15,543,544,566,571,604,625,638,646,657,662],{"__ignoreMap":86},[132,545,546,549,552,554,557,560,563],{"class":134,"line":135},[132,547,548],{"class":138},"    const",[132,550,551],{"class":142}," computedCssStyles",[132,553,146],{"class":138},[132,555,556],{"class":152}," getComputedStyle",[132,558,559],{"class":156},"(childDom, ",[132,561,562],{"class":142},"null",[132,564,565],{"class":156},")\n",[132,567,568],{"class":134,"line":167},[132,569,570],{"class":393},"    // console.log(`computedCssStyles`, computedCssStyles)\n",[132,572,573,575,578,580,583,586,589,592,595,598,601],{"class":134,"line":189},[132,574,548],{"class":138},[132,576,577],{"class":142}," _effectCssAttrs",[132,579,146],{"class":138},[132,581,582],{"class":156}," pointCssAttrs.",[132,584,585],{"class":142},"length",[132,587,588],{"class":138}," >",[132,590,591],{"class":142}," 0",[132,593,594],{"class":138}," ?",[132,596,597],{"class":156}," pointCssAttrs ",[132,599,600],{"class":138},":",[132,602,603],{"class":156}," EffectCssAttrs\n",[132,605,606,609,612,615,619,622],{"class":134,"line":418},[132,607,608],{"class":156},"    _effectCssAttrs.",[132,610,611],{"class":152},"forEach",[132,613,614],{"class":156},"( ",[132,616,618],{"class":617},"sqxcx","cssAttr",[132,620,621],{"class":138}," =>",[132,623,624],{"class":156}," {\n",[132,626,627,630,633,635],{"class":134,"line":427},[132,628,629],{"class":138},"          const",[132,631,632],{"class":142}," value",[132,634,146],{"class":138},[132,636,637],{"class":156}," computedCssStyles[cssAttr]\n",[132,639,640,643],{"class":134,"line":435},[132,641,642],{"class":138},"          if",[132,644,645],{"class":156}," (value) {\n",[132,647,648,651,654],{"class":134,"line":443},[132,649,650],{"class":156},"            curCssStyles[cssAttr] ",[132,652,653],{"class":138},"=",[132,655,656],{"class":156}," value\n",[132,658,659],{"class":134,"line":451},[132,660,661],{"class":156},"          }\n",[132,663,664],{"class":134,"line":459},[132,665,666],{"class":156},"        })\n",[11,668,669,670,673],{},"这样，我们只需要拿到文章最外层的",[15,671,672],{},"Dom","，循环所有子元素，获取到其有效样式，组合成内联样式",[11,675,676,677,679,680,268],{},"然后再把全部",[15,678,672],{},"整合起来，就得到了一个",[26,681,682],{},"带有样式的html字符串",[11,684,685,686,688],{},"然后再衔接上一小节的",[15,687,121],{}," Api，就已经实现了功能。",[11,690,691],{},"但是测试下来，还是有很多需要填补和优化的地方。",[11,693,694],{},"比如影响样式的属性列举的不太全，导致有些渲染的不对劲；",[11,696,697,698,701],{},"比如",[15,699,700],{},"fontFamily","这个明显不需要每个元素都获取一遍的样式需要单独处理；",[11,703,704],{},"比如文章太长时，元素太多，复制出来的内容太大，也许精简一下也能得到相同的效果；",[11,706,707,708,710,711,713,714,717],{},"比如代码块 ",[15,709,124],{}," 元素内，每个",[15,712,132],{},"其实只需要",[15,715,716],{},"color","；",[11,719,720,721,723],{},"比如博客自定义了图片组件用于放大查看，其他展示平台只需要",[15,722,84],{},"单个标签等等类似的问题。",[11,725,726],{},"比如在A平台有效，在B平台有些样式不支持，需要单独处理。",[11,728,729],{},"这些问题列出来看着有点多，但基本都是先把主要功能打通后，逐个解决的，问题不大。",[11,731,732],{},"最后再来理一遍思路：",[734,735,736,740,769,784,789],"ul",{},[737,738,739],"li",{},"拿到文章最外层的元素，循环处理",[737,741,742,743,746,747],{},"封装一个整合单个元素的",[15,744,745],{},"递归函数（getDomCssStyle)","，放在循环内，获取到处理后的带有内联样式的html字符串\n",[734,748,749,752,757,760,763,766],{},[737,750,751],{},"处理各种特殊情况：无dom、忽略某些nodeType、忽略某些无用的标签（tagName）、忽略某些无用的class（classList）",[737,753,754,755],{},"特殊处理某些组件，如图片 ",[15,756,84],{},[737,758,759],{},"设置缓存，优化代码。（针对文章又长又复杂时）",[737,761,762],{},"深度优先，有子元素时，先去递归组装好全部子元素",[737,764,765],{},"组装最终的dom字符串，return",[737,767,768],{},"优化：抽离函数、常量等",[737,770,771,772,775,776,779,780,783],{},"使用 ",[15,773,774],{},"ClipboardItem"," （两种类型 ",[15,777,778],{},"text/plain"," 、",[15,781,782],{},"text/html"," ）创建实例",[737,785,771,786,788],{},[15,787,251],{}," 实例",[737,790,791],{},"粘贴进其他编辑器",[11,793,794,795,337,798,268],{},"以上全部语法均基于",[26,796,797],{},"原生DOM",[26,799,220],{},[62,801,802],{"id":802},"结语",[11,804,805],{},"以上就是我为了脱离第三方markdown编辑网站而做出的一个小小功能，没有依赖任何第三方插件，目前已经应用在了我的博客站上，用来往公众号同步。但我的博客站还没搞完，所以先不贴出来了。",[11,807,808],{},"功能比较简单，相信大部分人都能实现，但我就是没搜到有类似的插件。大家都是画地为牢，做了一个个功能完全一样的markdown编辑网站...",[11,810,811],{},"幸好我解决了这个问题，再也不用发愁啦！",[11,813,814],{},"对自建博客或是此插件感兴趣，欢迎关注～",[816,817,818],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":86,"searchDepth":167,"depth":167,"links":820},[821,822,823,824],{"id":64,"depth":167,"text":64},{"id":261,"depth":167,"text":261},{"id":380,"depth":167,"text":380},{"id":802,"depth":167,"text":802},"2024-11-03T00:00:00.000Z","很多人选择了markdown语法来写文章，因为它可以在纯文字的基础上添加少量的语法，就能渲染出更美观的样式，并且可以自己扩展样式。","md","2025-02-12T00:00:00.000Z",{},true,"/post/zzao/copy-md-styles-to-wx","---\ntitle: 基于原生 DOM 实现Markdown复制样式到公众号\ndate: 2024-11-03\nlastmod: 2025-02-12\ntags: [\"博客\", \"Markdown\"]\nshowTitle: 告别在线Markdown格式转换！基于原生 DOM 实现Markdown复制样式到公众号\n---\n很多人选择了`markdown`语法来写文章，因为它可以在纯文字的基础上添加少量的语法，就能渲染出更美观的样式，并且可以自己扩展样式。\n\n更重要的是它的生态十分丰富，基本上所有平台、框架都支持markdown语法，再加上开源插件的协助，可以满足绝大部分展示需求。\n\n以前我写文章的流程是这样的：先在本地的**某个写作App**上把文章写完，确认没问题后，再打开**某个自己还算信赖的markdown转换网站/App**，一键复制内容，然后打开目标平台编辑器，粘贴进去，看看样式有没有问题，然后点击预览、发布。\n\n直接有几次半夜写完了文章想发布的时候，**markdown转换失败了**，要么是代码丢了高亮，要么是有些样式错乱了。而我别无他法，只能再找另一个网站去转换，但是经常写文章的话，某些样式可能是自己自定义的，某些主题别的网站可能还没集成，非常无奈。\n\n我一直觉得这是个问题，但碍于我也没解决，讲出来顶多是大家一起吐槽一下，所以就不了了之。\n\n直到前一阵，我又又又开始自建博客站，这次没有找现成的模板，因为我想**基于本地文件直接生成博客文章**，之前搬家搬的真的累，这次要一举让我写文章这个工作流达到完美。\n\n基于本地文件生成文章，我用`nuxt/content`实现了，于是问题来到「如何一键复制到公众号」上。\n\n要实现这个功能，有几点需要明确，思路才能理顺。\n\n## 复制的内容是什么\n\n我要想保持各平台样式一致，肯定是从自己博客上复制内容+样式，然后到其他平台上。\n\n所以，那些markdown转换的网站，他们复制的是什么内容？为什么可以带有样式？\n\n打开以前用过的网站，写一段markdown，然后点复制。粘贴进`VSCODE`看看到底是啥。\n\n如果能正常粘贴出来的话，你可能会看到如下内容（以下是我在一个mdx编辑器内粘贴出来的内容）：\n\n![](https://img.zzstudio.cn/1-img-20241104141167.png)\n\n而这是它的渲染结果是这样：\n\n![](https://img.zzstudio.cn/1-img-20241103171168.png)\n\n所以粘贴进其他编辑器的内容是什么？ **html**！\n\n更准确的说是**具有内联样式的html**！\n\n那为什么可能你复制完再去粘贴，可能看不到这个**html内容**？\n\n这是因为`navigator.clipboard` 同时设置了两种类型的文本，你在支持富文本的编辑器内粘贴，就会使用html内容，你在不支持富文本的文件内粘贴，就会只保留文本。\n\n```typescript\nconst htmlData = new Blob([yourHTML], { type: 'text/html' })\nconst textData = new Blob([yourText], { type: 'text/plain' })\nconst clipboardItem = new ClipboardItem({ 'text/html': htmlData, 'text/plain': textData})\n```\n\n以上都是**浏览器原生对象**\n\n然后使用`navigator.clipboard`写入到粘贴板：\n\n```typescript\nawait navigator.clipboard.write([clipboardItem])\n```\n\n从使用技术手段实现的角度：`navigator.clipboard.write`可以把带有内联样式的html代码写入到粘贴板，目标编辑器就可以粘贴出带有样式的内容。\n\n所以问题变成了：**怎么把自己的博客里的文章转换为带有内联样式的html代码**\n\n## 如何获取到文章的样式\n\n获取文章样式这个操作，让任何一个前端都能写出来，但这里明显不能用简单的`style属性`。\n\n因为影响样式的css，可能是内联样式，也可能是通过外部引入的css。\n\n所以这里我用了 `getComputedStyle` 这个方法，传入`DOM`，它可以获取到**DOM元素最终的样式**。\n\n什么意思？ 意思就是仅靠这一个方法就可能实现这个功能！\n\n所以方案就很明确了：用`getComputedStyle`获取到样式，转换为`style=\"xxx\"` 这样的内联样式，插入到原有的`html`中。\n\n这一步转换有没有插件？有，我搜罗了几个开源项目，基本都是使用了 `juice` 这个插件。\n\n它可以让你传入`html`，在传入`css`，然后帮你拼接成`具有内联样式的html`。所以它适合在你知道了自己的css在哪里的场景，也就是一个在线的markdown编辑器里。\n\n我要是解决的就是脱离在线编辑器，所以肯定是不能走这个路子。\n\n虽然`getComputedStyle`获取到样式有几百个至多，而又有那么多的元素，直接原封不动的拼接，内容肯定是太多太大了。\n\n但好在用markdown写文章的人，一般追求的都是**简洁**、**大气**、**低调**、**极客**，对吧？彦祖。\n\n所以平时用到的markdow语法，其实也是有限的几种。\n\n而渲染后的文章，通常也只有这几个元素：`p`、`a`、`span`、`blockquote`、`strong`、`code`等。\n\n它们分别对应了：段落、超链接、代码块、标注、加粗等。\n\n所以只需要把影响样式的样式属性限制一下，从`getComputedStyle`里只取这几个！\n\n## 通过调试得到样式的全部覆盖\n\n先思考一下那些样式影响了文章的样式，列出来：\n\n```typescript\n// 对元素有影响的属性\nexport const EffectCssAttrs = [\n  // 'fontFamily',\n  'fontSize',\n  'fontWeight',\n  'color',\n  'textAlign',\n  'lineHeight',\n  'whiteSpace',\n  'textSizeAdjust',\n  'overflowX',\n  'padding',\n  'paddingTop',\n  'paddingBottom',\n  'paddingLeft',\n  'paddingRight',\n  ...\n]\n```\n\n然后在通过 `getComputedStyle` 获取到dom的全部样式时，使用此列表过滤：\n\n```typescript\n\tconst computedCssStyles = getComputedStyle(childDom, null)\n    // console.log(`computedCssStyles`, computedCssStyles)\n    const _effectCssAttrs = pointCssAttrs.length > 0 ? pointCssAttrs : EffectCssAttrs\n    _effectCssAttrs.forEach( cssAttr => {\n          const value = computedCssStyles[cssAttr]\n          if (value) {\n            curCssStyles[cssAttr] = value\n          }\n        })\n```\n\n这样，我们只需要拿到文章最外层的`Dom`，循环所有子元素，获取到其有效样式，组合成内联样式\n\n然后再把全部`Dom`整合起来，就得到了一个**带有样式的html字符串**。\n\n然后再衔接上一小节的`navigator.clipboard` Api，就已经实现了功能。\n\n但是测试下来，还是有很多需要填补和优化的地方。\n\n比如影响样式的属性列举的不太全，导致有些渲染的不对劲；\n\n比如`fontFamily`这个明显不需要每个元素都获取一遍的样式需要单独处理；\n\n比如文章太长时，元素太多，复制出来的内容太大，也许精简一下也能得到相同的效果；\n\n比如代码块 `pre` 元素内，每个`span`其实只需要`color`；\n\n比如博客自定义了图片组件用于放大查看，其他展示平台只需要`img`单个标签等等类似的问题。\n\n比如在A平台有效，在B平台有些样式不支持，需要单独处理。\n\n这些问题列出来看着有点多，但基本都是先把主要功能打通后，逐个解决的，问题不大。\n\n最后再来理一遍思路：\n\n- 拿到文章最外层的元素，循环处理\n- 封装一个整合单个元素的`递归函数（getDomCssStyle)`，放在循环内，获取到处理后的带有内联样式的html字符串\n\t- 处理各种特殊情况：无dom、忽略某些nodeType、忽略某些无用的标签（tagName）、忽略某些无用的class（classList）\n\t- 特殊处理某些组件，如图片 `img`\n\t- 设置缓存，优化代码。（针对文章又长又复杂时）\n\t- 深度优先，有子元素时，先去递归组装好全部子元素\n\t- 组装最终的dom字符串，return\n\t- 优化：抽离函数、常量等\n- 使用 `ClipboardItem` （两种类型 `text/plain` 、`text/html` ）创建实例\n- 使用 `navigator.clipboard.write` 实例\n- 粘贴进其他编辑器\n\n以上全部语法均基于**原生DOM**、**浏览器原生对象**。\n\n## 结语\n\n以上就是我为了脱离第三方markdown编辑网站而做出的一个小小功能，没有依赖任何第三方插件，目前已经应用在了我的博客站上，用来往公众号同步。但我的博客站还没搞完，所以先不贴出来了。\n\n功能比较简单，相信大部分人都能实现，但我就是没搜到有类似的插件。大家都是画地为牢，做了一个个功能完全一样的markdown编辑网站...\n\n幸好我解决了这个问题，再也不用发愁啦！\n\n对自建博客或是此插件感兴趣，欢迎关注～",{"title":5,"description":826},"告别在线Markdown格式转换！基于原生 DOM 实现Markdown复制样式到公众号","post/zzao/copy-md-styles-to-wx",[837,838],"博客","Markdown","eCNpwZalIS3i6D8_s87A3MZIQoHRpp4dfD_U8BNdlhQ",[841,845],{"title":842,"path":843,"stem":844},"OpenClaw 安装入门（Windows）","/post/zzao/openclaw/openclaw-install-windows","post/zzao/openclaw/openclaw-install-windows",{"title":846,"path":847,"stem":848},"假设你是AI，你的Skill应该是什么样的","/post/zzao/ai-skill-structure","post/zzao/ai-skill-structure",1779005086689]