[{"data":1,"prerenderedAt":5295},["ShallowReactive",2],{"navigation":3,"posts-undefined-前端-0-999":20},[4,8,12,16],{"title":5,"path":6,"stem":7},"首页","\u002F","00.index",{"title":9,"path":10,"stem":11},"文章","\u002Fposts","01.posts",{"title":13,"path":14,"stem":15},"动态","\u002Fmoments","02.moments",{"title":17,"path":18,"stem":19},"关于","\u002Fabout","09.about",[21,99,1086,2658,4130,4745,4869,4952,5259],{"id":22,"title":23,"body":24,"class":79,"cover":80,"coverSize":79,"date":81,"description":74,"draft":82,"extension":83,"hideComments":82,"location":79,"meta":84,"navigation":85,"path":86,"readingTime":87,"seo":92,"sitemap":93,"stem":94,"tags":95,"time":79,"weather":79,"__hash__":98},"posts\u002Fposts\u002F2024\u002F20240529.webvolve-series-events-speech.md","W3C 技术分享 —— 《前端在数字人创作工具中的应用实践》",{"type":25,"value":26,"toc":73},"minimark",[27,31,43,46,49],[28,29,30],"h2",{"id":30},"会议背景",[32,33,34,35,42],"p",{},"「",[36,37,41],"a",{"href":38,"rel":39},"https:\u002F\u002Fwww.w3.org\u002F2024\u002F01\u002Fwebevolve-series-events\u002Fmedia\u002Fzh.index.html",[40],"nofollow","Web 进化论 - 2024 年度大会","」是由 W3C 中国（北京航空航天大学）发起的 “Web 进化论” 系列专题活动之一。本次活动围绕机器学习、WebGPU 与媒体技术展开，重点聚焦多媒体 Web 技术、媒体传输、机器学习、元宇宙、沉浸式 Web、以及 GPU 在 Web 标准化领域的应用。与会者将在此分享多媒体 Web 技术的最新动态，探讨媒体传输的技术趋势，并深入发掘如何利用机器学习与 GPU 加速技术来推动 Web 标准化进程，进而赋能产业发展。大会旨在征集来自国内产业界的技术用例与标准需求，加强行业间的交流合作，促进前沿技术与标准的融合，推动相关标准的制定与实施。",[28,44,45],{"id":45},"演讲内容",[32,47,48],{},"我分享的主题是《前端在数字人创作工具中的应用实践》。",[50,51,52],"blockquote",{},[32,53,54,55,60,61,66,67,72],{},"摘要：邓斌（哔哩哔哩资深工程师）展示了基于音色克隆和数字分身技术的“必剪 Studio”智能创作平台，介绍了绿幕抠像的原理、流程、技术挑战和应对方案，以及音视频合成、音频波形可视化、音频在线转码、SSML 可视化编辑器等应用实践（参见",[36,56,59],{"href":57,"rel":58},"https:\u002F\u002Fwww.w3.org\u002F2024\u002F01\u002Fwebevolve-series-events\u002Fmedia\u002Fslides\u002Fdeng-bin.pdf",[40],"演示文稿","、现场视频：",[36,62,65],{"href":63,"rel":64},"https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1yT421a7Eb\u002F",[40],"B 站","、",[36,68,71],{"href":69,"rel":70},"https:\u002F\u002Fyoutu.be\u002FDbGPi54nIv4?si=Akb1pnWDyF-fjniL",[40],"YouTube","）。",{"title":74,"searchDepth":75,"depth":75,"links":76},"",2,[77,78],{"id":30,"depth":75,"text":30},{"id":45,"depth":75,"text":45},null,"jpg","2024-05-29",false,"md",{},true,"\u002Fposts\u002F2024\u002Fwebvolve-series-events-speech",{"text":88,"minutes":89,"time":90,"words":91},"2 min read",1.77,106200,354,{"title":23,"description":74},{"loc":86},"posts\u002F2024\u002F20240529.webvolve-series-events-speech",[96,97],"演讲","前端","0CqE6vR-IY4OzpQ9NDobd33_ILUCcb-_I4c5ufzB9DU",{"id":100,"title":101,"body":102,"class":79,"cover":80,"coverSize":79,"date":1071,"description":74,"draft":82,"extension":83,"hideComments":82,"location":79,"meta":1072,"navigation":85,"path":1073,"readingTime":1074,"seo":1079,"sitemap":1080,"stem":1081,"tags":1082,"time":79,"weather":79,"__hash__":1085},"posts\u002Fposts\u002F2024\u002F20240522.get-video-rotation-by-mp4box-js.md","MP4Box.js 获取视频旋转信息",{"type":25,"value":103,"toc":1064},[104,109,112,123,127,134,152,156,167,177,182,185,266,283,286,1039,1042,1051,1060],[50,105,106],{},[32,107,108],{},"声明：本文部分内容使用 ChatGPT 生成",[28,110,111],{"id":111},"序言",[32,113,114,115,119,120,122],{},"公司的一个项目中用到 ",[116,117,118],"code",{},"MP4Box.js"," 在上传视频前去解析视频的宽高，并且根据宽高的比例做一些拦截，只允许 16:9 横屏的素材。后来发现一个问题，部分竖屏的素材也被提交上来了。经过研究，发现这类视频可能是由手机拍摄的，带了旋转信息，因此 ",[116,121,118],{}," 中的原始宽高有问题。",[28,124,126],{"id":125},"什么是-mp4boxjs","什么是 MP4Box.js",[32,128,129,133],{},[36,130,118],{"href":131,"rel":132},"https:\u002F\u002Fgithub.com\u002Fgpac\u002Fmp4box.js",[40]," 是一个支持在浏览器中处理 MP4 文件的 JS 库，可以实现获取 MP4 文件的元数据信息、分割文件、提取媒体样本等高级处理能力。",[32,135,136,137,139,140,143,144,147,148,151],{},"通过 ",[116,138,118],{}," 可以从 ",[116,141,142],{},"videoTrack"," 中的 ",[116,145,146],{},"width"," 和 ",[116,149,150],{},"height"," 中获取视频的宽高，对于一般的视频都是 OK 的，但是带了旋转信息，通过 MP4Box 读出的宽高仍是旋转前的宽高，导致在一些场景下的判断会有问题。那么，如何获取到视频的旋转信息呢？",[28,153,155],{"id":154},"mp4boxjs-如何获取旋转信息","MP4Box.js 如何获取旋转信息",[32,157,158,159,162,163,166],{},"在 MP4 和 MOV 文件中，旋转信息通常存储在 ",[116,160,161],{},"tkhd"," (Track Header Box) 或 ",[116,164,165],{},"mvhd"," (Movie Header Box) 中。这个信息会影响视频轨道的显示方式。",[32,168,169,170,172,173,176],{},"Track Header Box (",[116,171,161],{},")：包含一个 ",[116,174,175],{},"matrix"," 的矩阵，描述视频帧的旋转。",[32,178,179,181],{},[116,180,175],{}," 字段中的旋转信息是通过一个 3x3 矩阵来表示的，具体可以参考 ISO\u002FIEC 14496-12 标准。",[32,183,184],{},"具体实现代码：",[186,187,191],"pre",{"className":188,"code":189,"language":190,"meta":74,"style":74},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","Math.atan2(videoTrack.matrix[1], videoTrack.matrix[0]) * (180 \u002F Math.PI)\n","ts",[116,192,193],{"__ignoreMap":74},[194,195,198,202,206,210,213,215,218,222,225,228,231,233,235,238,241,245,248,251,254,257,259,263],"span",{"class":196,"line":197},"line",1,[194,199,201],{"class":200},"su5hD","Math",[194,203,205],{"class":204},"sP7_E",".",[194,207,209],{"class":208},"sGLFI","atan2",[194,211,212],{"class":200},"(videoTrack",[194,214,205],{"class":204},[194,216,217],{"class":200},"matrix[",[194,219,221],{"class":220},"srdBf","1",[194,223,224],{"class":200},"]",[194,226,227],{"class":204},",",[194,229,230],{"class":200}," videoTrack",[194,232,205],{"class":204},[194,234,217],{"class":200},[194,236,237],{"class":220},"0",[194,239,240],{"class":200},"]) ",[194,242,244],{"class":243},"smGrS","*",[194,246,247],{"class":200}," (",[194,249,250],{"class":220},"180",[194,252,253],{"class":243}," \u002F",[194,255,256],{"class":200}," Math",[194,258,205],{"class":204},[194,260,262],{"class":261},"s_hVV","PI",[194,264,265],{"class":200},")\n",[32,267,268,269,272,273,275,276,279,280,282],{},"其中，",[116,270,271],{},"Math.atan2"," 是 JS 中的一个数学函数，用于计算从点 (0, 0) 到点 (x, y) 之间的直线与 x 轴正方向之间的角度，角度的单位为弧度。这个函数能够处理所有的象限，因此可以返回从 -π 到 π 之间的值。",[116,274,271],{}," 函数在计算几何、游戏开发、图形编程以及需要处理极坐标转换等场景中非常有用。与 ",[116,277,278],{},"Math.atan"," 不同，",[116,281,271],{}," 可以处理 (x, y) 都为零的情况，并根据 x 和 y 的符号确定正确的象限。",[28,284,285],{"id":285},"完整实现代码",[186,287,289],{"className":188,"code":288,"language":190,"meta":74,"style":74},"function getVideoMetaInfo(file: File): Promise\u003Cany> {\n  return new Promise((resolve, reject) => {\n    const mp4boxFile = MP4Box.createFile()\n\n    mp4boxFile.onReady = function (info: any) {\n      if (info && info.videoTracks?.length) {\n        const videoTrack = info.videoTracks[0]\n        const result = {\n          duration: videoTrack.duration \u002F videoTrack.timescale,\n          codec: videoTrack.codec,\n          fps: videoTrack.nb_samples \u002F (videoTrack.duration \u002F videoTrack.timescale),\n          width: videoTrack.video.width,\n          height: videoTrack.video.height,\n          rotation: Math.atan2(videoTrack.matrix[1], videoTrack.matrix[0]) * (180 \u002F Math.PI),\n        }\n\n        resolve(result)\n      }\n    }\n\n    mp4boxFile.onError = function (info: any) {\n      console.error('mp4box.js error', info)\n      reject(info)\n    }\n\n    const reader = file.stream().getReader()\n    let offset = 0\n    reader.read().then(function getNextChunk({ done, value }: any) {\n      if (done) {\n        mp4boxFile.flush()\n        return\n      }\n\n      const copy = value.buffer\n      copy.fileStart = offset\n      offset += value.length\n      mp4boxFile.appendBuffer(copy)\n      reader.read().then(getNextChunk)\n    })\n  })\n}\n",[116,290,291,335,366,389,395,425,458,482,494,521,538,575,596,616,676,682,687,700,706,712,717,743,772,784,789,794,822,836,883,897,910,916,921,926,944,960,976,994,1017,1025,1033],{"__ignoreMap":74},[194,292,293,297,300,303,307,310,314,317,319,322,325,329,332],{"class":196,"line":197},[194,294,296],{"class":295},"sbsja","function",[194,298,299],{"class":208}," getVideoMetaInfo",[194,301,302],{"class":204},"(",[194,304,306],{"class":305},"s99_P","file",[194,308,309],{"class":243},":",[194,311,313],{"class":312},"sbgvK"," File",[194,315,316],{"class":204},")",[194,318,309],{"class":243},[194,320,321],{"class":312}," Promise",[194,323,324],{"class":204},"\u003C",[194,326,328],{"class":327},"sZMiF","any",[194,330,331],{"class":204},">",[194,333,334],{"class":204}," {\n",[194,336,337,341,344,346,349,351,354,356,359,361,364],{"class":196,"line":75},[194,338,340],{"class":339},"sVHd0","  return",[194,342,343],{"class":243}," new",[194,345,321],{"class":327},[194,347,302],{"class":348},"skxfh",[194,350,302],{"class":204},[194,352,353],{"class":305},"resolve",[194,355,227],{"class":204},[194,357,358],{"class":305}," reject",[194,360,316],{"class":204},[194,362,363],{"class":295}," =>",[194,365,334],{"class":204},[194,367,369,372,375,378,381,383,386],{"class":196,"line":368},3,[194,370,371],{"class":295},"    const",[194,373,374],{"class":261}," mp4boxFile",[194,376,377],{"class":243}," =",[194,379,380],{"class":200}," MP4Box",[194,382,205],{"class":204},[194,384,385],{"class":208},"createFile",[194,387,388],{"class":348},"()\n",[194,390,392],{"class":196,"line":391},4,[194,393,394],{"emptyLinePlaceholder":85},"\n",[194,396,398,401,403,406,408,411,413,416,418,421,423],{"class":196,"line":397},5,[194,399,400],{"class":200},"    mp4boxFile",[194,402,205],{"class":204},[194,404,405],{"class":208},"onReady",[194,407,377],{"class":243},[194,409,410],{"class":295}," function",[194,412,247],{"class":204},[194,414,415],{"class":305},"info",[194,417,309],{"class":243},[194,419,420],{"class":327}," any",[194,422,316],{"class":204},[194,424,334],{"class":204},[194,426,428,431,433,435,438,441,443,446,449,452,455],{"class":196,"line":427},6,[194,429,430],{"class":339},"      if",[194,432,247],{"class":348},[194,434,415],{"class":200},[194,436,437],{"class":243}," &&",[194,439,440],{"class":200}," info",[194,442,205],{"class":204},[194,444,445],{"class":200},"videoTracks",[194,447,448],{"class":204},"?.",[194,450,451],{"class":261},"length",[194,453,454],{"class":348},") ",[194,456,457],{"class":204},"{\n",[194,459,461,464,466,468,470,472,474,477,479],{"class":196,"line":460},7,[194,462,463],{"class":295},"        const",[194,465,230],{"class":261},[194,467,377],{"class":243},[194,469,440],{"class":200},[194,471,205],{"class":204},[194,473,445],{"class":200},[194,475,476],{"class":348},"[",[194,478,237],{"class":220},[194,480,481],{"class":348},"]\n",[194,483,485,487,490,492],{"class":196,"line":484},8,[194,486,463],{"class":295},[194,488,489],{"class":261}," result",[194,491,377],{"class":243},[194,493,334],{"class":204},[194,495,497,500,502,504,506,509,511,513,515,518],{"class":196,"line":496},9,[194,498,499],{"class":348},"          duration",[194,501,309],{"class":204},[194,503,230],{"class":200},[194,505,205],{"class":204},[194,507,508],{"class":200},"duration",[194,510,253],{"class":243},[194,512,230],{"class":200},[194,514,205],{"class":204},[194,516,517],{"class":200},"timescale",[194,519,520],{"class":204},",\n",[194,522,524,527,529,531,533,536],{"class":196,"line":523},10,[194,525,526],{"class":348},"          codec",[194,528,309],{"class":204},[194,530,230],{"class":200},[194,532,205],{"class":204},[194,534,535],{"class":200},"codec",[194,537,520],{"class":204},[194,539,541,544,546,548,550,553,555,557,559,561,563,565,567,569,571,573],{"class":196,"line":540},11,[194,542,543],{"class":348},"          fps",[194,545,309],{"class":204},[194,547,230],{"class":200},[194,549,205],{"class":204},[194,551,552],{"class":200},"nb_samples",[194,554,253],{"class":243},[194,556,247],{"class":348},[194,558,142],{"class":200},[194,560,205],{"class":204},[194,562,508],{"class":200},[194,564,253],{"class":243},[194,566,230],{"class":200},[194,568,205],{"class":204},[194,570,517],{"class":200},[194,572,316],{"class":348},[194,574,520],{"class":204},[194,576,578,581,583,585,587,590,592,594],{"class":196,"line":577},12,[194,579,580],{"class":348},"          width",[194,582,309],{"class":204},[194,584,230],{"class":200},[194,586,205],{"class":204},[194,588,589],{"class":200},"video",[194,591,205],{"class":204},[194,593,146],{"class":200},[194,595,520],{"class":204},[194,597,599,602,604,606,608,610,612,614],{"class":196,"line":598},13,[194,600,601],{"class":348},"          height",[194,603,309],{"class":204},[194,605,230],{"class":200},[194,607,205],{"class":204},[194,609,589],{"class":200},[194,611,205],{"class":204},[194,613,150],{"class":200},[194,615,520],{"class":204},[194,617,619,622,624,626,628,630,632,634,636,638,640,642,644,646,648,650,652,654,656,658,660,662,664,666,668,670,672,674],{"class":196,"line":618},14,[194,620,621],{"class":348},"          rotation",[194,623,309],{"class":204},[194,625,256],{"class":200},[194,627,205],{"class":204},[194,629,209],{"class":208},[194,631,302],{"class":348},[194,633,142],{"class":200},[194,635,205],{"class":204},[194,637,175],{"class":200},[194,639,476],{"class":348},[194,641,221],{"class":220},[194,643,224],{"class":348},[194,645,227],{"class":204},[194,647,230],{"class":200},[194,649,205],{"class":204},[194,651,175],{"class":200},[194,653,476],{"class":348},[194,655,237],{"class":220},[194,657,240],{"class":348},[194,659,244],{"class":243},[194,661,247],{"class":348},[194,663,250],{"class":220},[194,665,253],{"class":243},[194,667,256],{"class":200},[194,669,205],{"class":204},[194,671,262],{"class":261},[194,673,316],{"class":348},[194,675,520],{"class":204},[194,677,679],{"class":196,"line":678},15,[194,680,681],{"class":204},"        }\n",[194,683,685],{"class":196,"line":684},16,[194,686,394],{"emptyLinePlaceholder":85},[194,688,690,693,695,698],{"class":196,"line":689},17,[194,691,692],{"class":208},"        resolve",[194,694,302],{"class":348},[194,696,697],{"class":200},"result",[194,699,265],{"class":348},[194,701,703],{"class":196,"line":702},18,[194,704,705],{"class":204},"      }\n",[194,707,709],{"class":196,"line":708},19,[194,710,711],{"class":204},"    }\n",[194,713,715],{"class":196,"line":714},20,[194,716,394],{"emptyLinePlaceholder":85},[194,718,720,722,724,727,729,731,733,735,737,739,741],{"class":196,"line":719},21,[194,721,400],{"class":200},[194,723,205],{"class":204},[194,725,726],{"class":208},"onError",[194,728,377],{"class":243},[194,730,410],{"class":295},[194,732,247],{"class":204},[194,734,415],{"class":305},[194,736,309],{"class":243},[194,738,420],{"class":327},[194,740,316],{"class":204},[194,742,334],{"class":204},[194,744,746,749,751,754,756,760,764,766,768,770],{"class":196,"line":745},22,[194,747,748],{"class":200},"      console",[194,750,205],{"class":204},[194,752,753],{"class":208},"error",[194,755,302],{"class":348},[194,757,759],{"class":758},"sjJ54","'",[194,761,763],{"class":762},"s_sjI","mp4box.js error",[194,765,759],{"class":758},[194,767,227],{"class":204},[194,769,440],{"class":200},[194,771,265],{"class":348},[194,773,775,778,780,782],{"class":196,"line":774},23,[194,776,777],{"class":208},"      reject",[194,779,302],{"class":348},[194,781,415],{"class":200},[194,783,265],{"class":348},[194,785,787],{"class":196,"line":786},24,[194,788,711],{"class":204},[194,790,792],{"class":196,"line":791},25,[194,793,394],{"emptyLinePlaceholder":85},[194,795,797,799,802,804,807,809,812,815,817,820],{"class":196,"line":796},26,[194,798,371],{"class":295},[194,800,801],{"class":261}," reader",[194,803,377],{"class":243},[194,805,806],{"class":200}," file",[194,808,205],{"class":204},[194,810,811],{"class":208},"stream",[194,813,814],{"class":348},"()",[194,816,205],{"class":204},[194,818,819],{"class":208},"getReader",[194,821,388],{"class":348},[194,823,825,828,831,833],{"class":196,"line":824},27,[194,826,827],{"class":295},"    let",[194,829,830],{"class":200}," offset",[194,832,377],{"class":243},[194,834,835],{"class":220}," 0\n",[194,837,839,842,844,847,849,851,854,856,858,861,864,867,869,872,875,877,879,881],{"class":196,"line":838},28,[194,840,841],{"class":200},"    reader",[194,843,205],{"class":204},[194,845,846],{"class":208},"read",[194,848,814],{"class":348},[194,850,205],{"class":204},[194,852,853],{"class":208},"then",[194,855,302],{"class":348},[194,857,296],{"class":295},[194,859,860],{"class":208}," getNextChunk",[194,862,863],{"class":204},"({",[194,865,866],{"class":305}," done",[194,868,227],{"class":204},[194,870,871],{"class":305}," value",[194,873,874],{"class":204}," }",[194,876,309],{"class":243},[194,878,420],{"class":327},[194,880,316],{"class":204},[194,882,334],{"class":204},[194,884,886,888,890,893,895],{"class":196,"line":885},29,[194,887,430],{"class":339},[194,889,247],{"class":348},[194,891,892],{"class":200},"done",[194,894,454],{"class":348},[194,896,457],{"class":204},[194,898,900,903,905,908],{"class":196,"line":899},30,[194,901,902],{"class":200},"        mp4boxFile",[194,904,205],{"class":204},[194,906,907],{"class":208},"flush",[194,909,388],{"class":348},[194,911,913],{"class":196,"line":912},31,[194,914,915],{"class":339},"        return\n",[194,917,919],{"class":196,"line":918},32,[194,920,705],{"class":204},[194,922,924],{"class":196,"line":923},33,[194,925,394],{"emptyLinePlaceholder":85},[194,927,929,932,935,937,939,941],{"class":196,"line":928},34,[194,930,931],{"class":295},"      const",[194,933,934],{"class":261}," copy",[194,936,377],{"class":243},[194,938,871],{"class":200},[194,940,205],{"class":204},[194,942,943],{"class":200},"buffer\n",[194,945,947,950,952,955,957],{"class":196,"line":946},35,[194,948,949],{"class":200},"      copy",[194,951,205],{"class":204},[194,953,954],{"class":200},"fileStart",[194,956,377],{"class":243},[194,958,959],{"class":200}," offset\n",[194,961,963,966,969,971,973],{"class":196,"line":962},36,[194,964,965],{"class":200},"      offset",[194,967,968],{"class":243}," +=",[194,970,871],{"class":200},[194,972,205],{"class":204},[194,974,975],{"class":261},"length\n",[194,977,979,982,984,987,989,992],{"class":196,"line":978},37,[194,980,981],{"class":200},"      mp4boxFile",[194,983,205],{"class":204},[194,985,986],{"class":208},"appendBuffer",[194,988,302],{"class":348},[194,990,991],{"class":200},"copy",[194,993,265],{"class":348},[194,995,997,1000,1002,1004,1006,1008,1010,1012,1015],{"class":196,"line":996},38,[194,998,999],{"class":200},"      reader",[194,1001,205],{"class":204},[194,1003,846],{"class":208},[194,1005,814],{"class":348},[194,1007,205],{"class":204},[194,1009,853],{"class":208},[194,1011,302],{"class":348},[194,1013,1014],{"class":200},"getNextChunk",[194,1016,265],{"class":348},[194,1018,1020,1023],{"class":196,"line":1019},39,[194,1021,1022],{"class":204},"    }",[194,1024,265],{"class":348},[194,1026,1028,1031],{"class":196,"line":1027},40,[194,1029,1030],{"class":204},"  }",[194,1032,265],{"class":348},[194,1034,1036],{"class":196,"line":1035},41,[194,1037,1038],{"class":204},"}\n",[28,1040,1041],{"id":1041},"后记",[32,1043,1044,1045,1050],{},"在解决这个问题的过程中，我发现另一个强大的 JS 库：",[36,1046,1049],{"href":1047,"rel":1048},"https:\u002F\u002Fgithub.com\u002Fbuzz\u002Fmediainfo.js",[40],"mediainfo.js","，已经帮我们做好了这一切，并且支持解析更多格式的文件。",[32,1052,1053,1054,1059],{},"我基于这个库，做了一个可视化的工具页：",[36,1055,1058],{"href":1056,"rel":1057},"https:\u002F\u002Ftools.yuanfen.net\u002Fmetadata",[40],"媒体文件元数据解析","，方便解析媒体文件的元数据，纯浏览器本地解析，性能优异，并且不需要读完整个文件，读完头就可以了。",[1061,1062,1063],"style",{},"html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":74,"searchDepth":75,"depth":75,"links":1065},[1066,1067,1068,1069,1070],{"id":111,"depth":75,"text":111},{"id":125,"depth":75,"text":126},{"id":154,"depth":75,"text":155},{"id":285,"depth":75,"text":285},{"id":1041,"depth":75,"text":1041},"2024-05-22",{},"\u002Fposts\u002F2024\u002Fget-video-rotation-by-mp4box-js",{"text":1075,"minutes":1076,"time":1077,"words":1078},"4 min read",3.68,220800,736,{"title":101,"description":74},{"loc":1073},"posts\u002F2024\u002F20240522.get-video-rotation-by-mp4box-js",[1083,97,1084],"技术","音视频处理","D0zmDUaMjME7E7unN1UVePE8EFtBxbFmdtpj9iS2Dlc",{"id":1087,"title":1088,"body":1089,"class":79,"cover":79,"coverSize":79,"date":2645,"description":74,"draft":82,"extension":83,"hideComments":82,"location":79,"meta":2646,"navigation":85,"path":2647,"readingTime":2648,"seo":2653,"sitemap":2654,"stem":2655,"tags":2656,"time":79,"weather":79,"__hash__":2657},"posts\u002Fposts\u002F2023\u002F20230719.font2svg-solution.md","Font2svg 特殊字体渲染方案",{"type":25,"value":1090,"toc":2639},[1091,1118,1121,1124,1127,1132,1135,1139,1146,1150,1153,1156,1159,1163,1166,1170,1173,1177,1180,1184,1187,1191,1194,1198,1201,1205,1208,1212,1216,1219,1222,1225,1229,1232,1235,1238,1241,1244,1247,1250,1253,1256,1259,1262,1265,1268,1271,1274,1277,1282,1286,1303,2070,2073,2076,2079,2082,2225,2239,2245,2444,2447,2450,2468,2471,2485,2488,2491,2494,2497,2500,2503,2506,2509,2576,2600,2603,2606,2609,2612,2615,2618,2621,2624,2627,2630,2633,2636],[50,1092,1093,1107,1115],{},[32,1094,1095,1096,66,1101,1106],{},"2023-09-12 发表于 ",[36,1097,1100],{"href":1098,"rel":1099},"https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002FeAlceV6H0JO019m8TozwxA",[40],"哔哩哔哩技术公众号",[36,1102,1105],{"href":1103,"rel":1104},"https:\u002F\u002Fwww.bilibili.com\u002Fread\u002Fcv26464950\u002F",[40],"哔哩哔哩技术专栏"," 等处。",[32,1108,1109,1110],{},"GitHub 开源地址：",[36,1111,1114],{"href":1112,"rel":1113},"https:\u002F\u002Fgithub.com\u002Ffont2svg",[40],"Font2svg",[32,1116,1117],{},"注：开源版方案和文中略有差别，但更通用。API 服务已经完成，前端组件的开源版还没搞完。",[28,1119,1120],{"id":1120},"背景介绍",[32,1122,1123],{},"在 Web 开发中，经常会需要在页面中引入一些特殊字体，这些字体通常不在系统字体库的范围内，并且动辄 4~5MB，甚至有些字体超过 10MB，会影响用户加载体验，尤其在手机端使用移动网络的情况下。",[32,1125,1126],{},"针对不同的业务场景，通常会有以下几种解决方案：",[1128,1129,1131],"h4",{"id":1130},"一使用切图","一、使用切图",[32,1133,1134],{},"当需要显示特殊字体的文案为固定文案时，直接使用切图可能是最常见的一种解决方案，无论是透明的 PNG 切图，还是用矢量的 SVG，都能在便捷度和加载体验上有不错的体验。当然，使用 PNG 的时候需要注意高分辨率屏幕的适配，使用 1 倍图在高分辨率的屏幕下肉眼很容易看出模糊。",[1128,1136,1138],{"id":1137},"二使用原始字体","二、使用原始字体",[32,1140,1141,1142,1145],{},"当需要显示特殊字体的文案是动态文案时，就没办法直接用文字切图来实现了。通常在对用户加载体验没有特别要求的场景下，可直接通过 ",[116,1143,1144],{},"@font-face"," 来直接引入特殊字体的文件进来。缺点也显而易见，正如前文所说，动辄 4~5MB，甚至超过 10MB 的字体文件，会比较影响用户的加载体验，在首屏需要显示的场景下，用户往往会先看到先渲染默认字体，再闪现成特殊字体的情况。",[1128,1147,1149],{"id":1148},"三使用裁剪压缩后的字体","三、使用裁剪\u002F压缩后的字体",[32,1151,1152],{},"字体裁剪技术，可通过省略字体变体（如某些字体会包含多个粗细）、裁剪字体字符集（省略不常用的字符）、通过其他压缩算法（如 woff、woff2）等方式来降低字体体积。当文案内容相对可控的情况下，用该方案可比直接加载原始字体要节省更多体积。但若字符裁剪过多，会导致缺少部分字符，在显示时出现字体不一致的情况；若字符裁剪过少，体积仍会较大。并且通常情况下，裁剪后的字符数也是远大于用户实际需要渲染的字符数，还是会有非常大的不必要的下载流量。",[28,1154,1155],{"id":1155},"实现原理",[32,1157,1158],{},"在动态渲染特殊字体文案的场景下，为了提升用户的加载体验，我们研发了一个创新的解决方案：「Font2svg」。在介绍实现原理前，先简单介绍下字体相关的基础知识。",[1128,1160,1162],{"id":1161},"字符character","字符（Character）",[32,1164,1165],{},"在计算机中，字符是任意单个文本元素，例如字母、数字、标点符号、汉字甚至 Emoji 表情等。字符通常是构建文本的基本单位。",[1128,1167,1169],{"id":1168},"字符集character-set","字符集（Character Set）",[32,1171,1172],{},"字符集是一组预定义的字符的集合。它是对字符进行分类和组织的方式，以便在计算机系统中能够使用。会为每一个字符分配一个唯一的 ID，叫做 Code Point（码点、内码），常见字符集：ASCII、GB2312、GBK、Unicode 等。",[1128,1174,1176],{"id":1175},"字符编码character-encoding","字符编码（Character Encoding）",[32,1178,1179],{},"字符编码是一种将字符映射到特定二进制数的规则，以便在计算机中存储。通常字符集和字符编码都是成对出现，如 ASCII、GB2312、GBK 等，都是既代表了字符集，也代表了对应的字符编码。而 Unicode 比较特殊，有多种字符编码，如 UTF-8、UTF-16 等。",[1128,1181,1183],{"id":1182},"字体家族font-family","字体家族（Font Family）",[32,1185,1186],{},"字体家族是指具有相似设计特征的一组字体。这些字体共享类似的外观，但在细节上可能略有不同，如粗细、斜体等。例如：「思源宋体」就是一个字体家族。",[1128,1188,1190],{"id":1189},"字体样式font-style","字体样式（Font Style）",[32,1192,1193],{},"字体样式是字体家族中特定字体的变体，它定义了字体的外观。例如：Light、Regular、Bold、Heavy 等。",[1128,1195,1197],{"id":1196},"字型font","字型（Font）",[32,1199,1200],{},"字型是一个字体家族下的特定样式的字体，例如: 思源宋体-Bold",[1128,1202,1204],{"id":1203},"字形glyph","字形（Glyph）",[32,1206,1207],{},"字形是指一个单独字符在特定的字体（字型）中的具体外观或形状。每个字符都有对应的字形，它们描述了字符的细节和轮廓。如下图，就是「哔」这个字符在不同的字体下不同的字形：",[1209,1210],"post-image",{"filename":1211},"01.png",[1128,1213,1215],{"id":1214},"字体度量metrics","字体度量（Metrics）",[32,1217,1218],{},"字体度量是指字体中字符的尺寸和位置信息。它包括了字符的宽度、高度、上沿线（ascender）、下沿线（descender）、基线（baseline）等数据。字体度量在排版中非常重要，它决定了字符之间的间距和对齐方式，确保文本在屏幕或打印上的合理布局。",[1209,1220],{"filename":1221},"02.png",[1209,1223],{"filename":1224},"03.png",[1128,1226,1228],{"id":1227},"轮廓outline","轮廓（Outline）",[32,1230,1231],{},"字体轮廓是字形的矢量图形表示，一个字形由若干轮廓组成，每个轮廓由若干条线段或贝塞尔曲线闭合而成。根据字体格式不同，使用的贝塞尔曲线的阶数也有区别。TrueType（TTF）字体采用二次贝塞尔曲线（3 点控制），而 OpenType（OTF）字体采用三次贝塞尔曲线（4 点控制）。",[32,1233,1234],{},"一个轮廓组成的字母 C：",[1209,1236],{"filename":1237},"04.gif",[32,1239,1240],{},"多个轮廓组成的字母 B：",[1209,1242],{"filename":1243},"05.gif",[32,1245,1246],{},"二次贝塞尔曲线：",[1209,1248],{"filename":1249},"06.gif",[32,1251,1252],{},"三次贝塞尔曲线：",[1209,1254],{"filename":1255},"07.gif",[32,1257,1258],{},"本方案的原理本质上还是利用 SVG 来渲染特殊字体，通过上面的介绍，我们知道，字体的轮廓由若干线段和贝塞尔曲线组成，得知了线段的坐标和贝塞尔曲线的控制点，我们就可以绘制出轮廓的 SVG，而有了 SVG 我们就可以用来显示。因此我们可以通过解析字体文件，按需获取对应字符的 SVG 来避免加载整个字体文件。如何去唯一表示一个特定字符呢？那就是通过字符的 Unicode 编码。如下图所示，我们在渲染「哔哩哔哩乾杯」这几个字符的时候，只需要根据「哔」、「哩」、「乾」、「杯」这四个字符的 Unicode 编码去获取他们的 SVG，然后组合起来显示即可。",[1209,1260],{"filename":1261},"08.png",[32,1263,1264],{},"考虑到性能，如果每个字符都实时去请求服务端生成 SVG 的话，并发小的时候没影响，并发大了的话，这个字体转 SVG 的服务就会成为瓶颈。不过由于这些 SVG 生成之后就几乎不会变，所以可以预先全部生成好，放到 CDN 上，每次直接根据 Unicode 编码去 CDN 上获取指定字符的 SVG 即可。这样既避免了服务端的压力，还利用了 CDN 的特性，可以让用户更快的获取到所需的资源。",[32,1266,1267],{},"「字体转 SVG」只是第一步，在实际渲染中，还需要修改 SVG 的样式，例如字体颜色、下划线等，因此我们还做了一个前端的组件，用来对获取到的 SVG 进行样式的处理以还原字体的样式。这个组件实现了根据所需字符「动态加载 SVG」以及「SVG 复原字体样式」，在使用起来只需要传入字体名称、所需显示的字符、一些字体样式等，大大减少了前端接入的复杂度。",[32,1269,1270],{},"整体流程如下图，页面开发阶段，在 Font2svg 后台预先上传字体文件，解析所有字符的 SVG，并预先上传至对象存储\u002FCDN 上。在页面加载时，通过前端组件传入需要渲染的字符和对应字体的配置，从 CDN 拉取特定字体特定字符的 SVG，并根据样式对字符的 SVG 进行二次编辑，复原字体上色、描边、下划线等样式。",[1209,1272],{"filename":1273},"09.png",[32,1275,1276],{},"后台效果预览：",[1278,1279],"video-player",{":autoplay":1280,":controls":1280,":loop":1280,"autoPlay":85,"filename":1281},"true","10.mp4",[1128,1283,1285],{"id":1284},"如何解析字体并生成字符的-svg","如何解析字体并生成字符的 SVG",[32,1287,1288,1289,1294,1295,1298,1299,1302],{},"字体解析服务通过 Python 实现，借助 ",[36,1290,1293],{"href":1291,"rel":1292},"https:\u002F\u002Fgithub.com\u002Frougier\u002Ffreetype-py",[40],"freetype"," 这个库对字体进行解析，遍历字体中的每一个字符，每个字符的字形（glyph）包含了轮廓（outline）和度量（metrics）数据，通过 ",[116,1296,1297],{},"outline.decompose"," 方法，可以将字符的轮廓数据分解为一系列的路径段，其中每个路径段都是一个点序列，表示字符的一部分。这些路径段可以是直线段、二次贝塞尔曲线或三次贝塞尔曲线。通过这些路径段的类型和点坐标，再通过 ",[116,1300,1301],{},"svgpathtools"," 这个库绘制成 SVG 的路径。",[186,1304,1308],{"className":1305,"code":1306,"language":1307,"meta":74,"style":74},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","outline.decompose(\n    context=None,\n    move_to=self.callbackMoveTo,\n    line_to=self.callbackLineTo,\n    conic_to=self.callbackConicTo,\n    cubic_to=self.callbackCubicTo,\n)\n\ndef callbackMoveTo(self, *args):\n    self._verbose(\"MoveTo \", len(args), self.vectorsToPoints(args))\n    self._lastX, self._lastY = args[0].x, args[0].y\n\ndef callbackLineTo(self, *args):\n    self._verbose(\"LineTo \", len(args), self.vectorsToPoints(args))\n    line = Line(self.lastXyToComplex(), self.vectorToComplex(args[0]))\n    self.svg_paths.append(line)\n    self._lastX, self._lastY = args[0].x, args[0].y\n\ndef callbackConicTo(self, *args):\n    self._verbose(\"ConicTo\", len(args), self.vectorsToPoints(args))\n    curve = QuadraticBezier(self.lastXyToComplex(), self.vectorToComplex(args[0]), self.vectorToComplex(args[1]))\n    self.svg_paths.append(curve)\n    self._lastX, self._lastY = args[1].x, args[1].y\n\ndef callbackCubicTo(self, *args):\n    self._verbose(\"CubicTo\", len(args), self.vectorsToPoints(args))\n    curve = CubicBezier(\n        self.lastXyToComplex(),\n        self.vectorToComplex(args[0]),\n        self.vectorToComplex(args[1]),\n        self.vectorToComplex(args[2]),\n    )\n    self.svg_paths.append(curve)\n    self._lastX, self._lastY = args[2].x, args[2].y\n","python",[116,1309,1310,1324,1338,1355,1371,1387,1403,1407,1411,1436,1484,1530,1534,1553,1592,1632,1652,1692,1696,1715,1754,1807,1826,1866,1870,1889,1928,1939,1951,1970,1988,2007,2012,2030],{"__ignoreMap":74},[194,1311,1312,1315,1317,1321],{"class":196,"line":197},[194,1313,1314],{"class":200},"outline",[194,1316,205],{"class":204},[194,1318,1320],{"class":1319},"slqww","decompose",[194,1322,1323],{"class":204},"(\n",[194,1325,1326,1329,1332,1336],{"class":196,"line":75},[194,1327,1328],{"class":305},"    context",[194,1330,1331],{"class":243},"=",[194,1333,1335],{"class":1334},"s39Yj","None",[194,1337,520],{"class":204},[194,1339,1340,1343,1345,1348,1350,1353],{"class":196,"line":368},[194,1341,1342],{"class":305},"    move_to",[194,1344,1331],{"class":243},[194,1346,1347],{"class":261},"self",[194,1349,205],{"class":204},[194,1351,1352],{"class":348},"callbackMoveTo",[194,1354,520],{"class":204},[194,1356,1357,1360,1362,1364,1366,1369],{"class":196,"line":391},[194,1358,1359],{"class":305},"    line_to",[194,1361,1331],{"class":243},[194,1363,1347],{"class":261},[194,1365,205],{"class":204},[194,1367,1368],{"class":348},"callbackLineTo",[194,1370,520],{"class":204},[194,1372,1373,1376,1378,1380,1382,1385],{"class":196,"line":397},[194,1374,1375],{"class":305},"    conic_to",[194,1377,1331],{"class":243},[194,1379,1347],{"class":261},[194,1381,205],{"class":204},[194,1383,1384],{"class":348},"callbackConicTo",[194,1386,520],{"class":204},[194,1388,1389,1392,1394,1396,1398,1401],{"class":196,"line":427},[194,1390,1391],{"class":305},"    cubic_to",[194,1393,1331],{"class":243},[194,1395,1347],{"class":261},[194,1397,205],{"class":204},[194,1399,1400],{"class":348},"callbackCubicTo",[194,1402,520],{"class":204},[194,1404,1405],{"class":196,"line":460},[194,1406,265],{"class":204},[194,1408,1409],{"class":196,"line":484},[194,1410,394],{"emptyLinePlaceholder":85},[194,1412,1413,1416,1419,1421,1424,1426,1429,1433],{"class":196,"line":496},[194,1414,1415],{"class":295},"def",[194,1417,1418],{"class":208}," callbackMoveTo",[194,1420,302],{"class":204},[194,1422,1347],{"class":1423},"smCYv",[194,1425,227],{"class":204},[194,1427,1428],{"class":243}," *",[194,1430,1432],{"class":1431},"sFwrP","args",[194,1434,1435],{"class":204},"):\n",[194,1437,1438,1441,1443,1446,1448,1451,1454,1456,1458,1462,1464,1466,1469,1472,1474,1477,1479,1481],{"class":196,"line":523},[194,1439,1440],{"class":261},"    self",[194,1442,205],{"class":204},[194,1444,1445],{"class":1319},"_verbose",[194,1447,302],{"class":204},[194,1449,1450],{"class":758},"\"",[194,1452,1453],{"class":762},"MoveTo ",[194,1455,1450],{"class":758},[194,1457,227],{"class":204},[194,1459,1461],{"class":1460},"sptTA"," len",[194,1463,302],{"class":204},[194,1465,1432],{"class":1319},[194,1467,1468],{"class":204},"),",[194,1470,1471],{"class":261}," self",[194,1473,205],{"class":204},[194,1475,1476],{"class":1319},"vectorsToPoints",[194,1478,302],{"class":204},[194,1480,1432],{"class":1319},[194,1482,1483],{"class":204},"))\n",[194,1485,1486,1488,1490,1493,1495,1497,1499,1502,1504,1507,1509,1511,1514,1517,1519,1521,1523,1525,1527],{"class":196,"line":540},[194,1487,1440],{"class":261},[194,1489,205],{"class":204},[194,1491,1492],{"class":348},"_lastX",[194,1494,227],{"class":204},[194,1496,1471],{"class":261},[194,1498,205],{"class":204},[194,1500,1501],{"class":348},"_lastY",[194,1503,377],{"class":243},[194,1505,1506],{"class":200}," args",[194,1508,476],{"class":204},[194,1510,237],{"class":220},[194,1512,1513],{"class":204},"].",[194,1515,1516],{"class":348},"x",[194,1518,227],{"class":204},[194,1520,1506],{"class":200},[194,1522,476],{"class":204},[194,1524,237],{"class":220},[194,1526,1513],{"class":204},[194,1528,1529],{"class":348},"y\n",[194,1531,1532],{"class":196,"line":577},[194,1533,394],{"emptyLinePlaceholder":85},[194,1535,1536,1538,1541,1543,1545,1547,1549,1551],{"class":196,"line":598},[194,1537,1415],{"class":295},[194,1539,1540],{"class":208}," callbackLineTo",[194,1542,302],{"class":204},[194,1544,1347],{"class":1423},[194,1546,227],{"class":204},[194,1548,1428],{"class":243},[194,1550,1432],{"class":1431},[194,1552,1435],{"class":204},[194,1554,1555,1557,1559,1561,1563,1565,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586,1588,1590],{"class":196,"line":618},[194,1556,1440],{"class":261},[194,1558,205],{"class":204},[194,1560,1445],{"class":1319},[194,1562,302],{"class":204},[194,1564,1450],{"class":758},[194,1566,1567],{"class":762},"LineTo ",[194,1569,1450],{"class":758},[194,1571,227],{"class":204},[194,1573,1461],{"class":1460},[194,1575,302],{"class":204},[194,1577,1432],{"class":1319},[194,1579,1468],{"class":204},[194,1581,1471],{"class":261},[194,1583,205],{"class":204},[194,1585,1476],{"class":1319},[194,1587,302],{"class":204},[194,1589,1432],{"class":1319},[194,1591,1483],{"class":204},[194,1593,1594,1597,1599,1602,1604,1606,1608,1611,1614,1616,1618,1621,1623,1625,1627,1629],{"class":196,"line":678},[194,1595,1596],{"class":200},"    line ",[194,1598,1331],{"class":243},[194,1600,1601],{"class":1319}," Line",[194,1603,302],{"class":204},[194,1605,1347],{"class":261},[194,1607,205],{"class":204},[194,1609,1610],{"class":1319},"lastXyToComplex",[194,1612,1613],{"class":204},"(),",[194,1615,1471],{"class":261},[194,1617,205],{"class":204},[194,1619,1620],{"class":1319},"vectorToComplex",[194,1622,302],{"class":204},[194,1624,1432],{"class":1319},[194,1626,476],{"class":204},[194,1628,237],{"class":220},[194,1630,1631],{"class":204},"]))\n",[194,1633,1634,1636,1638,1641,1643,1646,1648,1650],{"class":196,"line":684},[194,1635,1440],{"class":261},[194,1637,205],{"class":204},[194,1639,1640],{"class":348},"svg_paths",[194,1642,205],{"class":204},[194,1644,1645],{"class":1319},"append",[194,1647,302],{"class":204},[194,1649,196],{"class":1319},[194,1651,265],{"class":204},[194,1653,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688,1690],{"class":196,"line":689},[194,1655,1440],{"class":261},[194,1657,205],{"class":204},[194,1659,1492],{"class":348},[194,1661,227],{"class":204},[194,1663,1471],{"class":261},[194,1665,205],{"class":204},[194,1667,1501],{"class":348},[194,1669,377],{"class":243},[194,1671,1506],{"class":200},[194,1673,476],{"class":204},[194,1675,237],{"class":220},[194,1677,1513],{"class":204},[194,1679,1516],{"class":348},[194,1681,227],{"class":204},[194,1683,1506],{"class":200},[194,1685,476],{"class":204},[194,1687,237],{"class":220},[194,1689,1513],{"class":204},[194,1691,1529],{"class":348},[194,1693,1694],{"class":196,"line":702},[194,1695,394],{"emptyLinePlaceholder":85},[194,1697,1698,1700,1703,1705,1707,1709,1711,1713],{"class":196,"line":708},[194,1699,1415],{"class":295},[194,1701,1702],{"class":208}," callbackConicTo",[194,1704,302],{"class":204},[194,1706,1347],{"class":1423},[194,1708,227],{"class":204},[194,1710,1428],{"class":243},[194,1712,1432],{"class":1431},[194,1714,1435],{"class":204},[194,1716,1717,1719,1721,1723,1725,1727,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748,1750,1752],{"class":196,"line":714},[194,1718,1440],{"class":261},[194,1720,205],{"class":204},[194,1722,1445],{"class":1319},[194,1724,302],{"class":204},[194,1726,1450],{"class":758},[194,1728,1729],{"class":762},"ConicTo",[194,1731,1450],{"class":758},[194,1733,227],{"class":204},[194,1735,1461],{"class":1460},[194,1737,302],{"class":204},[194,1739,1432],{"class":1319},[194,1741,1468],{"class":204},[194,1743,1471],{"class":261},[194,1745,205],{"class":204},[194,1747,1476],{"class":1319},[194,1749,302],{"class":204},[194,1751,1432],{"class":1319},[194,1753,1483],{"class":204},[194,1755,1756,1759,1761,1764,1766,1768,1770,1772,1774,1776,1778,1780,1782,1784,1786,1788,1791,1793,1795,1797,1799,1801,1803,1805],{"class":196,"line":719},[194,1757,1758],{"class":200},"    curve ",[194,1760,1331],{"class":243},[194,1762,1763],{"class":1319}," QuadraticBezier",[194,1765,302],{"class":204},[194,1767,1347],{"class":261},[194,1769,205],{"class":204},[194,1771,1610],{"class":1319},[194,1773,1613],{"class":204},[194,1775,1471],{"class":261},[194,1777,205],{"class":204},[194,1779,1620],{"class":1319},[194,1781,302],{"class":204},[194,1783,1432],{"class":1319},[194,1785,476],{"class":204},[194,1787,237],{"class":220},[194,1789,1790],{"class":204},"]),",[194,1792,1471],{"class":261},[194,1794,205],{"class":204},[194,1796,1620],{"class":1319},[194,1798,302],{"class":204},[194,1800,1432],{"class":1319},[194,1802,476],{"class":204},[194,1804,221],{"class":220},[194,1806,1631],{"class":204},[194,1808,1809,1811,1813,1815,1817,1819,1821,1824],{"class":196,"line":745},[194,1810,1440],{"class":261},[194,1812,205],{"class":204},[194,1814,1640],{"class":348},[194,1816,205],{"class":204},[194,1818,1645],{"class":1319},[194,1820,302],{"class":204},[194,1822,1823],{"class":1319},"curve",[194,1825,265],{"class":204},[194,1827,1828,1830,1832,1834,1836,1838,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1864],{"class":196,"line":774},[194,1829,1440],{"class":261},[194,1831,205],{"class":204},[194,1833,1492],{"class":348},[194,1835,227],{"class":204},[194,1837,1471],{"class":261},[194,1839,205],{"class":204},[194,1841,1501],{"class":348},[194,1843,377],{"class":243},[194,1845,1506],{"class":200},[194,1847,476],{"class":204},[194,1849,221],{"class":220},[194,1851,1513],{"class":204},[194,1853,1516],{"class":348},[194,1855,227],{"class":204},[194,1857,1506],{"class":200},[194,1859,476],{"class":204},[194,1861,221],{"class":220},[194,1863,1513],{"class":204},[194,1865,1529],{"class":348},[194,1867,1868],{"class":196,"line":786},[194,1869,394],{"emptyLinePlaceholder":85},[194,1871,1872,1874,1877,1879,1881,1883,1885,1887],{"class":196,"line":791},[194,1873,1415],{"class":295},[194,1875,1876],{"class":208}," callbackCubicTo",[194,1878,302],{"class":204},[194,1880,1347],{"class":1423},[194,1882,227],{"class":204},[194,1884,1428],{"class":243},[194,1886,1432],{"class":1431},[194,1888,1435],{"class":204},[194,1890,1891,1893,1895,1897,1899,1901,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926],{"class":196,"line":796},[194,1892,1440],{"class":261},[194,1894,205],{"class":204},[194,1896,1445],{"class":1319},[194,1898,302],{"class":204},[194,1900,1450],{"class":758},[194,1902,1903],{"class":762},"CubicTo",[194,1905,1450],{"class":758},[194,1907,227],{"class":204},[194,1909,1461],{"class":1460},[194,1911,302],{"class":204},[194,1913,1432],{"class":1319},[194,1915,1468],{"class":204},[194,1917,1471],{"class":261},[194,1919,205],{"class":204},[194,1921,1476],{"class":1319},[194,1923,302],{"class":204},[194,1925,1432],{"class":1319},[194,1927,1483],{"class":204},[194,1929,1930,1932,1934,1937],{"class":196,"line":824},[194,1931,1758],{"class":200},[194,1933,1331],{"class":243},[194,1935,1936],{"class":1319}," CubicBezier",[194,1938,1323],{"class":204},[194,1940,1941,1944,1946,1948],{"class":196,"line":838},[194,1942,1943],{"class":261},"        self",[194,1945,205],{"class":204},[194,1947,1610],{"class":1319},[194,1949,1950],{"class":204},"(),\n",[194,1952,1953,1955,1957,1959,1961,1963,1965,1967],{"class":196,"line":885},[194,1954,1943],{"class":261},[194,1956,205],{"class":204},[194,1958,1620],{"class":1319},[194,1960,302],{"class":204},[194,1962,1432],{"class":1319},[194,1964,476],{"class":204},[194,1966,237],{"class":220},[194,1968,1969],{"class":204},"]),\n",[194,1971,1972,1974,1976,1978,1980,1982,1984,1986],{"class":196,"line":899},[194,1973,1943],{"class":261},[194,1975,205],{"class":204},[194,1977,1620],{"class":1319},[194,1979,302],{"class":204},[194,1981,1432],{"class":1319},[194,1983,476],{"class":204},[194,1985,221],{"class":220},[194,1987,1969],{"class":204},[194,1989,1990,1992,1994,1996,1998,2000,2002,2005],{"class":196,"line":912},[194,1991,1943],{"class":261},[194,1993,205],{"class":204},[194,1995,1620],{"class":1319},[194,1997,302],{"class":204},[194,1999,1432],{"class":1319},[194,2001,476],{"class":204},[194,2003,2004],{"class":220},"2",[194,2006,1969],{"class":204},[194,2008,2009],{"class":196,"line":918},[194,2010,2011],{"class":204},"    )\n",[194,2013,2014,2016,2018,2020,2022,2024,2026,2028],{"class":196,"line":923},[194,2015,1440],{"class":261},[194,2017,205],{"class":204},[194,2019,1640],{"class":348},[194,2021,205],{"class":204},[194,2023,1645],{"class":1319},[194,2025,302],{"class":204},[194,2027,1823],{"class":1319},[194,2029,265],{"class":204},[194,2031,2032,2034,2036,2038,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068],{"class":196,"line":928},[194,2033,1440],{"class":261},[194,2035,205],{"class":204},[194,2037,1492],{"class":348},[194,2039,227],{"class":204},[194,2041,1471],{"class":261},[194,2043,205],{"class":204},[194,2045,1501],{"class":348},[194,2047,377],{"class":243},[194,2049,1506],{"class":200},[194,2051,476],{"class":204},[194,2053,2004],{"class":220},[194,2055,1513],{"class":204},[194,2057,1516],{"class":348},[194,2059,227],{"class":204},[194,2061,1506],{"class":200},[194,2063,476],{"class":204},[194,2065,2004],{"class":220},[194,2067,1513],{"class":204},[194,2069,1529],{"class":348},[32,2071,2072],{},"除了 SVG 的路径之外，还有一个很重要的内容，就是 viewBox。因为在字体中，表达一个字形，除了轮廓之外，还有一个很重要的 metrics 信息，metrics 信息没有还原好，在字体排版的时候就会出现问题。如果以轮廓本身的大小作为容器的话，一些未占满空间的文字，或者一些标点符号，在排版时，就会被缩放的很大；而如果以字体的上沿线和下沿线来计算一个方形容器显示的话，对于中文字符问题不大，但对于半角字符，其中间的间隙就会显得太大，如下图：",[1209,2074],{"filename":2075},"11.png",[32,2077,2078],{},"因此，我们需要从 Metrics 中提取每个字形的基本信息。以横排为例，取每个字形自己的宽度来绘制 viewBox，高度还是通过上下沿线相减来计算。这样在排版时就可以得到想要的效果了：",[1209,2080],{"filename":2081},"12.png",[186,2083,2085],{"className":1305,"code":2084,"language":1307,"meta":74,"style":74},"def calcViewBox(self, metrics):\n    view_box_min_x = 0\n    view_box_min_y = -self.face.ascender\n    view_box_width = metrics.horiAdvance\n    view_box_height = self.face.ascender - self.face.descender\n    return f\"{view_box_min_x} {view_box_min_y} {view_box_width} {view_box_height}\"\n",[116,2086,2087,2105,2114,2136,2150,2181],{"__ignoreMap":74},[194,2088,2089,2091,2094,2096,2098,2100,2103],{"class":196,"line":197},[194,2090,1415],{"class":295},[194,2092,2093],{"class":208}," calcViewBox",[194,2095,302],{"class":204},[194,2097,1347],{"class":1423},[194,2099,227],{"class":204},[194,2101,2102],{"class":1431}," metrics",[194,2104,1435],{"class":204},[194,2106,2107,2110,2112],{"class":196,"line":75},[194,2108,2109],{"class":200},"    view_box_min_x ",[194,2111,1331],{"class":243},[194,2113,835],{"class":220},[194,2115,2116,2119,2121,2124,2126,2128,2131,2133],{"class":196,"line":368},[194,2117,2118],{"class":200},"    view_box_min_y ",[194,2120,1331],{"class":243},[194,2122,2123],{"class":243}," -",[194,2125,1347],{"class":261},[194,2127,205],{"class":204},[194,2129,2130],{"class":348},"face",[194,2132,205],{"class":204},[194,2134,2135],{"class":348},"ascender\n",[194,2137,2138,2141,2143,2145,2147],{"class":196,"line":391},[194,2139,2140],{"class":200},"    view_box_width ",[194,2142,1331],{"class":243},[194,2144,2102],{"class":200},[194,2146,205],{"class":204},[194,2148,2149],{"class":348},"horiAdvance\n",[194,2151,2152,2155,2157,2159,2161,2163,2165,2168,2170,2172,2174,2176,2178],{"class":196,"line":397},[194,2153,2154],{"class":200},"    view_box_height ",[194,2156,1331],{"class":243},[194,2158,1471],{"class":261},[194,2160,205],{"class":204},[194,2162,2130],{"class":348},[194,2164,205],{"class":204},[194,2166,2167],{"class":348},"ascender",[194,2169,2123],{"class":243},[194,2171,1471],{"class":261},[194,2173,205],{"class":204},[194,2175,2130],{"class":348},[194,2177,205],{"class":204},[194,2179,2180],{"class":348},"descender\n",[194,2182,2183,2186,2189,2191,2194,2197,2200,2203,2206,2208,2210,2213,2215,2217,2220,2222],{"class":196,"line":427},[194,2184,2185],{"class":339},"    return",[194,2187,2188],{"class":295}," f",[194,2190,1450],{"class":762},[194,2192,2193],{"class":220},"{",[194,2195,2196],{"class":200},"view_box_min_x",[194,2198,2199],{"class":220},"}",[194,2201,2202],{"class":220}," {",[194,2204,2205],{"class":200},"view_box_min_y",[194,2207,2199],{"class":220},[194,2209,2202],{"class":220},[194,2211,2212],{"class":200},"view_box_width",[194,2214,2199],{"class":220},[194,2216,2202],{"class":220},[194,2218,2219],{"class":200},"view_box_height",[194,2221,2199],{"class":220},[194,2223,2224],{"class":762},"\"\n",[32,2226,2227,2228,66,2231,66,2234,66,2236,2238],{},"在有了 ",[116,2229,2230],{},"path",[116,2232,2233],{},"viewBox",[116,2235,146],{},[116,2237,150],{}," 等信息之后，我们就可以直接生成能表达具体字形所需要的 SVG 文件了。",[32,2240,2241,2242,2244],{},"还有一个特殊字符，那就是空格，他是不包含任何轮廓的，解析轮廓的时候结果是空的。在生成 SVG 的时候，如果 path 是为空会失败，这时候可以生成一个和 ",[116,2243,2233],{}," 同样大小的透明度为 0 的轮廓用来占位。",[186,2246,2248],{"className":1305,"code":2247,"language":1307,"meta":74,"style":74},"path = (\n    Path(*self.svg_paths).scaled(1, -1)\n    if len(self.svg_paths) > 0\n    else parse_path(\n        f\"M 0,{-self.face.ascender} L {metrics.horiAdvance},{-self.face.ascender} L {metrics.horiAdvance},{-self.face.descender} L 0,{-self.face.descender} Z\"\n    )\n)\n",[116,2249,2250,2260,2293,2315,2325,2436,2440],{"__ignoreMap":74},[194,2251,2252,2255,2257],{"class":196,"line":197},[194,2253,2254],{"class":200},"path ",[194,2256,1331],{"class":243},[194,2258,2259],{"class":204}," (\n",[194,2261,2262,2265,2267,2269,2271,2273,2275,2278,2281,2283,2285,2287,2289,2291],{"class":196,"line":75},[194,2263,2264],{"class":1319},"    Path",[194,2266,302],{"class":204},[194,2268,244],{"class":243},[194,2270,1347],{"class":261},[194,2272,205],{"class":204},[194,2274,1640],{"class":348},[194,2276,2277],{"class":204},").",[194,2279,2280],{"class":1319},"scaled",[194,2282,302],{"class":204},[194,2284,221],{"class":220},[194,2286,227],{"class":204},[194,2288,2123],{"class":243},[194,2290,221],{"class":220},[194,2292,265],{"class":204},[194,2294,2295,2298,2300,2302,2304,2306,2308,2310,2313],{"class":196,"line":368},[194,2296,2297],{"class":339},"    if",[194,2299,1461],{"class":1460},[194,2301,302],{"class":204},[194,2303,1347],{"class":261},[194,2305,205],{"class":204},[194,2307,1640],{"class":348},[194,2309,316],{"class":204},[194,2311,2312],{"class":243}," >",[194,2314,835],{"class":220},[194,2316,2317,2320,2323],{"class":196,"line":391},[194,2318,2319],{"class":339},"    else",[194,2321,2322],{"class":1319}," parse_path",[194,2324,1323],{"class":204},[194,2326,2327,2330,2333,2335,2338,2340,2342,2344,2346,2348,2350,2353,2355,2358,2360,2363,2365,2367,2369,2371,2373,2375,2377,2379,2381,2383,2385,2387,2389,2391,2393,2395,2397,2399,2401,2403,2405,2407,2409,2412,2414,2417,2419,2421,2423,2425,2427,2429,2431,2433],{"class":196,"line":397},[194,2328,2329],{"class":295},"        f",[194,2331,2332],{"class":762},"\"M 0,",[194,2334,2193],{"class":220},[194,2336,2337],{"class":243},"-",[194,2339,1347],{"class":261},[194,2341,205],{"class":204},[194,2343,2130],{"class":348},[194,2345,205],{"class":204},[194,2347,2167],{"class":348},[194,2349,2199],{"class":220},[194,2351,2352],{"class":762}," L ",[194,2354,2193],{"class":220},[194,2356,2357],{"class":1319},"metrics",[194,2359,205],{"class":204},[194,2361,2362],{"class":348},"horiAdvance",[194,2364,2199],{"class":220},[194,2366,227],{"class":762},[194,2368,2193],{"class":220},[194,2370,2337],{"class":243},[194,2372,1347],{"class":261},[194,2374,205],{"class":204},[194,2376,2130],{"class":348},[194,2378,205],{"class":204},[194,2380,2167],{"class":348},[194,2382,2199],{"class":220},[194,2384,2352],{"class":762},[194,2386,2193],{"class":220},[194,2388,2357],{"class":1319},[194,2390,205],{"class":204},[194,2392,2362],{"class":348},[194,2394,2199],{"class":220},[194,2396,227],{"class":762},[194,2398,2193],{"class":220},[194,2400,2337],{"class":243},[194,2402,1347],{"class":261},[194,2404,205],{"class":204},[194,2406,2130],{"class":348},[194,2408,205],{"class":204},[194,2410,2411],{"class":348},"descender",[194,2413,2199],{"class":220},[194,2415,2416],{"class":762}," L 0,",[194,2418,2193],{"class":220},[194,2420,2337],{"class":243},[194,2422,1347],{"class":261},[194,2424,205],{"class":204},[194,2426,2130],{"class":348},[194,2428,205],{"class":204},[194,2430,2411],{"class":348},[194,2432,2199],{"class":220},[194,2434,2435],{"class":762}," Z\"\n",[194,2437,2438],{"class":196,"line":427},[194,2439,2011],{"class":204},[194,2441,2442],{"class":196,"line":460},[194,2443,265],{"class":204},[1128,2445,2446],{"id":2446},"怎么设置字体样式",[32,2448,2449],{},"在前面的步骤中，我们为每个字形预先生成好了对应的 SVG 文件，但我们知道，SVG 文件是一个静态的文件，其字体颜色等样式是固定的，在前端使用的时候，如何去动态设置颜色等样式呢？",[32,2451,2452,2453,2456,2457,2459,2460,2463,2464,2467],{},"我们在前端去加载 SVG 字体文件的时候，并不是直接通过 ",[116,2454,2455],{},"img"," 标签的方式来引入 SVG，而是在前端组件中去下载 SVG 文件内容并解析 DOM 对象，然后根据需要去修改对应的属性或添加额外的样式，例如修改 ",[116,2458,2230],{}," 的 ",[116,2461,2462],{},"fill"," 属性来设置颜色，修改完样式后再把修改后的 SVG 标签直接插入到文档中。还有一些字体全局的样式信息，例如下划线的位置、粗细等，在不同的字体中，也是有不一样的配置的。我们需要想办法把这些信息也通过 SVG 传递过来。解决方法也很简单，我们在 SVG 的根节点上添加一个自定义的属性，将所需传递的信息转成 ",[116,2465,2466],{},"JSON"," 格式然后塞到这个属性里，前端在解析 SVG 的 DOM 时，把这个属性中的数据取出来解析并渲染就可以了。",[1209,2469],{"filename":2470},"13.png",[32,2472,2473,2474,147,2477,2480,2481,2484],{},"上面截图中，",[116,2475,2476],{},"underline",[116,2478,2479],{},"underlineThickness"," 就分别代表了下划线位置和粗细信息，在解析到这两个信息后再做一些转换处理，可通过 ",[116,2482,2483],{},"::before"," 伪元素来绘制下划线：",[1209,2486],{"filename":2487},"14.png",[28,2489,2490],{"id":2490},"实践案例",[1128,2492,2493],{"id":2493},"页面接入",[32,2495,2496],{},"创作中心每周会给 UP 主推送荣誉周报，页面中首屏有非常大的篇幅显示的是本周关键词，效果如下图：",[1209,2498],{"filename":2499},"15.jpg",[32,2501,2502],{},"这个场景有两处使用了特殊字体「方正 FW 筑紫黑」，一个是卡片标题「本周关键词」这几个字，这个是固定标题，用传统的切图方式也是 OK 的，但下面的关键词是根据 UP 主上周的投稿、评论、弹幕、播放等数据由服务端动态下发的，这个就无法通过切图来实现了，需要穷举的关键词特别多。",[32,2504,2505],{},"在使用本方案之前，该页面使用的是直接引入字体文件来设置字体。由于该页面加载资源较多，在加载时不可避免会出现字体闪烁、图片资源加载过程页面抖动的问题，在手机上通过 4G 网络访问时尤其明显。为了避免这种情况，该页面渲染之前加入了一个 Loading 页，在 Loading 页上加载所需的所有资源，包括图片、字体文件等。",[32,2507,2508],{},"使用本方案优化前后的数据对比如下：",[2510,2511,2512,2530],"table",{},[2513,2514,2515],"thead",{},[2516,2517,2518,2521,2524,2527],"tr",{},[2519,2520],"th",{},[2519,2522,2523],{},"渲染文字所需资源",[2519,2525,2526],{},"首屏加载时长",[2519,2528,2529],{},"页面跳失率",[2531,2532,2533,2548,2562],"tbody",{},[2516,2534,2535,2539,2542,2545],{},[2536,2537,2538],"td",{},"优化前",[2536,2540,2541],{},"2855KB",[2536,2543,2544],{},"2268ms",[2536,2546,2547],{},"5.47%",[2516,2549,2550,2553,2556,2559],{},[2536,2551,2552],{},"优化后",[2536,2554,2555],{},"45KB",[2536,2557,2558],{},"1791ms",[2536,2560,2561],{},"3.11%",[2516,2563,2564,2567,2570,2573],{},[2536,2565,2566],{},"对比",[2536,2568,2569],{},"🔽 下降 98.4%",[2536,2571,2572],{},"🔽 下降 21%",[2536,2574,2575],{},"🔽 下降 2.36PP",[2577,2578,2579,2588,2594],"ul",{},[2580,2581,2582,2583,2587],"li",{},"渲染文字所需资源：优化前字体文件为 2855KB，优化后 JS 组件库为 37KB，4 个 SVG 为 8KB，总共 45KB 的资源，",[2584,2585,2586],"strong",{},"下降了 98.4%","。",[2580,2589,2590,2591,2587],{},"首屏加载时长：从 2268ms 下降至 1791ms，",[2584,2592,2593],{},"下降了 21%",[2580,2595,2596,2597,2587],{},"页面跳失率：从 5.47% 降低至 3.11%，",[2584,2598,2599],{},"下降了 2.36PP",[32,2601,2602],{},"最终本方案不仅通过降低首屏加载时长提升了用户的加载体验，同时也获得了页面跳失率下降的业务结果。",[1128,2604,2605],{"id":2605},"失败兜底",[32,2607,2608],{},"尽管本方案所需加载的资源量相比直接引入字体包要小很多，但请求数量会根据字符数有所上升，在极端情况下，网络失败会导致资源请求失败；另外，一些字体包含的字符数有限，在请求这个字体本身就不存在的字符的时候，也会出现请求失败的情况。在缺失 SVG 的情况下如何进行兜底的显示也是我们需要考虑的内容。",[32,2610,2611],{},"在组件侧获取不到对应的 SVG 时，可通过系统默认字体直接在字符位置进行渲染作为兜底，与浏览器默认的处理方式一致。具体效果如下：",[1209,2613],{"filename":2614},"16.png",[32,2616,2617],{},"由于兜底的默认字体与目标字体不同，他们的容器大小和下划线位置、下划线粗细等设置均有可能不一样，所以在开启了下划线的情况下，会出现下划线高度和粗细不一致的现象，如下图：",[1209,2619],{"filename":2620},"17.png",[32,2622,2623],{},"为了解决这个问题，我们在渲染下划线的时候，对于兜底的默认字体，不采用 css 样式来绘制下划线，而是与 SVG 一样，通过伪元素并且使用目标字体的下划线设置来进行绘制，最终效果如下图：",[1209,2625],{"filename":2626},"18.png",[28,2628,2629],{"id":2629},"总结",[32,2631,2632],{},"在本文中，我们深入探讨了 Font2svg 方案的技术原理和实现细节。我们通过将字体转换成 SVG 进行渲染，降低了用户渲染特殊字体动态文字所需下载的文件大小，提高了加载速度，从而优化了特殊字体在 Web 页面中的加载体验。",[32,2634,2635],{},"这套方案在动态渲染特殊字体文案的场景下具有广泛的应用前景，不只是在 Web 端，在客户端上也同样适用。它能为设计师和开发者提供更灵活、高效的特殊字体渲染方案，让设计师不会再因为字体包体积而放弃使用一些艺术字体，同时也让开发者不再为字体包的大小而头疼。",[1061,2637,2638],{},"html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .slqww, html code.shiki .slqww{--shiki-light:#6182B8;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .smCYv, html code.shiki .smCYv{--shiki-light:#E53935;--shiki-light-font-style:italic;--shiki-default:#24292E;--shiki-default-font-style:inherit;--shiki-dark:#E1E4E8;--shiki-dark-font-style:inherit}html pre.shiki code .sFwrP, html code.shiki .sFwrP{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#24292E;--shiki-default-font-style:inherit;--shiki-dark:#E1E4E8;--shiki-dark-font-style:inherit}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}",{"title":74,"searchDepth":75,"depth":75,"links":2640},[2641,2642,2643,2644],{"id":1120,"depth":75,"text":1120},{"id":1155,"depth":75,"text":1155},{"id":2490,"depth":75,"text":2490},{"id":2629,"depth":75,"text":2629},"2023-07-19",{},"\u002Fposts\u002F2023\u002Ffont2svg-solution",{"text":2649,"minutes":2650,"time":2651,"words":2652},"21 min read",20.145,1208700,4029,{"title":1088,"description":74},{"loc":2647},"posts\u002F2023\u002F20230719.font2svg-solution",[1083,97],"ExIjK3gmZDqEqp_xAilxKHvuQ01bwCJnvj3dq-s3k6o",{"id":2659,"title":2660,"body":2661,"class":79,"cover":4116,"coverSize":79,"date":4117,"description":2665,"draft":82,"extension":83,"hideComments":82,"location":79,"meta":4118,"navigation":85,"path":4119,"readingTime":4120,"seo":4124,"sitemap":4125,"stem":4126,"tags":4127,"time":79,"weather":4128,"__hash__":4129},"posts\u002Fposts\u002F2019\u002F20190324.audiowave-animation.md","声波动画教程",{"type":25,"value":2662,"toc":4114},[2663,2666,2669,2672,2676,2682,2685,2750,3004,4031,4034,4047,4053,4066,4079,4082,4097,4111],[32,2664,2665],{},"最近项目中有一个声波动画的效果的需求，网上没找到合适的，于是手撸了一个。",[1128,2667,2668],{"id":2668},"效果",[1209,2670],{"filename":2671},"01.gif",[1128,2673,2675],{"id":2674},"demo","Demo",[32,2677,2678],{},[36,2679,2680],{"href":2680,"rel":2681},"https:\u002F\u002Fjsfiddle.net\u002FHADB\u002Fx8vdkmqh\u002F",[40],[1128,2683,2684],{"id":2684},"示例代码",[186,2686,2690],{"className":2687,"code":2688,"language":2689,"meta":74,"style":74},"language-html shiki shiki-themes material-theme-lighter github-light github-dark","\u003Cdiv class=\"background\">\n  \u003Cdiv class=\"audiowave\">\u003C\u002Fdiv>\n\u003C\u002Fdiv>\n","html",[116,2691,2692,2716,2741],{"__ignoreMap":74},[194,2693,2694,2696,2700,2704,2706,2708,2711,2713],{"class":196,"line":197},[194,2695,324],{"class":204},[194,2697,2699],{"class":2698},"sQzsp","div",[194,2701,2703],{"class":2702},"s9AJx"," class",[194,2705,1331],{"class":204},[194,2707,1450],{"class":758},[194,2709,2710],{"class":762},"background",[194,2712,1450],{"class":758},[194,2714,2715],{"class":204},">\n",[194,2717,2718,2721,2723,2725,2727,2729,2732,2734,2737,2739],{"class":196,"line":75},[194,2719,2720],{"class":204},"  \u003C",[194,2722,2699],{"class":2698},[194,2724,2703],{"class":2702},[194,2726,1331],{"class":204},[194,2728,1450],{"class":758},[194,2730,2731],{"class":762},"audiowave",[194,2733,1450],{"class":758},[194,2735,2736],{"class":204},">\u003C\u002F",[194,2738,2699],{"class":2698},[194,2740,2715],{"class":204},[194,2742,2743,2746,2748],{"class":196,"line":368},[194,2744,2745],{"class":204},"\u003C\u002F",[194,2747,2699],{"class":2698},[194,2749,2715],{"class":204},[186,2751,2755],{"className":2752,"code":2753,"language":2754,"meta":74,"style":74},"language-javascript shiki shiki-themes material-theme-lighter github-light github-dark","\u002F*\n\nAuthor: HADB\nDate: 2019-03-24\nMy Blog: https:\u002F\u002Fhadb.me\nMy GitHub: https:\u002F\u002Fgithub.com\u002FHADB\n\n*\u002F\n\nconst wavePillarCount = 100 \u002F\u002F 柱子总数（用来调整密度）\nconst waveCount = 5 \u002F\u002F 波形总数（用来调整波形数量）\nconst waveAnimationDuration = 3 \u002F\u002F 单个动画秒数（与 animation-duration 一致）\nconst randomRate = 1 \u002F\u002F 随机倍率，越大越随机\n\nfor (i = 0; i \u003C wavePillarCount; i++) {\n  const offset = ((i \u002F wavePillarCount - 1) * waveCount * waveAnimationDuration) - Math.random() * randomRate\n  document.querySelector('.audiowave').innerHTML += `\u003Cdiv style=\"-webkit-animation-delay:${offset}s;\">\u003C\u002Fdiv>`\n}\n","javascript",[116,2756,2757,2763,2767,2772,2777,2782,2787,2791,2796,2800,2816,2831,2846,2861,2865,2900,2952,3000],{"__ignoreMap":74},[194,2758,2759],{"class":196,"line":197},[194,2760,2762],{"class":2761},"sutJx","\u002F*\n",[194,2764,2765],{"class":196,"line":75},[194,2766,394],{"emptyLinePlaceholder":85},[194,2768,2769],{"class":196,"line":368},[194,2770,2771],{"class":2761},"Author: HADB\n",[194,2773,2774],{"class":196,"line":391},[194,2775,2776],{"class":2761},"Date: 2019-03-24\n",[194,2778,2779],{"class":196,"line":397},[194,2780,2781],{"class":2761},"My Blog: https:\u002F\u002Fhadb.me\n",[194,2783,2784],{"class":196,"line":427},[194,2785,2786],{"class":2761},"My GitHub: https:\u002F\u002Fgithub.com\u002FHADB\n",[194,2788,2789],{"class":196,"line":460},[194,2790,394],{"emptyLinePlaceholder":85},[194,2792,2793],{"class":196,"line":484},[194,2794,2795],{"class":2761},"*\u002F\n",[194,2797,2798],{"class":196,"line":496},[194,2799,394],{"emptyLinePlaceholder":85},[194,2801,2802,2805,2808,2810,2813],{"class":196,"line":523},[194,2803,2804],{"class":295},"const",[194,2806,2807],{"class":261}," wavePillarCount",[194,2809,377],{"class":243},[194,2811,2812],{"class":220}," 100",[194,2814,2815],{"class":2761}," \u002F\u002F 柱子总数（用来调整密度）\n",[194,2817,2818,2820,2823,2825,2828],{"class":196,"line":540},[194,2819,2804],{"class":295},[194,2821,2822],{"class":261}," waveCount",[194,2824,377],{"class":243},[194,2826,2827],{"class":220}," 5",[194,2829,2830],{"class":2761}," \u002F\u002F 波形总数（用来调整波形数量）\n",[194,2832,2833,2835,2838,2840,2843],{"class":196,"line":577},[194,2834,2804],{"class":295},[194,2836,2837],{"class":261}," waveAnimationDuration",[194,2839,377],{"class":243},[194,2841,2842],{"class":220}," 3",[194,2844,2845],{"class":2761}," \u002F\u002F 单个动画秒数（与 animation-duration 一致）\n",[194,2847,2848,2850,2853,2855,2858],{"class":196,"line":598},[194,2849,2804],{"class":295},[194,2851,2852],{"class":261}," randomRate",[194,2854,377],{"class":243},[194,2856,2857],{"class":220}," 1",[194,2859,2860],{"class":2761}," \u002F\u002F 随机倍率，越大越随机\n",[194,2862,2863],{"class":196,"line":618},[194,2864,394],{"emptyLinePlaceholder":85},[194,2866,2867,2870,2873,2875,2878,2881,2884,2886,2888,2890,2893,2896,2898],{"class":196,"line":678},[194,2868,2869],{"class":339},"for",[194,2871,2872],{"class":200}," (i ",[194,2874,1331],{"class":243},[194,2876,2877],{"class":220}," 0",[194,2879,2880],{"class":204},";",[194,2882,2883],{"class":200}," i ",[194,2885,324],{"class":243},[194,2887,2807],{"class":200},[194,2889,2880],{"class":204},[194,2891,2892],{"class":200}," i",[194,2894,2895],{"class":243},"++",[194,2897,454],{"class":200},[194,2899,457],{"class":204},[194,2901,2902,2905,2907,2909,2912,2915,2917,2919,2921,2923,2925,2927,2929,2931,2933,2935,2937,2939,2941,2944,2947,2949],{"class":196,"line":684},[194,2903,2904],{"class":295},"  const",[194,2906,830],{"class":261},[194,2908,377],{"class":243},[194,2910,2911],{"class":348}," ((",[194,2913,2914],{"class":200},"i",[194,2916,253],{"class":243},[194,2918,2807],{"class":200},[194,2920,2123],{"class":243},[194,2922,2857],{"class":220},[194,2924,454],{"class":348},[194,2926,244],{"class":243},[194,2928,2822],{"class":200},[194,2930,1428],{"class":243},[194,2932,2837],{"class":200},[194,2934,454],{"class":348},[194,2936,2337],{"class":243},[194,2938,256],{"class":200},[194,2940,205],{"class":204},[194,2942,2943],{"class":208},"random",[194,2945,2946],{"class":348},"() ",[194,2948,244],{"class":243},[194,2950,2951],{"class":200}," randomRate\n",[194,2953,2954,2957,2959,2962,2964,2966,2969,2971,2973,2975,2978,2980,2983,2986,2989,2992,2994,2997],{"class":196,"line":689},[194,2955,2956],{"class":200},"  document",[194,2958,205],{"class":204},[194,2960,2961],{"class":208},"querySelector",[194,2963,302],{"class":348},[194,2965,759],{"class":758},[194,2967,2968],{"class":762},".audiowave",[194,2970,759],{"class":758},[194,2972,316],{"class":348},[194,2974,205],{"class":204},[194,2976,2977],{"class":200},"innerHTML",[194,2979,968],{"class":243},[194,2981,2982],{"class":758}," `",[194,2984,2985],{"class":762},"\u003Cdiv style=\"-webkit-animation-delay:",[194,2987,2988],{"class":758},"${",[194,2990,2991],{"class":200},"offset",[194,2993,2199],{"class":758},[194,2995,2996],{"class":762},"s;\">\u003C\u002Fdiv>",[194,2998,2999],{"class":758},"`\n",[194,3001,3002],{"class":196,"line":702},[194,3003,1038],{"class":204},[186,3005,3009],{"className":3006,"code":3007,"language":3008,"meta":74,"style":74},"language-css shiki shiki-themes material-theme-lighter github-light github-dark","body {\n  padding: 0;\n  margin: 0;\n}\n\n.background {\n  width: 100vw;\n  height: 100vh;\n  background-color: #193082;\n}\n\n.audiowave {\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: 100%;\n  height: 200px;\n  display: flex;\n  flex-direction: row;\n  align-items: flex-start;\n  justify-content: space-between;\n}\n\n.audiowave div {\n  top: 0;\n  background: rgba(44, 99, 255, 0.25);\n  z-index: 9;\n  width: 3px;\n  height: 0;\n  animation: audiowave 3s infinite linear;\n}\n\n@keyframes audiowave {\n  0% {\n    height: 28.75%;\n  }\n\n  5% {\n    height: 37.5%;\n  }\n\n  10% {\n    height: 56.25%;\n  }\n\n  15% {\n    height: 48.75%;\n  }\n\n  20% {\n    height: 68.75%;\n  }\n\n  25% {\n    height: 82.5%;\n  }\n\n  30% {\n    height: 71.25%;\n  }\n\n  35% {\n    height: 78.125%;\n  }\n\n  40% {\n    height: 68.75%;\n  }\n\n  45% {\n    height: 80.875%;\n  }\n\n  50% {\n    height: 90%;\n  }\n\n  55% {\n    height: 91.875%;\n  }\n\n  60% {\n    height: 87%;\n  }\n\n  65% {\n    height: 70%;\n  }\n\n  70% {\n    height: 60%;\n  }\n\n  75% {\n    height: 55%;\n  }\n\n  80% {\n    height: 45%;\n  }\n\n  85% {\n    height: 40%;\n  }\n\n  90% {\n    height: 35%;\n  }\n\n  95% {\n    height: 30%;\n  }\n\n  100% {\n    height: 28.75%;\n  }\n}\n","css",[116,3010,3011,3019,3032,3043,3047,3051,3060,3075,3089,3104,3108,3112,3120,3132,3143,3154,3167,3181,3193,3205,3217,3229,3233,3237,3248,3258,3291,3303,3315,3325,3349,3353,3357,3367,3374,3388,3393,3397,3404,3417,3421,3425,3433,3447,3452,3457,3465,3479,3484,3489,3497,3511,3516,3521,3529,3543,3548,3553,3561,3575,3580,3585,3593,3607,3612,3617,3625,3638,3643,3648,3656,3670,3675,3680,3688,3702,3707,3712,3720,3734,3739,3744,3752,3766,3771,3776,3784,3798,3803,3808,3816,3830,3835,3840,3848,3862,3867,3872,3880,3894,3899,3904,3912,3926,3931,3936,3944,3958,3963,3968,3976,3990,3995,4000,4008,4021,4026],{"__ignoreMap":74},[194,3012,3013,3017],{"class":196,"line":197},[194,3014,3016],{"class":3015},"s83vy","body",[194,3018,334],{"class":204},[194,3020,3021,3025,3027,3029],{"class":196,"line":75},[194,3022,3024],{"class":3023},"soE4H","  padding",[194,3026,309],{"class":204},[194,3028,2877],{"class":220},[194,3030,3031],{"class":204},";\n",[194,3033,3034,3037,3039,3041],{"class":196,"line":368},[194,3035,3036],{"class":3023},"  margin",[194,3038,309],{"class":204},[194,3040,2877],{"class":220},[194,3042,3031],{"class":204},[194,3044,3045],{"class":196,"line":391},[194,3046,1038],{"class":204},[194,3048,3049],{"class":196,"line":397},[194,3050,394],{"emptyLinePlaceholder":85},[194,3052,3053,3056,3058],{"class":196,"line":427},[194,3054,205],{"class":3055},"stp6e",[194,3057,2710],{"class":312},[194,3059,334],{"class":204},[194,3061,3062,3065,3067,3069,3073],{"class":196,"line":460},[194,3063,3064],{"class":3023},"  width",[194,3066,309],{"class":204},[194,3068,2812],{"class":220},[194,3070,3072],{"class":3071},"sw1J6","vw",[194,3074,3031],{"class":204},[194,3076,3077,3080,3082,3084,3087],{"class":196,"line":484},[194,3078,3079],{"class":3023},"  height",[194,3081,309],{"class":204},[194,3083,2812],{"class":220},[194,3085,3086],{"class":3071},"vh",[194,3088,3031],{"class":204},[194,3090,3091,3094,3096,3099,3102],{"class":196,"line":496},[194,3092,3093],{"class":3023},"  background-color",[194,3095,309],{"class":204},[194,3097,3098],{"class":1334}," #",[194,3100,3101],{"class":261},"193082",[194,3103,3031],{"class":204},[194,3105,3106],{"class":196,"line":523},[194,3107,1038],{"class":204},[194,3109,3110],{"class":196,"line":540},[194,3111,394],{"emptyLinePlaceholder":85},[194,3113,3114,3116,3118],{"class":196,"line":577},[194,3115,205],{"class":3055},[194,3117,2731],{"class":312},[194,3119,334],{"class":204},[194,3121,3122,3125,3127,3130],{"class":196,"line":598},[194,3123,3124],{"class":3023},"  position",[194,3126,309],{"class":204},[194,3128,3129],{"class":261}," absolute",[194,3131,3031],{"class":204},[194,3133,3134,3137,3139,3141],{"class":196,"line":618},[194,3135,3136],{"class":3023},"  left",[194,3138,309],{"class":204},[194,3140,2877],{"class":220},[194,3142,3031],{"class":204},[194,3144,3145,3148,3150,3152],{"class":196,"line":678},[194,3146,3147],{"class":3023},"  top",[194,3149,309],{"class":204},[194,3151,2877],{"class":220},[194,3153,3031],{"class":204},[194,3155,3156,3158,3160,3162,3165],{"class":196,"line":684},[194,3157,3064],{"class":3023},[194,3159,309],{"class":204},[194,3161,2812],{"class":220},[194,3163,3164],{"class":3071},"%",[194,3166,3031],{"class":204},[194,3168,3169,3171,3173,3176,3179],{"class":196,"line":689},[194,3170,3079],{"class":3023},[194,3172,309],{"class":204},[194,3174,3175],{"class":220}," 200",[194,3177,3178],{"class":3071},"px",[194,3180,3031],{"class":204},[194,3182,3183,3186,3188,3191],{"class":196,"line":702},[194,3184,3185],{"class":3023},"  display",[194,3187,309],{"class":204},[194,3189,3190],{"class":261}," flex",[194,3192,3031],{"class":204},[194,3194,3195,3198,3200,3203],{"class":196,"line":708},[194,3196,3197],{"class":3023},"  flex-direction",[194,3199,309],{"class":204},[194,3201,3202],{"class":261}," row",[194,3204,3031],{"class":204},[194,3206,3207,3210,3212,3215],{"class":196,"line":714},[194,3208,3209],{"class":3023},"  align-items",[194,3211,309],{"class":204},[194,3213,3214],{"class":261}," flex-start",[194,3216,3031],{"class":204},[194,3218,3219,3222,3224,3227],{"class":196,"line":719},[194,3220,3221],{"class":3023},"  justify-content",[194,3223,309],{"class":204},[194,3225,3226],{"class":261}," space-between",[194,3228,3031],{"class":204},[194,3230,3231],{"class":196,"line":745},[194,3232,1038],{"class":204},[194,3234,3235],{"class":196,"line":774},[194,3236,394],{"emptyLinePlaceholder":85},[194,3238,3239,3241,3243,3246],{"class":196,"line":786},[194,3240,205],{"class":3055},[194,3242,2731],{"class":312},[194,3244,3245],{"class":3015}," div",[194,3247,334],{"class":204},[194,3249,3250,3252,3254,3256],{"class":196,"line":791},[194,3251,3147],{"class":3023},[194,3253,309],{"class":204},[194,3255,2877],{"class":220},[194,3257,3031],{"class":204},[194,3259,3260,3263,3265,3268,3270,3273,3275,3278,3280,3283,3285,3288],{"class":196,"line":796},[194,3261,3262],{"class":3023},"  background",[194,3264,309],{"class":204},[194,3266,3267],{"class":1460}," rgba",[194,3269,302],{"class":204},[194,3271,3272],{"class":220},"44",[194,3274,227],{"class":204},[194,3276,3277],{"class":220}," 99",[194,3279,227],{"class":204},[194,3281,3282],{"class":220}," 255",[194,3284,227],{"class":204},[194,3286,3287],{"class":220}," 0.25",[194,3289,3290],{"class":204},");\n",[194,3292,3293,3296,3298,3301],{"class":196,"line":824},[194,3294,3295],{"class":3023},"  z-index",[194,3297,309],{"class":204},[194,3299,3300],{"class":220}," 9",[194,3302,3031],{"class":204},[194,3304,3305,3307,3309,3311,3313],{"class":196,"line":838},[194,3306,3064],{"class":3023},[194,3308,309],{"class":204},[194,3310,2842],{"class":220},[194,3312,3178],{"class":3071},[194,3314,3031],{"class":204},[194,3316,3317,3319,3321,3323],{"class":196,"line":885},[194,3318,3079],{"class":3023},[194,3320,309],{"class":204},[194,3322,2877],{"class":220},[194,3324,3031],{"class":204},[194,3326,3327,3330,3332,3335,3338,3341,3344,3347],{"class":196,"line":899},[194,3328,3329],{"class":3023},"  animation",[194,3331,309],{"class":204},[194,3333,3334],{"class":200}," audiowave ",[194,3336,3337],{"class":220},"3",[194,3339,3340],{"class":3071},"s",[194,3342,3343],{"class":261}," infinite",[194,3345,3346],{"class":261}," linear",[194,3348,3031],{"class":204},[194,3350,3351],{"class":196,"line":912},[194,3352,1038],{"class":204},[194,3354,3355],{"class":196,"line":918},[194,3356,394],{"emptyLinePlaceholder":85},[194,3358,3359,3362,3365],{"class":196,"line":923},[194,3360,3361],{"class":339},"@keyframes",[194,3363,3364],{"class":305}," audiowave",[194,3366,334],{"class":204},[194,3368,3369,3372],{"class":196,"line":928},[194,3370,3371],{"class":312},"  0%",[194,3373,334],{"class":204},[194,3375,3376,3379,3381,3384,3386],{"class":196,"line":946},[194,3377,3378],{"class":3023},"    height",[194,3380,309],{"class":204},[194,3382,3383],{"class":220}," 28.75",[194,3385,3164],{"class":3071},[194,3387,3031],{"class":204},[194,3389,3390],{"class":196,"line":962},[194,3391,3392],{"class":204},"  }\n",[194,3394,3395],{"class":196,"line":978},[194,3396,394],{"emptyLinePlaceholder":85},[194,3398,3399,3402],{"class":196,"line":996},[194,3400,3401],{"class":312},"  5%",[194,3403,334],{"class":204},[194,3405,3406,3408,3410,3413,3415],{"class":196,"line":1019},[194,3407,3378],{"class":3023},[194,3409,309],{"class":204},[194,3411,3412],{"class":220}," 37.5",[194,3414,3164],{"class":3071},[194,3416,3031],{"class":204},[194,3418,3419],{"class":196,"line":1027},[194,3420,3392],{"class":204},[194,3422,3423],{"class":196,"line":1035},[194,3424,394],{"emptyLinePlaceholder":85},[194,3426,3428,3431],{"class":196,"line":3427},42,[194,3429,3430],{"class":312},"  10%",[194,3432,334],{"class":204},[194,3434,3436,3438,3440,3443,3445],{"class":196,"line":3435},43,[194,3437,3378],{"class":3023},[194,3439,309],{"class":204},[194,3441,3442],{"class":220}," 56.25",[194,3444,3164],{"class":3071},[194,3446,3031],{"class":204},[194,3448,3450],{"class":196,"line":3449},44,[194,3451,3392],{"class":204},[194,3453,3455],{"class":196,"line":3454},45,[194,3456,394],{"emptyLinePlaceholder":85},[194,3458,3460,3463],{"class":196,"line":3459},46,[194,3461,3462],{"class":312},"  15%",[194,3464,334],{"class":204},[194,3466,3468,3470,3472,3475,3477],{"class":196,"line":3467},47,[194,3469,3378],{"class":3023},[194,3471,309],{"class":204},[194,3473,3474],{"class":220}," 48.75",[194,3476,3164],{"class":3071},[194,3478,3031],{"class":204},[194,3480,3482],{"class":196,"line":3481},48,[194,3483,3392],{"class":204},[194,3485,3487],{"class":196,"line":3486},49,[194,3488,394],{"emptyLinePlaceholder":85},[194,3490,3492,3495],{"class":196,"line":3491},50,[194,3493,3494],{"class":312},"  20%",[194,3496,334],{"class":204},[194,3498,3500,3502,3504,3507,3509],{"class":196,"line":3499},51,[194,3501,3378],{"class":3023},[194,3503,309],{"class":204},[194,3505,3506],{"class":220}," 68.75",[194,3508,3164],{"class":3071},[194,3510,3031],{"class":204},[194,3512,3514],{"class":196,"line":3513},52,[194,3515,3392],{"class":204},[194,3517,3519],{"class":196,"line":3518},53,[194,3520,394],{"emptyLinePlaceholder":85},[194,3522,3524,3527],{"class":196,"line":3523},54,[194,3525,3526],{"class":312},"  25%",[194,3528,334],{"class":204},[194,3530,3532,3534,3536,3539,3541],{"class":196,"line":3531},55,[194,3533,3378],{"class":3023},[194,3535,309],{"class":204},[194,3537,3538],{"class":220}," 82.5",[194,3540,3164],{"class":3071},[194,3542,3031],{"class":204},[194,3544,3546],{"class":196,"line":3545},56,[194,3547,3392],{"class":204},[194,3549,3551],{"class":196,"line":3550},57,[194,3552,394],{"emptyLinePlaceholder":85},[194,3554,3556,3559],{"class":196,"line":3555},58,[194,3557,3558],{"class":312},"  30%",[194,3560,334],{"class":204},[194,3562,3564,3566,3568,3571,3573],{"class":196,"line":3563},59,[194,3565,3378],{"class":3023},[194,3567,309],{"class":204},[194,3569,3570],{"class":220}," 71.25",[194,3572,3164],{"class":3071},[194,3574,3031],{"class":204},[194,3576,3578],{"class":196,"line":3577},60,[194,3579,3392],{"class":204},[194,3581,3583],{"class":196,"line":3582},61,[194,3584,394],{"emptyLinePlaceholder":85},[194,3586,3588,3591],{"class":196,"line":3587},62,[194,3589,3590],{"class":312},"  35%",[194,3592,334],{"class":204},[194,3594,3596,3598,3600,3603,3605],{"class":196,"line":3595},63,[194,3597,3378],{"class":3023},[194,3599,309],{"class":204},[194,3601,3602],{"class":220}," 78.125",[194,3604,3164],{"class":3071},[194,3606,3031],{"class":204},[194,3608,3610],{"class":196,"line":3609},64,[194,3611,3392],{"class":204},[194,3613,3615],{"class":196,"line":3614},65,[194,3616,394],{"emptyLinePlaceholder":85},[194,3618,3620,3623],{"class":196,"line":3619},66,[194,3621,3622],{"class":312},"  40%",[194,3624,334],{"class":204},[194,3626,3628,3630,3632,3634,3636],{"class":196,"line":3627},67,[194,3629,3378],{"class":3023},[194,3631,309],{"class":204},[194,3633,3506],{"class":220},[194,3635,3164],{"class":3071},[194,3637,3031],{"class":204},[194,3639,3641],{"class":196,"line":3640},68,[194,3642,3392],{"class":204},[194,3644,3646],{"class":196,"line":3645},69,[194,3647,394],{"emptyLinePlaceholder":85},[194,3649,3651,3654],{"class":196,"line":3650},70,[194,3652,3653],{"class":312},"  45%",[194,3655,334],{"class":204},[194,3657,3659,3661,3663,3666,3668],{"class":196,"line":3658},71,[194,3660,3378],{"class":3023},[194,3662,309],{"class":204},[194,3664,3665],{"class":220}," 80.875",[194,3667,3164],{"class":3071},[194,3669,3031],{"class":204},[194,3671,3673],{"class":196,"line":3672},72,[194,3674,3392],{"class":204},[194,3676,3678],{"class":196,"line":3677},73,[194,3679,394],{"emptyLinePlaceholder":85},[194,3681,3683,3686],{"class":196,"line":3682},74,[194,3684,3685],{"class":312},"  50%",[194,3687,334],{"class":204},[194,3689,3691,3693,3695,3698,3700],{"class":196,"line":3690},75,[194,3692,3378],{"class":3023},[194,3694,309],{"class":204},[194,3696,3697],{"class":220}," 90",[194,3699,3164],{"class":3071},[194,3701,3031],{"class":204},[194,3703,3705],{"class":196,"line":3704},76,[194,3706,3392],{"class":204},[194,3708,3710],{"class":196,"line":3709},77,[194,3711,394],{"emptyLinePlaceholder":85},[194,3713,3715,3718],{"class":196,"line":3714},78,[194,3716,3717],{"class":312},"  55%",[194,3719,334],{"class":204},[194,3721,3723,3725,3727,3730,3732],{"class":196,"line":3722},79,[194,3724,3378],{"class":3023},[194,3726,309],{"class":204},[194,3728,3729],{"class":220}," 91.875",[194,3731,3164],{"class":3071},[194,3733,3031],{"class":204},[194,3735,3737],{"class":196,"line":3736},80,[194,3738,3392],{"class":204},[194,3740,3742],{"class":196,"line":3741},81,[194,3743,394],{"emptyLinePlaceholder":85},[194,3745,3747,3750],{"class":196,"line":3746},82,[194,3748,3749],{"class":312},"  60%",[194,3751,334],{"class":204},[194,3753,3755,3757,3759,3762,3764],{"class":196,"line":3754},83,[194,3756,3378],{"class":3023},[194,3758,309],{"class":204},[194,3760,3761],{"class":220}," 87",[194,3763,3164],{"class":3071},[194,3765,3031],{"class":204},[194,3767,3769],{"class":196,"line":3768},84,[194,3770,3392],{"class":204},[194,3772,3774],{"class":196,"line":3773},85,[194,3775,394],{"emptyLinePlaceholder":85},[194,3777,3779,3782],{"class":196,"line":3778},86,[194,3780,3781],{"class":312},"  65%",[194,3783,334],{"class":204},[194,3785,3787,3789,3791,3794,3796],{"class":196,"line":3786},87,[194,3788,3378],{"class":3023},[194,3790,309],{"class":204},[194,3792,3793],{"class":220}," 70",[194,3795,3164],{"class":3071},[194,3797,3031],{"class":204},[194,3799,3801],{"class":196,"line":3800},88,[194,3802,3392],{"class":204},[194,3804,3806],{"class":196,"line":3805},89,[194,3807,394],{"emptyLinePlaceholder":85},[194,3809,3811,3814],{"class":196,"line":3810},90,[194,3812,3813],{"class":312},"  70%",[194,3815,334],{"class":204},[194,3817,3819,3821,3823,3826,3828],{"class":196,"line":3818},91,[194,3820,3378],{"class":3023},[194,3822,309],{"class":204},[194,3824,3825],{"class":220}," 60",[194,3827,3164],{"class":3071},[194,3829,3031],{"class":204},[194,3831,3833],{"class":196,"line":3832},92,[194,3834,3392],{"class":204},[194,3836,3838],{"class":196,"line":3837},93,[194,3839,394],{"emptyLinePlaceholder":85},[194,3841,3843,3846],{"class":196,"line":3842},94,[194,3844,3845],{"class":312},"  75%",[194,3847,334],{"class":204},[194,3849,3851,3853,3855,3858,3860],{"class":196,"line":3850},95,[194,3852,3378],{"class":3023},[194,3854,309],{"class":204},[194,3856,3857],{"class":220}," 55",[194,3859,3164],{"class":3071},[194,3861,3031],{"class":204},[194,3863,3865],{"class":196,"line":3864},96,[194,3866,3392],{"class":204},[194,3868,3870],{"class":196,"line":3869},97,[194,3871,394],{"emptyLinePlaceholder":85},[194,3873,3875,3878],{"class":196,"line":3874},98,[194,3876,3877],{"class":312},"  80%",[194,3879,334],{"class":204},[194,3881,3883,3885,3887,3890,3892],{"class":196,"line":3882},99,[194,3884,3378],{"class":3023},[194,3886,309],{"class":204},[194,3888,3889],{"class":220}," 45",[194,3891,3164],{"class":3071},[194,3893,3031],{"class":204},[194,3895,3897],{"class":196,"line":3896},100,[194,3898,3392],{"class":204},[194,3900,3902],{"class":196,"line":3901},101,[194,3903,394],{"emptyLinePlaceholder":85},[194,3905,3907,3910],{"class":196,"line":3906},102,[194,3908,3909],{"class":312},"  85%",[194,3911,334],{"class":204},[194,3913,3915,3917,3919,3922,3924],{"class":196,"line":3914},103,[194,3916,3378],{"class":3023},[194,3918,309],{"class":204},[194,3920,3921],{"class":220}," 40",[194,3923,3164],{"class":3071},[194,3925,3031],{"class":204},[194,3927,3929],{"class":196,"line":3928},104,[194,3930,3392],{"class":204},[194,3932,3934],{"class":196,"line":3933},105,[194,3935,394],{"emptyLinePlaceholder":85},[194,3937,3939,3942],{"class":196,"line":3938},106,[194,3940,3941],{"class":312},"  90%",[194,3943,334],{"class":204},[194,3945,3947,3949,3951,3954,3956],{"class":196,"line":3946},107,[194,3948,3378],{"class":3023},[194,3950,309],{"class":204},[194,3952,3953],{"class":220}," 35",[194,3955,3164],{"class":3071},[194,3957,3031],{"class":204},[194,3959,3961],{"class":196,"line":3960},108,[194,3962,3392],{"class":204},[194,3964,3966],{"class":196,"line":3965},109,[194,3967,394],{"emptyLinePlaceholder":85},[194,3969,3971,3974],{"class":196,"line":3970},110,[194,3972,3973],{"class":312},"  95%",[194,3975,334],{"class":204},[194,3977,3979,3981,3983,3986,3988],{"class":196,"line":3978},111,[194,3980,3378],{"class":3023},[194,3982,309],{"class":204},[194,3984,3985],{"class":220}," 30",[194,3987,3164],{"class":3071},[194,3989,3031],{"class":204},[194,3991,3993],{"class":196,"line":3992},112,[194,3994,3392],{"class":204},[194,3996,3998],{"class":196,"line":3997},113,[194,3999,394],{"emptyLinePlaceholder":85},[194,4001,4003,4006],{"class":196,"line":4002},114,[194,4004,4005],{"class":312},"  100%",[194,4007,334],{"class":204},[194,4009,4011,4013,4015,4017,4019],{"class":196,"line":4010},115,[194,4012,3378],{"class":3023},[194,4014,309],{"class":204},[194,4016,3383],{"class":220},[194,4018,3164],{"class":3071},[194,4020,3031],{"class":204},[194,4022,4024],{"class":196,"line":4023},116,[194,4025,3392],{"class":204},[194,4027,4029],{"class":196,"line":4028},117,[194,4030,1038],{"class":204},[1128,4032,4033],{"id":4033},"代码解析",[32,4035,4036,4037,4040,4041,2459,4043,4046],{},"核心还是通过 ",[116,4038,4039],{},"animation-delay"," 动画延迟来实现，",[116,4042,2731],{},[116,4044,4045],{},"keyframes"," 是根据设计图中一个完整波形的大致高度来调整的，不需要太精确，因为我们会加一些随机数进去，这样可以使每个波形之间不完全一致，不然就太死板了。",[32,4048,4049,4052],{},[116,4050,4051],{},"wavePillarCount"," 是指整个声波中柱子的数量，可以根据总宽度和每个柱子的宽度按需调整。",[32,4054,4055,4058,4059,4062,4063,4065],{},[116,4056,4057],{},"waveCount"," 是波的数量，可以尝试把下面的 ",[116,4060,4061],{},"randomRate"," 设为 ",[116,4064,237],{}," 就可以清楚地看出来了。",[32,4067,4068,4070,4071,4073,4074,4062,4076,4078],{},[116,4069,4061],{}," 如上所述，是添加的随机延迟倍率，以便增加波形的杂音，不至于太死板。值越大杂音越大，值为 ",[116,4072,237],{}," 的时候没有杂音。下面是把 ",[116,4075,4061],{},[116,4077,237],{}," 的效果。",[1209,4080],{"filename":4081},"02.gif",[32,4083,4084,4085,4087,4088,4090,4091,4093,4094,4096],{},"另外，",[116,4086,2991],{}," 计算的时候之所以要减去 ",[116,4089,221],{},"，是为了保证 ",[116,4092,2991],{}," 的值为负数，这样可以保证动画在一开始就是完整的，不然部分波形一开始将不完整，等到了那个 ",[116,4095,2991],{}," 时才会开始动。",[32,4098,4099,4100,4102,4103,4105,4106,4105,4108,4110],{},"基本上就是这样，通过通过 ",[116,4101,4045],{}," 来设置初始波形，通过微调 ",[116,4104,4051],{},"、 ",[116,4107,4057],{},[116,4109,4061],{}," 来获得更加的效果。",[1061,4112,4113],{},"html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sQzsp, html code.shiki .sQzsp{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s9AJx, html code.shiki .s9AJx{--shiki-light:#9C3EDA;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s83vy, html code.shiki .s83vy{--shiki-light:#E2931D;--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .soE4H, html code.shiki .soE4H{--shiki-light:#8796B0;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .stp6e, html code.shiki .stp6e{--shiki-light:#39ADB5;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sw1J6, html code.shiki .sw1J6{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}",{"title":74,"searchDepth":75,"depth":75,"links":4115},[],"png","2019-03-24",{},"\u002Fposts\u002F2019\u002Faudiowave-animation",{"text":4121,"minutes":368,"time":4122,"words":4123},"3 min read",180000,600,{"title":2660,"description":2665},{"loc":4119},"posts\u002F2019\u002F20190324.audiowave-animation",[1083,97],"天气晴","sgdV0_o07zLLyyGhpqqjl1ZVLmOMtBlps06AjCbHWu0",{"id":4131,"title":4132,"body":4133,"class":79,"cover":79,"coverSize":79,"date":4728,"description":4137,"draft":82,"extension":83,"hideComments":82,"location":79,"meta":4729,"navigation":85,"path":4730,"readingTime":4731,"seo":4736,"sitemap":4737,"stem":4738,"tags":4739,"time":4742,"weather":4743,"__hash__":4744},"posts\u002Fposts\u002F2018\u002F20180414.frontend-components-and-docker-deploy.md","前端跨项目组件化及基于 Docker 的快速部署方案",{"type":25,"value":4134,"toc":4724},[4135,4138,4142,4145,4169,4172,4226,4251,4270,4280,4328,4335,4341,4388,4394,4403,4406,4446,4504,4522,4526,4529,4532,4644,4654,4664,4711,4718,4721],[32,4136,4137],{},"最近静下心来写了几个项目，花了些时间重新整理了整套组件化方案和部署方案，记录一下。",[4139,4140,4141],"h3",{"id":4141},"跨项目组件化",[32,4143,4144],{},"前端的组件化不用多说了，发展到现在，无论是 React 的还是 Vue，都提供了相当方便的组件化实现。在日常项目中，有些组件其实是可以跨多个项目使用的，将这些组件抽离出来作为单独项目，并复用到其他项目中去，一来可以避免重复造轮子，加快开发速度，二来维护效率也高，一些 bugfix 或者新特性直接在组件中更新，项目中只需要更新引用版本号即可，方便快捷。",[32,4146,4147,4148,4151,4152,4155,4156,4158,4159,4161,4162,4165,4166,4168],{},"跨项目的组件化方式也很多，开发阶段可以用 ",[116,4149,4150],{},"npm link","，相当于在主项目的 ",[116,4153,4154],{},"node_modules"," 目录中创建了一个链向组件项目的软链，方便是挺方便，但是有几个问题。一是 Eslint 的目录递归检查是基于最终实际目录的，也就是说虽然 Eslint 默认排除 ",[116,4157,4154],{}," 目录，但它依然会对该目录中的软链项目进行检查，一旦组件项目的 Eslint 规则和主项目的 Eslint 不一致的话，主项目 Eslint 就没法通过，这个比较蛋疼，就得临时禁用 Eslint 或者修改组件项目的规则。作为组件项目应该保证少依赖，而且要服务多个项目，没办法保证匹配各个项目的 Eslint 规则。第二个问题是，通过 ",[116,4160,4150],{}," 实现的依赖，不会体现在 ",[116,4163,4164],{},"package.json"," 中，如果通过 Docker 去部署，在 Docker 上是不知道你这个软链的，即便能够把软链写进去，在 Docker 中构建的时候，由于目录问题，也不能保证可以把组件项目文件拷过去。因此，在生产中，",[116,4167,4150],{}," 这个方案是没办法用的。",[32,4170,4171],{},"之前在国资的时候，采用的是通过组件项目的 git 地址来定位包，使用起来也很方便。如下：",[186,4173,4177],{"className":4174,"code":4175,"language":4176,"meta":74,"style":74},"language-json shiki shiki-themes material-theme-lighter github-light github-dark","{\n  \"dependencies\": {\n    \"example-component\": \"git+ssh:\u002F\u002Fgit@xxx.com\u002Fexample-component.git#v1.0.0\"\n  }\n}\n","json",[116,4178,4179,4183,4198,4218,4222],{"__ignoreMap":74},[194,4180,4181],{"class":196,"line":197},[194,4182,457],{"class":204},[194,4184,4185,4188,4192,4194,4196],{"class":196,"line":75},[194,4186,4187],{"class":1334},"  \"",[194,4189,4191],{"class":4190},"sseR_","dependencies",[194,4193,1450],{"class":1334},[194,4195,309],{"class":204},[194,4197,334],{"class":204},[194,4199,4200,4203,4206,4208,4210,4213,4216],{"class":196,"line":368},[194,4201,4202],{"class":1334},"    \"",[194,4204,4205],{"class":327},"example-component",[194,4207,1450],{"class":1334},[194,4209,309],{"class":204},[194,4211,4212],{"class":758}," \"",[194,4214,4215],{"class":762},"git+ssh:\u002F\u002Fgit@xxx.com\u002Fexample-component.git#v1.0.0",[194,4217,2224],{"class":758},[194,4219,4220],{"class":196,"line":391},[194,4221,3392],{"class":204},[194,4223,4224],{"class":196,"line":397},[194,4225,1038],{"class":204},[32,4227,4228,4229,4232,4233,4236,4237,4239,4240,4243,4244,4246,4247,4250],{},"npm 支持通过 tag 来定位版本，组件项目发布的时候打一个 tag，对应的在项目中更新最后的版本号重新安装就可以完成升级了。由于之前没有用 Docker 部署，所以也没发现有什么问题。用了 Docker 的话，就会有些小问题。一般为了精简，都会采用基于 ",[116,4230,4231],{},"Alpine"," 的基础镜像，我目前的前端项目都是基于 ",[116,4234,4235],{},"node:8-alpine"," 来构建的。要知道，",[116,4238,4231],{}," 镜像本身只有 4.8M，",[116,4241,4242],{},"node:8-apline"," 也只有 20 几兆，非常精简。但是通过上面的这种方式，需要依赖 git，而 ",[116,4245,4231],{}," 显然是没有安装 ",[116,4248,4249],{},"git"," 的，也没有必要为了部署专门去安装一个 git。",[32,4252,4253,4254,4257,4258,4261,4262,4265,4266,4269],{},"于是便有了第三种方案，基于 Nexus 的 npm repository 方案。把包发布到 npm 上不太现实，大部分公司项目还是希望私有，和 maven 一样，Nexus 也支持 npm。创建一个 ",[116,4255,4256],{},"hosted"," 类型的 npm 仓库，例如 ",[116,4259,4260],{},"npm-hosted","，具体教程自行谷歌。但是我们又不希望给该仓库增加过多的压力，不想把所有 npm 或者 yarn 默认的 registry 改为 Nexus，没必要，因为即便改为 Nexus，在国内的网络环境，还是 proxy 到 ",[116,4263,4264],{},"https:\u002F\u002Fregistry.npm.taobao.org"," 上去了，而且会在 Nexus 上留下大量缓存，也经过了两层的下载，我尝试过，很蛋疼，在 Nexus 没有缓存第一次去下载的时候，还会有很多失败。后来发现 npm 也支持直接通过 ",[116,4267,4268],{},"tgz"," 文件的方式来引用，这样就好办了。",[32,4271,4272,4273,4276,4277,4279],{},"首先执行 ",[116,4274,4275],{},"npm adduser --registry=https:\u002F\u002Fmyregistry.example.com","，输入 Nexus 上具有上传 npm 包权限的用户名和密码，会在本地记录该用户的登录认证。然后在组件项目的 ",[116,4278,4164],{}," 中加入：",[186,4281,4283],{"className":4174,"code":4282,"language":4176,"meta":74,"style":74},"{\n  \"publishConfig\": {\n    \"registry\": \"https:\u002F\u002Fmyregistry.example.com\u002Frepository\u002Fnpm-hosted\u002F\"\n  }\n}\n",[116,4284,4285,4289,4302,4320,4324],{"__ignoreMap":74},[194,4286,4287],{"class":196,"line":197},[194,4288,457],{"class":204},[194,4290,4291,4293,4296,4298,4300],{"class":196,"line":75},[194,4292,4187],{"class":1334},[194,4294,4295],{"class":4190},"publishConfig",[194,4297,1450],{"class":1334},[194,4299,309],{"class":204},[194,4301,334],{"class":204},[194,4303,4304,4306,4309,4311,4313,4315,4318],{"class":196,"line":368},[194,4305,4202],{"class":1334},[194,4307,4308],{"class":327},"registry",[194,4310,1450],{"class":1334},[194,4312,309],{"class":204},[194,4314,4212],{"class":758},[194,4316,4317],{"class":762},"https:\u002F\u002Fmyregistry.example.com\u002Frepository\u002Fnpm-hosted\u002F",[194,4319,2224],{"class":758},[194,4321,4322],{"class":196,"line":391},[194,4323,3392],{"class":204},[194,4325,4326],{"class":196,"line":397},[194,4327,1038],{"class":204},[32,4329,4330,4331,4334],{},"然后执行 ",[116,4332,4333],{},"npm publish","，组件项目就会被上传到 Nexus 了。",[32,4336,4337,4338,4340],{},"在具体项目中，需要用到改组件的时候，在 ",[116,4339,4164],{}," 中这样引用：",[186,4342,4344],{"className":4174,"code":4343,"language":4176,"meta":74,"style":74},"{\n  \"dependencies\": {\n    \"vue-footer\": \"https:\u002F\u002Fmyregistry.example.com\u002Frepository\u002Fnpm-hosted\u002Fexample-component\u002F-\u002Fexample-component-1.0.0.tgz\"\n  }\n}\n",[116,4345,4346,4350,4362,4380,4384],{"__ignoreMap":74},[194,4347,4348],{"class":196,"line":197},[194,4349,457],{"class":204},[194,4351,4352,4354,4356,4358,4360],{"class":196,"line":75},[194,4353,4187],{"class":1334},[194,4355,4191],{"class":4190},[194,4357,1450],{"class":1334},[194,4359,309],{"class":204},[194,4361,334],{"class":204},[194,4363,4364,4366,4369,4371,4373,4375,4378],{"class":196,"line":368},[194,4365,4202],{"class":1334},[194,4367,4368],{"class":327},"vue-footer",[194,4370,1450],{"class":1334},[194,4372,309],{"class":204},[194,4374,4212],{"class":758},[194,4376,4377],{"class":762},"https:\u002F\u002Fmyregistry.example.com\u002Frepository\u002Fnpm-hosted\u002Fexample-component\u002F-\u002Fexample-component-1.0.0.tgz",[194,4379,2224],{"class":758},[194,4381,4382],{"class":196,"line":391},[194,4383,3392],{"class":204},[194,4385,4386],{"class":196,"line":397},[194,4387,1038],{"class":204},[32,4389,4390,4391,2587],{},"或者直接 ",[116,4392,4393],{},"npm install https:\u002F\u002Fmyregistry.example.com\u002Frepository\u002Fnpm-hosted\u002Fexample-component\u002F-\u002Fexample-component-1.0.0.tgz --save",[32,4395,4396,4397,4399,4400,4402],{},"组件更新通过修改 ",[116,4398,4164],{}," 里的版本号，然后 ",[116,4401,4333],{},"，然后具体项目中修改最后的版本号，重新安装即可。",[32,4404,4405],{},"另外由于这次都是用的最新的组件，也遇到了一个很大的坑。",[32,4407,4408,4409,4412,4413,4416,4417,4062,4420,4423,4424,2459,4427,4430,4431,4434,4435,4439,4440,4445],{},"我的项目都是基于 Nuxt.js 来搞的，Nuxt.js 的框架用起来更爽，ssr 性能也比 vue 原生的要高。有一个问题，组件项目在 webpack 打包的时候，默认是不支持 Nuxt 的 SSR 的，用了 ",[116,4410,4411],{},"vue-style-loader"," 之后，里面会有多个地方用到了 ",[116,4414,4415],{},"document","。如果需要同时支持 Browser 和 SSR，需要再建一个 SSR 的 webpack config，将 ",[116,4418,4419],{},"target",[116,4421,4422],{},"node","，并且 ",[116,4425,4426],{},"vue-loader",[116,4428,4429],{},"options"," 中需要加入 ",[116,4432,4433],{},"optimizeSSR: false","，这个是因为尤大在最近的某个版本中针对 SSR 做了一些优化，但是在 Nuxt 的 SSR 中会有些问题，具体可以参见 ",[36,4436,4437],{"href":4437,"rel":4438},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fnuxt.js\u002Fissues\u002F2565",[40]," ，找了很久找到了这个 issue，追踪了下，尤大貌似在最近的 ",[36,4441,4444],{"href":4442,"rel":4443},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fvue\u002Fcommit\u002F9b22d86ab315a3c6061a6a4776eab1964304f92e",[40],"v2.5.17-beta.0"," 中已经修复了这个问题，具体等 release 版发布之后再试下。在 Nuxt 中，创建一个 plugin，直接引用生成的 SSR 版本的文件即可。",[186,4447,4449],{"className":2752,"code":4448,"language":2754,"meta":74,"style":74},"import ExampleComponent from 'example-component\u002Fdist\u002Fssr.js'\nimport Vue from 'vue'\n\nVue.use(ExampleComponent)\n",[116,4450,4451,4471,4487,4491],{"__ignoreMap":74},[194,4452,4453,4456,4459,4462,4465,4468],{"class":196,"line":197},[194,4454,4455],{"class":339},"import",[194,4457,4458],{"class":200}," ExampleComponent ",[194,4460,4461],{"class":339},"from",[194,4463,4464],{"class":758}," '",[194,4466,4467],{"class":762},"example-component\u002Fdist\u002Fssr.js",[194,4469,4470],{"class":758},"'\n",[194,4472,4473,4475,4478,4480,4482,4485],{"class":196,"line":75},[194,4474,4455],{"class":339},[194,4476,4477],{"class":200}," Vue ",[194,4479,4461],{"class":339},[194,4481,4464],{"class":758},[194,4483,4484],{"class":762},"vue",[194,4486,4470],{"class":758},[194,4488,4489],{"class":196,"line":368},[194,4490,394],{"emptyLinePlaceholder":85},[194,4492,4493,4496,4498,4501],{"class":196,"line":391},[194,4494,4495],{"class":200},"Vue",[194,4497,205],{"class":204},[194,4499,4500],{"class":208},"use",[194,4502,4503],{"class":200},"(ExampleComponent)\n",[32,4505,4506,4507,2459,4510,4513,4514,4517,4518,4521],{},"在 ",[116,4508,4509],{},"nuxt.config.js",[116,4511,4512],{},"plugins"," 中直接加入 ",[116,4515,4516],{},"'~plugins\u002Fcomponents-plugin.js'"," 即可。网上大部分解决方案是引用的时候将组件项目设置为 ",[116,4519,4520],{},"ssr: false","，其实是治标不治本，放弃了该组件在服务端的渲染，不可取。",[4139,4523,4525],{"id":4524},"基于-docker-的快速部署","基于 Docker 的快速部署",[32,4527,4528],{},"使用 Docker 也快 1 年了，基本上从开始用上 Docker 之后，就爱不释手了，大大缩短了发布时间，减少了运维成本。",[32,4530,4531],{},"目前我的项目都是部署在阿里云上，基于阿里云的容器集群方案，前面通过 SLB，后面横向部署多台机器。不多说，贴下 Dockerfile：",[186,4533,4537],{"className":4534,"code":4535,"language":4536,"meta":74,"style":74},"language-dockerfile shiki shiki-themes material-theme-lighter github-light github-dark","FROM node:8-alpine\n\nWORKDIR \u002Fapp\n\nCOPY package.json \u002Fapp\nCOPY yarn.lock \u002Fapp\nRUN npm config set registry https:\u002F\u002Fregistry.npm.taobao.org && yarn config set registry https:\u002F\u002Fregistry.npm.taobao.org && yarn install\nCOPY . \u002Fapp\nRUN npm run build\n\nEXPOSE 6002\nENV SERVER_ENV $SERVER_ENV\nCMD [\"npm\", \"run\", \"start\"]\n","dockerfile",[116,4538,4539,4547,4551,4559,4563,4571,4578,4586,4593,4600,4604,4612,4620],{"__ignoreMap":74},[194,4540,4541,4544],{"class":196,"line":197},[194,4542,4543],{"class":3071},"FROM",[194,4545,4546],{"class":200}," node:8-alpine\n",[194,4548,4549],{"class":196,"line":75},[194,4550,394],{"emptyLinePlaceholder":85},[194,4552,4553,4556],{"class":196,"line":368},[194,4554,4555],{"class":3071},"WORKDIR",[194,4557,4558],{"class":200}," \u002Fapp\n",[194,4560,4561],{"class":196,"line":391},[194,4562,394],{"emptyLinePlaceholder":85},[194,4564,4565,4568],{"class":196,"line":397},[194,4566,4567],{"class":3071},"COPY",[194,4569,4570],{"class":200}," package.json \u002Fapp\n",[194,4572,4573,4575],{"class":196,"line":427},[194,4574,4567],{"class":3071},[194,4576,4577],{"class":200}," yarn.lock \u002Fapp\n",[194,4579,4580,4583],{"class":196,"line":460},[194,4581,4582],{"class":3071},"RUN",[194,4584,4585],{"class":200}," npm config set registry https:\u002F\u002Fregistry.npm.taobao.org && yarn config set registry https:\u002F\u002Fregistry.npm.taobao.org && yarn install\n",[194,4587,4588,4590],{"class":196,"line":484},[194,4589,4567],{"class":3071},[194,4591,4592],{"class":200}," . \u002Fapp\n",[194,4594,4595,4597],{"class":196,"line":496},[194,4596,4582],{"class":3071},[194,4598,4599],{"class":200}," npm run build\n",[194,4601,4602],{"class":196,"line":523},[194,4603,394],{"emptyLinePlaceholder":85},[194,4605,4606,4609],{"class":196,"line":540},[194,4607,4608],{"class":3071},"EXPOSE",[194,4610,4611],{"class":200}," 6002\n",[194,4613,4614,4617],{"class":196,"line":577},[194,4615,4616],{"class":3071},"ENV",[194,4618,4619],{"class":200}," SERVER_ENV $SERVER_ENV\n",[194,4621,4622,4625,4628,4631,4634,4637,4639,4642],{"class":196,"line":598},[194,4623,4624],{"class":3071},"CMD",[194,4626,4627],{"class":200}," [",[194,4629,4630],{"class":762},"\"npm\"",[194,4632,4633],{"class":200},", ",[194,4635,4636],{"class":762},"\"run\"",[194,4638,4633],{"class":200},[194,4640,4641],{"class":762},"\"start\"",[194,4643,481],{"class":200},[32,4645,4646,4647,4650,4651,4653],{},"记得在 ",[116,4648,4649],{},".dockerignore"," 文件中把 ",[116,4652,4154],{}," 目录加上，在 COPY 的时候不进行复制，而是在 docker 环境中重新获取。",[32,4655,4656,4657,4659,4660,4663],{},"在项目的 ",[116,4658,4164],{}," 中配置好 ",[116,4661,4662],{},"deploy"," 命令：",[186,4665,4667],{"className":4174,"code":4666,"language":4176,"meta":74,"style":74},"{\n  \"scripts\": {\n    \"deploy\": \"docker build -t registry.cn-hangzhou.aliyuncs.com\u002Fyour-name\u002Fexample-project:$npm_package_version -t registry.cn-hangzhou.aliyuncs.com\u002Fyour-name\u002Fexample-project:latest . && docker push registry.cn-hangzhou.aliyuncs.com\u002Fyour-name\u002Fexample-project:$npm_package_version && docker push registry.cn-hangzhou.aliyuncs.com\u002Fyour-name\u002Fexample-project:latest\"\n  }\n}\n",[116,4668,4669,4673,4686,4703,4707],{"__ignoreMap":74},[194,4670,4671],{"class":196,"line":197},[194,4672,457],{"class":204},[194,4674,4675,4677,4680,4682,4684],{"class":196,"line":75},[194,4676,4187],{"class":1334},[194,4678,4679],{"class":4190},"scripts",[194,4681,1450],{"class":1334},[194,4683,309],{"class":204},[194,4685,334],{"class":204},[194,4687,4688,4690,4692,4694,4696,4698,4701],{"class":196,"line":368},[194,4689,4202],{"class":1334},[194,4691,4662],{"class":327},[194,4693,1450],{"class":1334},[194,4695,309],{"class":204},[194,4697,4212],{"class":758},[194,4699,4700],{"class":762},"docker build -t registry.cn-hangzhou.aliyuncs.com\u002Fyour-name\u002Fexample-project:$npm_package_version -t registry.cn-hangzhou.aliyuncs.com\u002Fyour-name\u002Fexample-project:latest . && docker push registry.cn-hangzhou.aliyuncs.com\u002Fyour-name\u002Fexample-project:$npm_package_version && docker push registry.cn-hangzhou.aliyuncs.com\u002Fyour-name\u002Fexample-project:latest",[194,4702,2224],{"class":758},[194,4704,4705],{"class":196,"line":391},[194,4706,3392],{"class":204},[194,4708,4709],{"class":196,"line":397},[194,4710,1038],{"class":204},[32,4712,4713,4714,4717],{},"在阿里云的容器镜像服务中建好镜像，便可以部署上去了。之前我是直接通过绑定 gitlab，在代码更新后，触发阿里云镜像服务在线构建，但是我们采用了私有的 npm 仓库的话，就比较麻烦了，还得配置 Nexus 账号，索性直接在本地构建，上传的网速也不是问题。在阿里云的容器服务中创建好应用，设置好触发器，在镜像更新后触发重新部署，就大功告成了。如果有多台机器，在调度配置中配置好“平滑升级”，会在同一服务的多个容器升级的时候，保证当前一批或者一个容器升级或者更新成功（健康检查成功）之后，再来更新或者升级下一批容器，也就是“滚动发布”。之后只需要 ",[116,4715,4716],{},"npm run deploy","，喝杯咖啡 ☕️，就完成了所有的部署工作了。方便、快捷、不易出错。",[32,4719,4720],{},"最近看了篇文章，里面有句话：“这世界上肯定存在让人上瘾的代码”，提出了“技术多巴胺”这个说法。而此刻的我，就仿佛打了几针“技术多巴胺”一样，很兴奋，一点睡意都没有。",[1061,4722,4723],{},"html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sseR_, html code.shiki .sseR_{--shiki-light:#9C3EDA;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sw1J6, html code.shiki .sw1J6{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":74,"searchDepth":75,"depth":75,"links":4725},[4726,4727],{"id":4141,"depth":368,"text":4141},{"id":4524,"depth":368,"text":4525},"2018-04-14",{},"\u002Fposts\u002F2018\u002Ffrontend-components-and-docker-deploy",{"text":4732,"minutes":4733,"time":4734,"words":4735},"10 min read",9.455,567300,1891,{"title":4132,"description":4137},{"loc":4730},"posts\u002F2018\u002F20180414.frontend-components-and-docker-deploy",[1083,97,4740,4741],"Docker","DevOps","凌晨","天气🌧","9_mH8lcdetq97RzpovxCwJ60jWmSz7r17jZvBEuf42s",{"id":4746,"title":4747,"body":4748,"class":79,"cover":79,"coverSize":79,"date":4857,"description":4752,"draft":82,"extension":83,"hideComments":82,"location":79,"meta":4858,"navigation":85,"path":4859,"readingTime":4860,"seo":4864,"sitemap":4865,"stem":4866,"tags":4867,"time":79,"weather":79,"__hash__":4868},"posts\u002Fposts\u002F2017\u002F20171116.wxapp-ssl-error.md","微信小程序在安卓上 SSL 报错的问题",{"type":25,"value":4749,"toc":4855},[4750,4753,4764,4771,4774,4782,4791,4802,4805,4852],[32,4751,4752],{},"开发工具上和 iOS 真机上访问 api 都是正常的，在安卓上提示如下错误：",[186,4754,4758],{"className":4755,"code":4756,"language":4757,"meta":74,"style":74},"language-log shiki shiki-themes material-theme-lighter github-light github-dark","request:fail ssl hand shake error\n","log",[116,4759,4760],{"__ignoreMap":74},[194,4761,4762],{"class":196,"line":197},[194,4763,4756],{"class":200},[32,4765,4766,4767,4770],{},"尝试在安卓的浏览器中访问 api 地址，提示“",[116,4768,4769],{},"该证书并非来自可信的授权中心","”，于是感觉应该是 SSL 证书的问题。",[32,4772,4773],{},"SSL 证书是通过 Let's Encrypt 申请的，部署在阿里云 SLB 上。",[32,4775,4776,4777,4781],{},"通过",[36,4778,4779],{"href":4779,"rel":4780},"https:\u002F\u002Fwww.ssllabs.com\u002Fssltest\u002Findex.html",[40]," 测试，TLS1.0、TLS1.1、TLS1.2 都是支持的，但有如下提示",[186,4783,4785],{"className":4755,"code":4784,"language":4757,"meta":74,"style":74},"This server's certificate chain is incomplete. Grade capped to B.\n",[116,4786,4787],{"__ignoreMap":74},[194,4788,4789],{"class":196,"line":197},[194,4790,4784],{"class":200},[32,4792,4793,4794,4797,4798,4801],{},"于是重新查看了下 Let's Encrypt 生成的证书文件，想起来在阿里云 SLB 的证书填写的是",[116,4795,4796],{},"cert.pem","的内容，没有包含中间证书。于是重新填写",[116,4799,4800],{},"fullchain.pem","里的内容，问题解决。",[32,4803,4804],{},"下面是 Let's Encrypt 生成的证书文件及其内容：",[4806,4807,4808],"scrollable-table",{},[2510,4809,4810,4820],{},[2513,4811,4812],{},[2516,4813,4814,4817],{},[2519,4815,4816],{},"文件名",[2519,4818,4819],{},"内容",[2531,4821,4822,4829,4837,4844],{},[2516,4823,4824,4826],{},[2536,4825,4796],{},[2536,4827,4828],{},"服务端证书",[2516,4830,4831,4834],{},[2536,4832,4833],{},"chain.pem",[2536,4835,4836],{},"浏览器需要的所有证书但不包括服务端证书，比如根证书和中间证书",[2516,4838,4839,4841],{},[2536,4840,4800],{},[2536,4842,4843],{},"包括了 cert.pem 和 chain.pem 的内容",[2516,4845,4846,4849],{},[2536,4847,4848],{},"privkey.pem",[2536,4850,4851],{},"证书的私钥",[1061,4853,4854],{},"html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":74,"searchDepth":75,"depth":75,"links":4856},[],"2017-11-16",{},"\u002Fposts\u002F2017\u002Fwxapp-ssl-error",{"text":88,"minutes":4861,"time":4862,"words":4863},1.2,72000,240,{"title":4747,"description":4752},{"loc":4859},"posts\u002F2017\u002F20171116.wxapp-ssl-error",[1083,97],"OJ4ttgQOk0mUu8qLcQvEH9tsqn4vc1dma_uHLfP5yM4",{"id":4870,"title":4871,"body":4872,"class":79,"cover":79,"coverSize":79,"date":4938,"description":74,"draft":82,"extension":83,"hideComments":82,"location":4939,"meta":4940,"navigation":85,"path":4941,"readingTime":4942,"seo":4946,"sitemap":4947,"stem":4948,"tags":4949,"time":4950,"weather":79,"__hash__":4951},"posts\u002Fposts\u002F2015\u002F20150820.change-domain-in-weixin.md","微信公众号中更换域名",{"type":25,"value":4873,"toc":4936},[4874,4877,4885,4888,4891,4894,4897,4900,4907,4910,4933],[1128,4875,4876],{"id":4876},"更新",[32,4878,4879,4880],{},"如果需要实现微信授权支持多个回调域名，可以参考我这个开源项目：",[36,4881,4884],{"href":4882,"rel":4883},"https:\u002F\u002Fgithub.com\u002FHADB\u002FGetWeixinCode",[40],"GetWeixinCode",[4886,4887],"hr",{},[1128,4889,4890],{"id":4890},"问题描述",[32,4892,4893],{},"项目刚做的时候，并没有找到好的域名，所以用了一个比较长的域名。后来公司花钱买了一个心仪的域名，理所当然，我们需要启用新域名了。",[32,4895,4896],{},"我们的 H5 站点是基于微信的，由于微信的各种坑，这里有很多值得注意的地方。",[32,4898,4899],{},"首先，需要在公众号设置中，将新域名加入到业务域名以及 JS 接口安全域名中，在微信支付的开发配置中，也要将新域名加入支付授权目录中。这几个比较容易，因为他们都支持配置多个域名。",[32,4901,4902,4903,4906],{},"我们的页面加载之后会立即通过静默授权跳转去拿用户的 code 以换取 openid，来实现自动登录，为了减少跳转，我们在微信公众号的自定义菜单中配置的链接就是微信的授权链接\n",[116,4904,4905],{},"https:\u002F\u002Fopen.weixin.qq.com\u002Fconnect\u002Foauth2\u002Fauthorize?XXXXX&redirect_uri=h.xxx-old.com","\n这里比较坑的是，授权回调页面域名只能配置一个，而且自定义菜单的修改最慢要 24 小时才能生效，而且你没法确定是什么时候生效，有的用户会生效，有的用户仍是旧的链接。所以这里的链接不能冒然改成新链接。不过我们可以这么实现。",[1128,4908,4909],{"id":4909},"解决方案",[4911,4912,4913,4920,4927],"ol",{},[2580,4914,4915,4916,4919],{},"修改公众号自定义菜单中配置的链接，直接改为原域名",[116,4917,4918],{},"http:\u002F\u002Fh.xxx-old.com","，在页面代码中做判断，如果没有拿到 code 参数，就主动跳转到微信的授权页面去拿 code（这个原来就做了，为了让用户直接访问域名的时候也能拿到 code）。这个生效可能需要 24 小时，稳妥的做法就是等 24 小时之后再进行后面的操作。",[2580,4921,4922,4923,4926],{},"将代码中跳转到微信授权页面的 redirect_uri 改为新域名：\n",[116,4924,4925],{},"https:\u002F\u002Fopen.weixin.qq.com\u002Fconnect\u002Foauth2\u002Fauthorize?XXXXX&redirect_uri=h.xxx-new.com","\n同时将微信公众号中的授权回调页面域名改为新域名（这个是立即生效的）。这时，无论是从旧域名访问还是从新域名访问，授权回调的时候，都会成功跳转到新域名，并且带上 code 了。",[2580,4928,4929,4930,4932],{},"修改公众号中的链接，改为微信授权链接，并且 redirect_uri 写成新域名：\n",[116,4931,4925],{},"\n这个时候，无论是更新后的链接还是尚未更新的链接，都能成功授权，只是直接用域名会多跳转一下而已。",[32,4934,4935],{},"Done.",{"title":74,"searchDepth":75,"depth":75,"links":4937},[],"2015-08-20","莘庄",{},"\u002Fposts\u002F2015\u002Fchange-domain-in-weixin",{"text":1075,"minutes":4943,"time":4944,"words":4945},3.27,196200,654,{"title":4871,"description":74},{"loc":4941},"posts\u002F2015\u002F20150820.change-domain-in-weixin",[1083,97],"上午","r8Hm16aVzTZvxFywiwcPdbBnSlvUwb2dxVuKq1sK1x0",{"id":4953,"title":4954,"body":4955,"class":79,"cover":79,"coverSize":79,"date":5246,"description":4959,"draft":82,"extension":83,"hideComments":82,"location":5247,"meta":5248,"navigation":85,"path":5249,"readingTime":5250,"seo":5254,"sitemap":5255,"stem":5256,"tags":5257,"time":4742,"weather":79,"__hash__":5258},"posts\u002Fposts\u002F2015\u002F20150413.fix-fixed-bug-in-ios-when-call-virtual-keyboards.md","修复 position:fixed 在 ios 虚拟键盘弹出时错位的 bug",{"type":25,"value":4956,"toc":5244},[4957,4960,4968,4977,4980,4983,4986,4989,4992,5025,5028,5235,5238,5241],[32,4958,4959],{},"问题描述：在使用 bootstrap 的 navbar-fixed-top 时，发现在 iPhone 上的微信里面，当点击 input 弹出输入法之后，顶部 fixed 的 navbar 消失，在输入法没有关闭的情况下，向上滚动，会发现 navbar 在半空中。",[32,4961,4962,4963,72],{},"Google 了一下，发现这个问题在 iOS 中很常见，Bootstrap 也对此进行了说明（",[36,4964,4967],{"href":4965,"rel":4966},"http:\u002F\u002Fgetbootstrap.com\u002Fgetting-started\u002F#support-fixed-position-keyboards",[40],"戳这里",[50,4969,4970,4974],{},[1128,4971,4973],{"id":4972},"virtual-keyboards","Virtual keyboards",[32,4975,4976],{},"Also, note that if you're using a fixed navbar or using inputs within a modal, iOS has a rendering bug that doesn't update the position of fixed elements when the virtual keyboard is triggered. A few workarounds for this include transforming your elements to position: absolute or invoking a timer on focus to try to correct the positioning manually. This is not handled by Bootstrap, so it is up to you to decide which solution is best for your application.",[32,4978,4979],{},"不过我在最近刚更新的 iOS8.3 的 Safari 中，没有发现这个问题。在 8.3 的 Safari 中，点击 input 弹出输入法之后，fixed 元素会失效，navbar 回到最顶端，没有浮在半空中。猜测是在弹出虚拟键盘之后为了节省页面空间，而对 fixed 的元素进行了处理，但是在微信中的浏览器上处理出了点问题。",[32,4981,4982],{},"目前发现最好的解决方案是在点击 input 之后，直接把 fixed 的元素变成 absolute 的，不需要浏览器自己去做处理。",[32,4984,4985],{},"有人说在滚动时用 timer 实时调整元素位置，我觉得这个很低端。浏览器去处理 fixed 元素自然有它的道理，确实可以节省屏幕空间。我们其实也没有必要在这个情况下强制显示 navbar，这时用户的重点在于输入。当我们 input 失去焦点之后，输入法关闭，这时我们再显示出 navbar。",[32,4987,4988],{},"下面直接上代码：",[32,4990,4991],{},"添加这样一段 css：",[186,4993,4995],{"className":3006,"code":4994,"language":3008,"meta":74,"style":74},".fixfixed.navbar-fixed-top {\n  position: absolute;\n}\n",[116,4996,4997,5011,5021],{"__ignoreMap":74},[194,4998,4999,5001,5004,5006,5009],{"class":196,"line":197},[194,5000,205],{"class":3055},[194,5002,5003],{"class":312},"fixfixed",[194,5005,205],{"class":3055},[194,5007,5008],{"class":312},"navbar-fixed-top",[194,5010,334],{"class":204},[194,5012,5013,5015,5017,5019],{"class":196,"line":75},[194,5014,3124],{"class":3023},[194,5016,309],{"class":204},[194,5018,3129],{"class":261},[194,5020,3031],{"class":204},[194,5022,5023],{"class":196,"line":368},[194,5024,1038],{"class":204},[32,5026,5027],{},"添加这样一段 js：",[186,5029,5033],{"className":5030,"code":5031,"language":5032,"meta":74,"style":74},"language-js shiki shiki-themes material-theme-lighter github-light github-dark","$(() => {\n  if (Modernizr.touch) {\n    $(document).on('focus', 'input', () => {\n      $('.navbar-fixed-top').addClass('fixfixed')\n    })\n\n    $(document).on('blur', 'input', () => {\n      $('.navbar-fixed-top').removeClass('fixfixed')\n    })\n  }\n})\n","js",[116,5034,5035,5048,5067,5110,5141,5147,5151,5190,5219,5225,5229],{"__ignoreMap":74},[194,5036,5037,5040,5042,5044,5046],{"class":196,"line":197},[194,5038,5039],{"class":208},"$",[194,5041,302],{"class":200},[194,5043,814],{"class":204},[194,5045,363],{"class":295},[194,5047,334],{"class":204},[194,5049,5050,5053,5055,5058,5060,5063,5065],{"class":196,"line":75},[194,5051,5052],{"class":339},"  if",[194,5054,247],{"class":348},[194,5056,5057],{"class":200},"Modernizr",[194,5059,205],{"class":204},[194,5061,5062],{"class":200},"touch",[194,5064,454],{"class":348},[194,5066,457],{"class":204},[194,5068,5069,5072,5074,5076,5078,5080,5083,5085,5087,5090,5092,5094,5096,5099,5101,5103,5106,5108],{"class":196,"line":368},[194,5070,5071],{"class":208},"    $",[194,5073,302],{"class":348},[194,5075,4415],{"class":200},[194,5077,316],{"class":348},[194,5079,205],{"class":204},[194,5081,5082],{"class":208},"on",[194,5084,302],{"class":348},[194,5086,759],{"class":758},[194,5088,5089],{"class":762},"focus",[194,5091,759],{"class":758},[194,5093,227],{"class":204},[194,5095,4464],{"class":758},[194,5097,5098],{"class":762},"input",[194,5100,759],{"class":758},[194,5102,227],{"class":204},[194,5104,5105],{"class":204}," ()",[194,5107,363],{"class":295},[194,5109,334],{"class":204},[194,5111,5112,5115,5117,5119,5122,5124,5126,5128,5131,5133,5135,5137,5139],{"class":196,"line":391},[194,5113,5114],{"class":208},"      $",[194,5116,302],{"class":348},[194,5118,759],{"class":758},[194,5120,5121],{"class":762},".navbar-fixed-top",[194,5123,759],{"class":758},[194,5125,316],{"class":348},[194,5127,205],{"class":204},[194,5129,5130],{"class":208},"addClass",[194,5132,302],{"class":348},[194,5134,759],{"class":758},[194,5136,5003],{"class":762},[194,5138,759],{"class":758},[194,5140,265],{"class":348},[194,5142,5143,5145],{"class":196,"line":397},[194,5144,1022],{"class":204},[194,5146,265],{"class":348},[194,5148,5149],{"class":196,"line":427},[194,5150,394],{"emptyLinePlaceholder":85},[194,5152,5153,5155,5157,5159,5161,5163,5165,5167,5169,5172,5174,5176,5178,5180,5182,5184,5186,5188],{"class":196,"line":460},[194,5154,5071],{"class":208},[194,5156,302],{"class":348},[194,5158,4415],{"class":200},[194,5160,316],{"class":348},[194,5162,205],{"class":204},[194,5164,5082],{"class":208},[194,5166,302],{"class":348},[194,5168,759],{"class":758},[194,5170,5171],{"class":762},"blur",[194,5173,759],{"class":758},[194,5175,227],{"class":204},[194,5177,4464],{"class":758},[194,5179,5098],{"class":762},[194,5181,759],{"class":758},[194,5183,227],{"class":204},[194,5185,5105],{"class":204},[194,5187,363],{"class":295},[194,5189,334],{"class":204},[194,5191,5192,5194,5196,5198,5200,5202,5204,5206,5209,5211,5213,5215,5217],{"class":196,"line":484},[194,5193,5114],{"class":208},[194,5195,302],{"class":348},[194,5197,759],{"class":758},[194,5199,5121],{"class":762},[194,5201,759],{"class":758},[194,5203,316],{"class":348},[194,5205,205],{"class":204},[194,5207,5208],{"class":208},"removeClass",[194,5210,302],{"class":348},[194,5212,759],{"class":758},[194,5214,5003],{"class":762},[194,5216,759],{"class":758},[194,5218,265],{"class":348},[194,5220,5221,5223],{"class":196,"line":496},[194,5222,1022],{"class":204},[194,5224,265],{"class":348},[194,5226,5227],{"class":196,"line":523},[194,5228,3392],{"class":204},[194,5230,5231,5233],{"class":196,"line":540},[194,5232,2199],{"class":204},[194,5234,265],{"class":200},[32,5236,5237],{},"使用了 Modernizr，仅在触屏上进行处理，对桌面浏览器不做处理，这样对于桌面浏览器上的体验更好。",[32,5239,5240],{},"完美解决问题！",[1061,5242,5243],{},"html pre.shiki code .stp6e, html code.shiki .stp6e{--shiki-light:#39ADB5;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .soE4H, html code.shiki .soE4H{--shiki-light:#8796B0;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":74,"searchDepth":75,"depth":75,"links":5245},[],"2015-04-13","家中",{},"\u002Fposts\u002F2015\u002Ffix-fixed-bug-in-ios-when-call-virtual-keyboards",{"text":4121,"minutes":5251,"time":5252,"words":5253},2.57,154200,514,{"title":4954,"description":4959},{"loc":5249},"posts\u002F2015\u002F20150413.fix-fixed-bug-in-ios-when-call-virtual-keyboards",[1083,97],"IpoJwNty1t4VbyLpxIQGlO4O7-oswPS7EG0WKTuTxds",{"id":5260,"title":5261,"body":5262,"class":79,"cover":79,"coverSize":79,"date":5283,"description":5270,"draft":82,"extension":83,"hideComments":82,"location":79,"meta":5284,"navigation":85,"path":5285,"readingTime":5286,"seo":5290,"sitemap":5291,"stem":5292,"tags":5293,"time":79,"weather":79,"__hash__":5294},"posts\u002Fposts\u002F2014\u002F20140827.webkit-white-spacenowrap.md","WebKit 浏览器 table 里的 white-space:nowrap 的问题",{"type":25,"value":5263,"toc":5281},[5264,5271],[32,5265,5266],{},[36,5267,5270],{"href":5268,"rel":5269},"http:\u002F\u002Fwww.w3help.org\u002Fzh-cn\u002Fcauses\u002FRT5004",[40],"参考链接",[32,5272,5273,5274,5277,5278,2587],{},"今天遇到一个类似的问题，在 table 里面，设置的 ",[116,5275,5276],{},"white-space:nowrap"," 导致一行被撑大，解决办法在 table 上设置 ",[116,5279,5280],{},"table-layout:fixed",{"title":74,"searchDepth":75,"depth":75,"links":5282},[],"2014-08-27",{},"\u002Fposts\u002F2014\u002Fwebkit-white-spacenowrap",{"text":5287,"minutes":5288,"time":5289,"words":996},"1 min read",0.19,11400,{"title":5261,"description":5270},{"loc":5285},"posts\u002F2014\u002F20140827.webkit-white-spacenowrap",[1083,97],"MFXuqYsYnUlpDQFgWGipvlfm5SrDJbuvTGRynYYgrrw",1777580268554]