Socket
Socket
Sign inDemoInstall

cky-blog-static

Package Overview
Dependencies
454
Maintainers
1
Versions
121
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.1.9-89a0bd41dabb93c8b3cd2109a8f6fc1f to 2.2.0-2c9674d79b1a4b9dbd7cf4d0d33d556f

2

package.json

@@ -1,1 +0,1 @@

{"name":"cky-blog-static","version":"2.1.9-89a0bd41dabb93c8b3cd2109a8f6fc1f","scripts":{"build":"hexo generate","clean":"hexo clean","deploy":"hexo deploy","server":"hexo server"},"hexo":{"version":"5.2.0"},"dependencies":{"cheerio":"^1.0.0-rc.12","express":"^4.17.1","hexo":"^5.0.0","hexo-deployer-git":"^2.1.0","hexo-generator-archive":"^1.0.0","hexo-generator-category":"^1.0.0","hexo-generator-feed":"^3.0.0","hexo-generator-index":"^2.0.0","hexo-generator-searchdb":"^1.3.3","hexo-generator-sitemap":"^2.1.0","hexo-generator-tag":"^1.0.0","hexo-renderer-ejs":"^1.0.0","hexo-renderer-marked":"^3.0.0","hexo-renderer-stylus":"^2.0.0","hexo-server":"^2.0.0","hexo-theme-miracle":"^2.1.7","hexo-yam":"^4.2.0","js-md5":"^0.7.3","node-fetch":"^2.6.7","upyun":"^3.4.6"}}
{"name":"cky-blog-static","version":"2.2.0-2c9674d79b1a4b9dbd7cf4d0d33d556f","scripts":{"build":"hexo generate","clean":"hexo clean","deploy":"hexo deploy","server":"hexo server"},"hexo":{"version":"5.2.0"},"dependencies":{"cheerio":"^1.0.0-rc.12","express":"^4.17.1","hexo":"^5.0.0","hexo-deployer-git":"^2.1.0","hexo-generator-archive":"^1.0.0","hexo-generator-category":"^1.0.0","hexo-generator-feed":"^3.0.0","hexo-generator-index":"^2.0.0","hexo-generator-searchdb":"^1.3.3","hexo-generator-sitemap":"^2.1.0","hexo-generator-tag":"^1.0.0","hexo-renderer-ejs":"^1.0.0","hexo-renderer-marked":"^3.0.0","hexo-renderer-stylus":"^2.0.0","hexo-server":"^2.0.0","hexo-theme-miracle":"^2.1.7","hexo-yam":"^4.2.0","js-md5":"^0.7.3","node-fetch":"^2.6.7","upyun":"^3.4.6"}}

@@ -1,1 +0,1 @@

{"title":"什么年代还在用传统 Pjax? —— 自定义 Pjax 提升页面加载速度 - YFun's Blog","page":"\n <div class=\"mg-top\">\n <article class=\"page\"><div id=\"post-meta-m\"><div class=\"post-meta\" id=\"post-meta\"><h3>什么年代还在用传统 Pjax? —— 自定义 Pjax 提升页面加载速度</h3><span class=\"post-meta-label\">YFun (@oCoke) </span><span class=\"post-meta-label\"><span class=\"p-dot\"></span> <time datetime=\"2022-12-15 12:30\" pubdate=\"\">2022-12-15 </time></span><span class=\"post-meta\"><span class=\"p-dot\"></span> 共 2.8k 字</span></div></div><div class=\"article-m\"><div class=\"post-toc\"><ol class=\"toc\"><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E5%89%8D%E8%A8%80\"><span class=\"toc-number\">1.</span> <span class=\"toc-text\">前言</span></a></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E5%8E%9F%E7%90%86\"><span class=\"toc-number\">2.</span> <span class=\"toc-text\">原理</span></a></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E5%88%86%E6%9E%90\"><span class=\"toc-number\">3.</span> <span class=\"toc-text\">分析</span></a></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E6%9C%80%E5%B0%8F%E5%8C%96%E7%9A%84%E6%95%B0%E6%8D%AE%E6%8E%A5%E5%8F%A3\"><span class=\"toc-number\">4.</span> <span class=\"toc-text\">最小化的数据接口</span></a><ol class=\"toc-child\"><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E8%BD%BD%E5%85%A5-HTML\"><span class=\"toc-number\">4.1.</span> <span class=\"toc-text\">载入 HTML</span></a></li><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E6%96%87%E4%BB%B6%E9%80%92%E5%BD%92\"><span class=\"toc-number\">4.2.</span> <span class=\"toc-text\">文件递归</span></a></li><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84\"><span class=\"toc-number\">4.3.</span> <span class=\"toc-text\">基本结构</span></a></li></ol></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E5%89%8D%E7%AB%AF-pjax-js\"><span class=\"toc-number\">5.</span> <span class=\"toc-text\">前端 pjax.js</span></a><ol class=\"toc-child\"><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E6%9B%BF%E6%8D%A2%E9%93%BE%E6%8E%A5\"><span class=\"toc-number\">5.1.</span> <span class=\"toc-text\">替换链接</span></a></li><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E8%B7%B3%E8%BD%AC\"><span class=\"toc-number\">5.2.</span> <span class=\"toc-text\">跳转</span></a></li><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#Prefetch-amp-Refetch\"><span class=\"toc-number\">5.3.</span> <span class=\"toc-text\">Prefetch &amp; Refetch</span></a></li><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E4%B8%80%E4%BA%9B%E4%BC%98%E5%8C%96\"><span class=\"toc-number\">5.4.</span> <span class=\"toc-text\">一些优化</span></a><ol class=\"toc-child\"><li class=\"toc-item toc-level-4\"><a class=\"toc-link\" href=\"#Prefetch-CSS-%E6%96%87%E4%BB%B6\"><span class=\"toc-number\">5.4.1.</span> <span class=\"toc-text\">Prefetch CSS 文件</span></a></li><li class=\"toc-item toc-level-4\"><a class=\"toc-link\" href=\"#%E5%85%B3%E4%BA%8E-Robots\"><span class=\"toc-number\">5.4.2.</span> <span class=\"toc-text\">关于 Robots</span></a></li></ol></li></ol></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E6%9C%80%E5%90%8E\"><span class=\"toc-number\">6.</span> <span class=\"toc-text\">最后</span></a></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E8%BF%98%E6%9C%89%E4%B8%80%E4%BA%9B%E9%94%99%E8%AF%AF\"><span class=\"toc-number\">7.</span> <span class=\"toc-text\">还有一些错误</span></a></li></ol></div><div id=\"article\"><div id=\"post-content\" class=\"markdown-body textretty\"><h2 id=\"前言\"><a href=\"#前言\" class=\"headerlink\" title=\"前言\"></a>前言</h2><p>Hexo 属于静态博客,很多同学给自己的博客加上 Pjax 是为了音乐播放器等功能不中断。</p><p>之前我也想过对博客和主题加入 Pjax 支持,但经过一番分析后觉得,这不仅引入了一个巨大的 <code>jquery.pjax.js</code>,反而优化效果不明显。</p><h2 id=\"原理\"><a href=\"#原理\" class=\"headerlink\" title=\"原理\"></a>原理</h2><p>其实,Pjax 的原理并不复杂。或许说,README 一开始就告诉你了:</p><figure class=\"highlight plain\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">pjax = pushState + ajax</span><br></pre></td></tr></tbody></table></figure><p>其中 <code>ajax</code> 用于页面的新内容,<code>pushState</code> 改变浏览器状态。</p><p>很简单吧。</p><p>事实上,<code>pjax</code> 并不应该应用于整个页面当中。而应该只是局部更改。</p><p>这样,Blog 当中的导航栏、样式文件等就不需要重复下载与预览。</p><h2 id=\"分析\"><a href=\"#分析\" class=\"headerlink\" title=\"分析\"></a>分析</h2><p>以我使用 Miracle 为主题的博客为例,进入首页,按 <code>F12</code> 查看页面 Elements.</p><p><picture><source class=\"lazyload-img\" data-srcset=\"https://cdn.jsdelivr.net/npm/ocoke-osg@0.0.18/rawimg/2022-12-15_12-32-37.webp\" srcset=\"data:image/gif;base64,R0lGODlhAQABAID/AP///wAAACwAAAAAAQABAAACAkQBADs\" type=\"image/webp\"><img webp-comp=\"\" src=\"https://cdn.jsdelivr.net/npm/ocoke-osg@0.0.18/rawimg/2022-12-15_12-32-37.png\" class=\"lazyload-img\" data-srcset=\"https://cdn.jsdelivr.net/npm/ocoke-osg@0.0.18/rawimg/2022-12-15_12-32-37.png\" srcset=\"data:image/gif;base64,R0lGODlhAQABAID/AP///wAAACwAAAAAAQABAAACAkQBADs\"></picture></p><p>可以发现,页面主要更改的也就是 <code>#page-main</code> 部分,只需要实现动态刷新这部分的内容就可以了。</p><p>那怎么实现呢?</p><h2 id=\"最小化的数据接口\"><a href=\"#最小化的数据接口\" class=\"headerlink\" title=\"最小化的数据接口\"></a>最小化的数据接口</h2><p>现在生成的页面当中,有 <code>&lt;head&gt;</code> 部分声明大量样式与元信息,<code>&lt;body&gt;</code> 之下重复的页脚、导航栏,还有每个页面下方都有的一些 <code>&lt;script&gt;</code>。</p><p>很明显,我们不需要这些。我们只要 <code>#page-main</code> 中的主要内容。</p><p>最重要的是,Hexo 是静态博客,这一点只能在生成文件时进行。</p><h3 id=\"载入-HTML\"><a href=\"#载入-HTML\" class=\"headerlink\" title=\"载入 HTML\"></a>载入 HTML</h3><p>我是用 Cheerio 模块帮我完成这一工作。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">const</span> cheerio = <span class=\"built_in\">require</span>(<span class=\"string\">'cheerio'</span>);</span><br><span class=\"line\"><span class=\"keyword\">const</span> fs = <span class=\"built_in\">require</span>(<span class=\"string\">\"fs\"</span>);</span><br><span class=\"line\"><span class=\"keyword\">const</span> path = <span class=\"built_in\">require</span>(<span class=\"string\">\"path\"</span>);</span><br><span class=\"line\"><span class=\"keyword\">const</span> filePath = path.resolve(<span class=\"string\">'public/'</span>);</span><br></pre></td></tr></tbody></table></figure><p>定义一个 <code>parse function</code>,打开文件并解析相关信息,顺便把不是 HTML 的文件排除掉。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">const</span> parse = <span class=\"function\">(<span class=\"params\">filename, fullpath</span>) =&gt;</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 不是 .html 我不要</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!filename.endsWith(<span class=\"string\">\".html\"</span>)) {</span><br><span class=\"line\"> <span class=\"keyword\">return</span> <span class=\"literal\">false</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><p>然后通过 Cheerio 解析 HTML:</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">{...</span><br><span class=\"line\"> <span class=\"comment\">// 组合新文件名</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> filepath = fullpath+<span class=\"string\">\".page.json\"</span>;</span><br><span class=\"line\"> <span class=\"comment\">// 读取文件内容</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> pageContent = fs.readFileSync(fullpath).toString();</span><br><span class=\"line\"> <span class=\"comment\">// 解析页面内容</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> $pg = cheerio.load(pageContent);</span><br><span class=\"line\"> <span class=\"keyword\">let</span> rtData = {};</span><br><span class=\"line\">...}</span><br></pre></td></tr></tbody></table></figure><p>然后获取页面的标题和 <code>#page-main</code> 下的 HTML.</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">{...</span><br><span class=\"line\">\t<span class=\"comment\">// 页面标题</span></span><br><span class=\"line\"> rtData.title = $pg(<span class=\"string\">\"title\"</span>).text();</span><br><span class=\"line\"> <span class=\"comment\">// OR $pg(\"#page-main\").html()</span></span><br><span class=\"line\"> <span class=\"comment\">// 我这么写是因为主题 #page-main 下还有 script 无法执行</span></span><br><span class=\"line\"> rtData.page = <span class=\"string\">`</span></span><br><span class=\"line\"><span class=\"string\"> &lt;div class=\"mg-top\"&gt;</span></span><br><span class=\"line\"><span class=\"string\"> <span class=\"subst\">${$pg(<span class=\"string\">\".mg-top\"</span>).html() || <span class=\"string\">\"\"</span>}</span></span></span><br><span class=\"line\"><span class=\"string\"> &lt;/div&gt;</span></span><br><span class=\"line\"><span class=\"string\"> &lt;footer class=\"text-center\"&gt;</span></span><br><span class=\"line\"><span class=\"string\"> <span class=\"subst\">${$pg(<span class=\"string\">\"footer\"</span>).html() || <span class=\"string\">\"\"</span>}</span></span></span><br><span class=\"line\"><span class=\"string\"> &lt;/footer&gt;</span></span><br><span class=\"line\"><span class=\"string\"> &lt;div class=\"p-btn\"&gt;</span></span><br><span class=\"line\"><span class=\"string\"> <span class=\"subst\">${$pg(<span class=\"string\">\".p-btn\"</span>).html() || <span class=\"string\">\"\"</span>}</span></span></span><br><span class=\"line\"><span class=\"string\"> &lt;/div&gt;</span></span><br><span class=\"line\"><span class=\"string\"> `</span>;</span><br><span class=\"line\"> rtData.path = filename;</span><br><span class=\"line\">...}</span><br></pre></td></tr></tbody></table></figure><p>页面中还有一些 <code>script</code>,比如阅读进度、懒加载等。所以需要一个 <code>extraJS</code> 放置额外的 Script.</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">{...</span><br><span class=\"line\"> rtData.extraJS = []</span><br><span class=\"line\"> <span class=\"comment\">// 只解析 #page-main 下的 script</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> $pageMain = cheerio.load($pg(<span class=\"string\">\"#page-main\"</span>).html());</span><br><span class=\"line\"> $pageMain(<span class=\"string\">'script'</span>).map(<span class=\"function\"><span class=\"keyword\">function</span>(<span class=\"params\">i, el</span>) </span>{</span><br><span class=\"line\"> <span class=\"comment\">// 尝试往 extraJS 中 push 相关代码</span></span><br><span class=\"line\"> <span class=\"keyword\">try</span> {rtData.extraJS.push($pageMain(<span class=\"built_in\">this</span>)[<span class=\"number\">0</span>].children[<span class=\"number\">0</span>].data);} <span class=\"keyword\">catch</span>(e) {}</span><br><span class=\"line\"> $pageMain(<span class=\"built_in\">this</span>).remove();</span><br><span class=\"line\"> });</span><br><span class=\"line\">...}</span><br></pre></td></tr></tbody></table></figure><p>最后,将 JSON 写入文件中。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">{...</span><br><span class=\"line\"> fs.writeFileSync(filepath, <span class=\"built_in\">JSON</span>.stringify(rtData));</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><h3 id=\"文件递归\"><a href=\"#文件递归\" class=\"headerlink\" title=\"文件递归\"></a>文件递归</h3><p>我们还需要一个函数递归 <code>public</code> 目录下的所有文件,这个不用多说。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">function</span> <span class=\"title\">fileDisplay</span>(<span class=\"params\">filePath</span>) </span>{</span><br><span class=\"line\"> <span class=\"comment\">// 根据文件路径读取文件,返回文件列表</span></span><br><span class=\"line\"> fs.readdir(filePath, <span class=\"function\"><span class=\"keyword\">function</span>(<span class=\"params\">err, files</span>) </span>{</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (err) {</span><br><span class=\"line\"> <span class=\"built_in\">console</span>.warn(err, <span class=\"string\">\"读取文件夹错误!\"</span>)</span><br><span class=\"line\"> } <span class=\"keyword\">else</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 遍历读取到的文件列表</span></span><br><span class=\"line\"> files.forEach(<span class=\"function\"><span class=\"keyword\">function</span>(<span class=\"params\">filename</span>) </span>{</span><br><span class=\"line\"> <span class=\"comment\">// 获取当前文件的绝对路径</span></span><br><span class=\"line\"> <span class=\"keyword\">var</span> filedir = path.join(filePath, filename);</span><br><span class=\"line\"> <span class=\"keyword\">var</span> fullname = filedir.split(<span class=\"string\">\"public\"</span>)[<span class=\"number\">1</span>];</span><br><span class=\"line\"> fs.stat(filedir, <span class=\"function\"><span class=\"keyword\">function</span>(<span class=\"params\">eror, stats</span>) </span>{</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (eror) {</span><br><span class=\"line\"> <span class=\"built_in\">console</span>.warn(<span class=\"string\">'获取文件 Stats 失败!'</span>);</span><br><span class=\"line\"> } <span class=\"keyword\">else</span> {</span><br><span class=\"line\"> <span class=\"keyword\">var</span> isFile = stats.isFile(); <span class=\"comment\">// 是文件</span></span><br><span class=\"line\"> <span class=\"keyword\">var</span> isDir = stats.isDirectory(); <span class=\"comment\">// 是文件夹</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (isFile) {</span><br><span class=\"line\"> parse(fullname, filedir);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (isDir) {</span><br><span class=\"line\"> fileDisplay(filedir); <span class=\"comment\">// 递归,如果是文件夹,就继续遍历该文件夹下面的文件</span></span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> });</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> });</span><br><span class=\"line\">}</span><br><span class=\"line\">fileDisplay(filePath);</span><br></pre></td></tr></tbody></table></figure><p>最后运行这个 Node.js 文件,就可以看到 <code>public/</code> 目录下多出很多 <code>***.page.json</code> 文件。</p><h3 id=\"基本结构\"><a href=\"#基本结构\" class=\"headerlink\" title=\"基本结构\"></a>基本结构</h3><p>这些文件内容也很简单,基本如下:</p><figure class=\"highlight json\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> <span class=\"comment\">// 页面的标题</span></span><br><span class=\"line\"> <span class=\"attr\">\"title\"</span>: <span class=\"string\">\"Hello World\"</span>,</span><br><span class=\"line\"> <span class=\"comment\">// 内容</span></span><br><span class=\"line\"> <span class=\"attr\">\"page\"</span>: <span class=\"string\">\"...\"</span>,</span><br><span class=\"line\"> <span class=\"comment\">// 路径</span></span><br><span class=\"line\"> <span class=\"attr\">\"path\"</span>: <span class=\"string\">\"/foo/bar\"</span>,</span><br><span class=\"line\"> <span class=\"comment\">// JS</span></span><br><span class=\"line\"> <span class=\"attr\">\"extraJS\"</span>: ['alert(<span class=\"string\">\"Hello World\"</span>);']</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><h2 id=\"前端-pjax-js\"><a href=\"#前端-pjax-js\" class=\"headerlink\" title=\"前端 pjax.js\"></a>前端 <code>pjax.js</code></h2><p>新建一个 <code>pjax.js</code>。</p><h3 id=\"替换链接\"><a href=\"#替换链接\" class=\"headerlink\" title=\"替换链接\"></a>替换链接</h3><p>我们需要先将页面当中所有本站链接转为 Pjax 的 Jump 函数。</p><p>判断条件是:有链接,不带 hash,且为本站链接</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// 转换页面中的链接为 Pjax 链接</span></span><br><span class=\"line\"><span class=\"keyword\">const</span> $pjax_convertAllLinks = <span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\">\t<span class=\"comment\">// 所有的 a 标签</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> linkElements = <span class=\"built_in\">document</span>.querySelectorAll(<span class=\"string\">\"a\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">for</span> (<span class=\"keyword\">let</span> i <span class=\"keyword\">of</span> linkElements) {</span><br><span class=\"line\"> <span class=\"comment\">// 有链接,不带 hash,且为本站链接</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (i.href &amp;&amp; !i.href.includes(<span class=\"string\">\"/#\"</span>) &amp;&amp; (i.href.startsWith(<span class=\"string\">\"/\"</span>) || i.href.match(<span class=\"keyword\">new</span> <span class=\"built_in\">RegExp</span>(<span class=\"built_in\">window</span>.location.hostname)))) {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> thisLink = <span class=\"keyword\">new</span> URL(i.href).pathname+<span class=\"keyword\">new</span> URL(i.href).hash;</span><br><span class=\"line\"> i.href = <span class=\"string\">`javascript:$pjax_jump('<span class=\"subst\">${thisLink}</span>');`</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><p>另外,要转化页面链接为全路径。</p><p>这里参考了下 ChenYFan 的 Service Worker 函数,需要根据实际情况做出调整。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// 转换路径为全路径</span></span><br><span class=\"line\"><span class=\"keyword\">const</span> $pjax_fullpath = <span class=\"function\">(<span class=\"params\">path</span>) =&gt;</span> {</span><br><span class=\"line\"> path = path.split(<span class=\"string\">'?'</span>)[<span class=\"number\">0</span>].split(<span class=\"string\">'#'</span>)[<span class=\"number\">0</span>]</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (path.match(<span class=\"regexp\">/\\/$/</span>)) {</span><br><span class=\"line\"> path += <span class=\"string\">'index.html'</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!path.match(<span class=\"regexp\">/\\.[a-zA-Z]+$/</span>)) {</span><br><span class=\"line\"> path += <span class=\"string\">'/index.html'</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">return</span> path;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">// $pjax_fullpath('/') =&gt; /index.html</span></span><br></pre></td></tr></tbody></table></figure><h3 id=\"跳转\"><a href=\"#跳转\" class=\"headerlink\" title=\"跳转\"></a>跳转</h3><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// 跳转页面</span></span><br><span class=\"line\"><span class=\"keyword\">const</span> $pjax_jump = <span class=\"keyword\">async</span> (path) =&gt; {</span><br><span class=\"line\"> <span class=\"keyword\">try</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 是 # 就别跳转了</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (path.startsWith(<span class=\"string\">\"#\"</span>)) {</span><br><span class=\"line\"> <span class=\"built_in\">window</span>.hash = path;</span><br><span class=\"line\"> <span class=\"keyword\">return</span> <span class=\"literal\">false</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"comment\">// 加载动画</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> loading = <span class=\"built_in\">document</span>.createElement(<span class=\"string\">'div'</span>);</span><br><span class=\"line\"> loading.innerHTML = <span class=\"string\">`&lt;div style=\"position: fixed;top:0;left:0;z-index:99999;display: block;width: 100%;height: 4px;overflow: hidden;background-color: rgba(63,81,181,.2);border-radius: 2px;\"&gt;&lt;div class=\"progress-indeterminate\" style=\"background-color: #3f51b5;\"&gt;&lt;/div&gt;&lt;style&gt;#page-main{transition:0.2s;}.progress-indeterminate::before{position:absolute;top:0;bottom:0;left:0;background-color:inherit;-webkit-animation:mdui-progress-indeterminate 2s linear infinite;animation:mdui-progress-indeterminate 2s linear infinite;content:' ';will-change:left,width;}.progress-indeterminate::after{position:absolute;top:0;bottom:0;left:0;background-color:inherit;-webkit-animation:mdui-progress-indeterminate-short 2s linear infinite;animation:mdui-progress-indeterminate-short 2s linear infinite;content:' ';will-change:left,width;}@keyframes mdui-progress-indeterminate{0%{left:0;width:0;}50%{left:30%;width:70%;}75%{left:100%;width:0;}}@keyframes mdui-progress-indeterminate-short{0%{left:0;width:0;}50%{left:0;width:0;}75%{left:0;width:25%;}100%{left:100%;width:0;}}&lt;/style&gt;&lt;/div&gt;`</span>;</span><br><span class=\"line\"> <span class=\"comment\">// 在 body 后加入 &lt;div&gt;</span></span><br><span class=\"line\"> <span class=\"built_in\">document</span>.body.appendChild(loading);</span><br><span class=\"line\"> <span class=\"comment\">// 如果页面中没有 page.css 或 search.css,为防止样式错乱,需要在加载过程中隐藏页面内容</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page_css\"</span>) || !<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"search_css\"</span>)) <span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page-main\"</span>).style.opacity = <span class=\"number\">0</span>;</span><br><span class=\"line\"> <span class=\"comment\">// 获取页面数据</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> pageData;</span><br><span class=\"line\"> <span class=\"comment\">// 看看 SessionStorage 里有没有缓存</span></span><br><span class=\"line\"> <span class=\"comment\">// 依赖后文的 prefetch</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (sessionStorage.getItem(<span class=\"string\">`<span class=\"subst\">${location.protocol}</span>//<span class=\"subst\">${location.hostname}</span><span class=\"subst\">${location.port ? <span class=\"string\">\":\"</span>+location.port:location.port}</span><span class=\"subst\">${$pjax_fullpath(path)}</span>`</span>)) {</span><br><span class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">\"FROM SESSIONSTORAGE\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">try</span> {</span><br><span class=\"line\"> pageData = <span class=\"built_in\">JSON</span>.parse(sessionStorage.getItem(<span class=\"string\">`<span class=\"subst\">${location.protocol}</span>//<span class=\"subst\">${location.hostname}</span><span class=\"subst\">${location.port ? <span class=\"string\">\":\"</span>+location.port:location.port}</span><span class=\"subst\">${$pjax_fullpath(path)}</span>`</span>));</span><br><span class=\"line\"> } <span class=\"keyword\">catch</span>(e) {</span><br><span class=\"line\"> <span class=\"comment\">// 还是出错就从服务器获取</span></span><br><span class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">\"FROM SERVER\"</span>);</span><br><span class=\"line\"> pageData = <span class=\"keyword\">await</span> fetch($pjax_fullpath(path) + <span class=\"string\">\".page.json\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.json());</span><br><span class=\"line\"> <span class=\"comment\">// 写到 SessionStorage 中</span></span><br><span class=\"line\"> sessionStorage.setItem(<span class=\"string\">`<span class=\"subst\">${location.protocol}</span>//<span class=\"subst\">${location.hostname}</span><span class=\"subst\">${location.port ? <span class=\"string\">\":\"</span>+location.port:location.port}</span><span class=\"subst\">${$pjax_fullpath(path)}</span>`</span>, <span class=\"built_in\">JSON</span>.stringify(pageData));</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } <span class=\"keyword\">else</span> {</span><br><span class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">\"FROM SERVER\"</span>);</span><br><span class=\"line\"> <span class=\"comment\">// fetch JSON</span></span><br><span class=\"line\"> pageData = <span class=\"keyword\">await</span> fetch($pjax_fullpath(path) + <span class=\"string\">\".page.json\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.json());</span><br><span class=\"line\"> sessionStorage.setItem(<span class=\"string\">`<span class=\"subst\">${location.protocol}</span>//<span class=\"subst\">${location.hostname}</span><span class=\"subst\">${location.port ? <span class=\"string\">\":\"</span>+location.port:location.port}</span><span class=\"subst\">${$pjax_fullpath(path)}</span>`</span>, <span class=\"built_in\">JSON</span>.stringify(pageData));</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"comment\">// 补齐页面 CSS</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"search_css\"</span>)) {</span><br><span class=\"line\"> fetch(<span class=\"string\">\"/css/search.css\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> ele = <span class=\"built_in\">document</span>.createElement(<span class=\"string\">\"style\"</span>);</span><br><span class=\"line\"> ele.innerHTML = res;</span><br><span class=\"line\"> ele.id = <span class=\"string\">\"search_css\"</span>;</span><br><span class=\"line\"> <span class=\"built_in\">document</span>.body.appendChild(ele);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page_css\"</span>)) {</span><br><span class=\"line\"> fetch(<span class=\"string\">\"/css/page.css\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> ele = <span class=\"built_in\">document</span>.createElement(<span class=\"string\">\"style\"</span>);</span><br><span class=\"line\"> ele.innerHTML = res;</span><br><span class=\"line\"> ele.id = <span class=\"string\">\"page_css\"</span>;</span><br><span class=\"line\"> <span class=\"built_in\">document</span>.body.appendChild(ele);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!pageData) <span class=\"keyword\">return</span> <span class=\"literal\">false</span>;</span><br><span class=\"line\"> <span class=\"comment\">// 组合 state</span></span><br><span class=\"line\"> <span class=\"keyword\">var</span> state = { <span class=\"attr\">title</span>: <span class=\"string\">''</span>, <span class=\"attr\">url</span>: <span class=\"built_in\">window</span>.location.href.split(<span class=\"string\">\"?\"</span>)[<span class=\"number\">0</span>] };</span><br><span class=\"line\"> <span class=\"comment\">// 利用 history.pushState() 修改地址栏而不跳转</span></span><br><span class=\"line\"> history.pushState(state, <span class=\"string\">''</span>, path);</span><br><span class=\"line\"> <span class=\"comment\">// 修改页面标题</span></span><br><span class=\"line\"> <span class=\"built_in\">document</span>.title = pageData.title;</span><br><span class=\"line\"> <span class=\"built_in\">setTimeout</span>(<span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 滚动到页面顶部</span></span><br><span class=\"line\"> <span class=\"built_in\">window</span>.scrollTo({<span class=\"attr\">top</span>: <span class=\"number\">0</span>, <span class=\"attr\">behavior</span>: <span class=\"string\">\"smooth\"</span>});</span><br><span class=\"line\"> <span class=\"comment\">// 写入 HTML</span></span><br><span class=\"line\"> <span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page-main\"</span>).innerHTML = pageData.page;</span><br><span class=\"line\"> <span class=\"built_in\">window</span>.onscroll = <span class=\"literal\">null</span>;</span><br><span class=\"line\"> <span class=\"keyword\">for</span> (<span class=\"keyword\">let</span> i <span class=\"keyword\">in</span> pageData.extraJS) {</span><br><span class=\"line\"> <span class=\"keyword\">try</span> {</span><br><span class=\"line\"> <span class=\"comment\">// eval() 执行 JS</span></span><br><span class=\"line\"> <span class=\"built_in\">eval</span>(pageData.extraJS[i]);</span><br><span class=\"line\"> } <span class=\"keyword\">catch</span>(e) {}</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">try</span>{$pjax_prefetch();}<span class=\"keyword\">catch</span>(e){}</span><br><span class=\"line\"> <span class=\"comment\">// 再次转换所有链接</span></span><br><span class=\"line\"> $pjax_convertAllLinks();</span><br><span class=\"line\"> }, <span class=\"number\">200</span>);</span><br><span class=\"line\"> <span class=\"built_in\">setTimeout</span>(<span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 重新显示页面</span></span><br><span class=\"line\"> <span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page-main\"</span>).style.opacity = <span class=\"number\">1</span>;</span><br><span class=\"line\"> loading.remove();</span><br><span class=\"line\"> }, <span class=\"number\">1000</span>);</span><br><span class=\"line\"> } <span class=\"keyword\">catch</span>(e) {</span><br><span class=\"line\"> <span class=\"comment\">// 有报错 直接跳转</span></span><br><span class=\"line\"> <span class=\"built_in\">console</span>.warn(e);</span><br><span class=\"line\"> <span class=\"built_in\">window</span>.location.href = path;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><p>如果使用 <code>window.location.href</code> 修改,那么页面就会刷新。<br>为了实现无刷新跳转,必须要使用 <code>pushState()</code> 更改。</p><p>执行 JavaScript 方面使用 <code>eval()</code> 函数。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// 组合 state</span></span><br><span class=\"line\"><span class=\"keyword\">var</span> state = { <span class=\"attr\">title</span>: <span class=\"string\">''</span>, <span class=\"attr\">url</span>: <span class=\"built_in\">window</span>.location.href.split(<span class=\"string\">\"?\"</span>)[<span class=\"number\">0</span>] };</span><br><span class=\"line\"><span class=\"comment\">// 利用 history.pushState() 修改地址栏而不跳转</span></span><br><span class=\"line\">history.pushState(state, <span class=\"string\">''</span>, path);</span><br><span class=\"line\"><span class=\"comment\">// 修改页面标题</span></span><br><span class=\"line\"><span class=\"built_in\">document</span>.title = pageData.title;</span><br><span class=\"line\"><span class=\"comment\">// 滚动到页面顶部</span></span><br><span class=\"line\"><span class=\"built_in\">window</span>.scrollTo({<span class=\"attr\">top</span>: <span class=\"number\">0</span>, <span class=\"attr\">behavior</span>: <span class=\"string\">\"smooth\"</span>});</span><br><span class=\"line\"><span class=\"comment\">// 写入 HTML</span></span><br><span class=\"line\"><span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page-main\"</span>).innerHTML = pageData.page;</span><br><span class=\"line\"><span class=\"built_in\">window</span>.onscroll = <span class=\"literal\">null</span>;</span><br><span class=\"line\"><span class=\"keyword\">for</span> (<span class=\"keyword\">let</span> i <span class=\"keyword\">in</span> pageData.extraJS) {</span><br><span class=\"line\"> <span class=\"keyword\">try</span> {</span><br><span class=\"line\"> <span class=\"comment\">// eval() 执行 JS</span></span><br><span class=\"line\"> <span class=\"built_in\">eval</span>(pageData.extraJS[i]);</span><br><span class=\"line\"> } <span class=\"keyword\">catch</span>(e) {}</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><h3 id=\"Prefetch-amp-Refetch\"><a href=\"#Prefetch-amp-Refetch\" class=\"headerlink\" title=\"Prefetch &amp; Refetch\"></a>Prefetch &amp; Refetch</h3><p>此处借鉴乐特关于 Prefetch Page 的源码,当用户打开节流模式或为低速网络时就不要 Prefetch.</p><p>Prefetch 可以提前缓存部分数据。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">const</span> $pjax_prefetch = <span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 节流和低速网络不要 Prefetch</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> nav = navigator;</span><br><span class=\"line\"> <span class=\"keyword\">const</span> { saveData, effectiveType } = nav.connection || nav.mozConnection || nav.webkitConnection || {};</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (saveData || <span class=\"regexp\">/2g/</span>.test(effectiveType)) <span class=\"keyword\">return</span> <span class=\"literal\">false</span>;</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"comment\">// 此处是 Blog 的一些常见链接</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> posts_list = <span class=\"built_in\">document</span>.querySelectorAll(<span class=\"string\">\".index-header a\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">for</span> (<span class=\"keyword\">let</span> i <span class=\"keyword\">in</span> posts_list) {</span><br><span class=\"line\"> <span class=\"comment\">// 全路径</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> thisLink = $pjax_fullpath(posts_list[i].href);</span><br><span class=\"line\"> <span class=\"comment\">// Session Storage 没有才 Fetch</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!sessionStorage.getItem(thisLink)) {</span><br><span class=\"line\"> fetch(thisLink + <span class=\"string\">\".page.json\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> sessionStorage.setItem(thisLink,res);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><p>Refetch 用于刷新已有的缓存(虽然 <code>SessionStorage</code> 关闭页面就没了)</p><p>其原理也很简单,<code>SessionStorage</code> 中所有的 Pjax 缓存重新获取就完事了。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">const</span> $pjax_refetch = <span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> sst = sessionStorage;</span><br><span class=\"line\"> <span class=\"keyword\">for</span> (<span class=\"keyword\">let</span> i <span class=\"keyword\">in</span> sst) {</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (i.startsWith(<span class=\"string\">\"http://\"</span>) || i.startsWith(<span class=\"string\">\"https://\"</span>)) {</span><br><span class=\"line\"> fetch(i + <span class=\"string\">\".page.json\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> sessionStorage.setItem(i, res);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><h3 id=\"一些优化\"><a href=\"#一些优化\" class=\"headerlink\" title=\"一些优化\"></a>一些优化</h3><h4 id=\"Prefetch-CSS-文件\"><a href=\"#Prefetch-CSS-文件\" class=\"headerlink\" title=\"Prefetch CSS 文件\"></a>Prefetch CSS 文件</h4><p>既然 CSS 文件需要补齐,那么打开页面 5s 后自动 Prefetch 可以提升速度。</p><blockquote><p>5s 后再获取是为了防止阻塞页面。</p></blockquote><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">setTimeout</span>(<span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"comment\">// Prefetch CSS 文件</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"search_css\"</span>)) {</span><br><span class=\"line\"> fetch(<span class=\"string\">\"/css/search.css\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> ele = <span class=\"built_in\">document</span>.createElement(<span class=\"string\">\"style\"</span>)</span><br><span class=\"line\"> ele.innerHTML = res;</span><br><span class=\"line\"> ele.id = <span class=\"string\">\"search_css\"</span>;</span><br><span class=\"line\"> <span class=\"built_in\">document</span>.body.appendChild(ele);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page_css\"</span>)) {</span><br><span class=\"line\"> fetch(<span class=\"string\">\"/css/page.css\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> ele = <span class=\"built_in\">document</span>.createElement(<span class=\"string\">\"style\"</span>)</span><br><span class=\"line\"> ele.innerHTML = res;</span><br><span class=\"line\"> ele.id = <span class=\"string\">\"page_css\"</span>;</span><br><span class=\"line\"> <span class=\"built_in\">document</span>.body.appendChild(ele);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> } </span><br><span class=\"line\">}, <span class=\"number\">5000</span>);</span><br></pre></td></tr></tbody></table></figure><h4 id=\"关于-Robots\"><a href=\"#关于-Robots\" class=\"headerlink\" title=\"关于 Robots\"></a>关于 Robots</h4><p>当你运行 <code>$pjax_convertAllLinks();</code> 后,你肯定会发现所有的链接都变成了 <code>javascript:$pjax_jump('/xxx');</code>。这对机器人来说很不友好。</p><p>所以,我们需要排除这些机器人。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">var</span> runningOnBrowser = <span class=\"keyword\">typeof</span> <span class=\"built_in\">window</span> !== <span class=\"string\">\"undefined\"</span>;</span><br><span class=\"line\"><span class=\"keyword\">var</span> isBot = runningOnBrowser &amp;&amp; !(<span class=\"string\">\"onscroll\"</span> <span class=\"keyword\">in</span> <span class=\"built_in\">window</span>) || <span class=\"keyword\">typeof</span> navigator !== <span class=\"string\">\"undefined\"</span> &amp;&amp; <span class=\"regexp\">/(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i</span>.test(navigator.userAgent);</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> (runningOnBrowser &amp;&amp; !isBot) {</span><br><span class=\"line\"> <span class=\"built_in\">setTimeout</span>(<span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">try</span>{$pjax_prefetch();}<span class=\"keyword\">catch</span>(e){}</span><br><span class=\"line\"> $pjax_convertAllLinks();</span><br><span class=\"line\"> }, <span class=\"number\">100</span>);</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><h2 id=\"最后\"><a href=\"#最后\" class=\"headerlink\" title=\"最后\"></a>最后</h2><p>在启用 Pjax 后,YFun's Blog 传输大小理论上最高缩小 3/4,性能速度均有提升。</p><p>如果你也在使用 Pjax,不妨试试看。</p><h2 id=\"还有一些错误\"><a href=\"#还有一些错误\" class=\"headerlink\" title=\"还有一些错误\"></a>还有一些错误</h2><p>如果你定义了 <code>onload</code> 等事件,页面没有刷新即代表没有变化,你需要在 <code>$pjax_jump()</code> 中简单清除一下这些信息。</p></div></div></div><div class=\"post-category\"><div id=\"p-meta-i\"><a class=\"hover-with-bg\" href=\"/categories/%E5%8D%9A%E5%AE%A2/\">博客</a> <a class=\"hover-with-bg\" href=\"/tags/%E5%8D%9A%E5%AE%A2/\"># 博客</a> <a class=\"hover-with-bg\" href=\"/tags/JavaScript/\"># JavaScript</a> <a class=\"hover-with-bg\" href=\"/tags/Pjax/\"># Pjax</a> <a class=\"hover-with-bg\" href=\"/tags/%E4%BC%98%E5%8C%96/\"># 优化</a></div></div><div class=\"post-footer\"><div class=\"donate text-center\"><span>喜欢这篇文章?为什么不考虑打赏一下作者呢?</span><div class=\"donate-way\"><a target=\"_blank\" rel=\"noopener\" href=\"https://afdian.net/@ocoke\" class=\"donate-btn button\">爱发电 (跨年立减优惠)</a></div></div><div class=\"post-copyright\"><p style=\"margin:5px 0\">文章作者:<a href=\"/\">YFun (@oCoke)</a></p><p style=\"margin:5px 0\">文章链接:<a href=\"https://blog.yfun.top/posts/2022/pjax/\">https://blog.yfun.top/posts/2022/pjax/</a></p><p style=\"margin:5px 0\">版权声明:本博客所有文章除特别声明外,均采用 <a target=\"_blank\" href=\"https://creativecommons.org/licenses/by-sa/4.0/deed.zh\" rel=\"nofollow noopener noopener\">CC BY-SA 4.0 协议</a> ,转载请注明出处,谢谢。</p></div></div><div class=\"comments\"><div id=\"miracle-comments\"><div id=\"tcomment\"><div style=\"margin:0 auto;width:100%;text-align:center\"><div class=\"donutSpinner\" style=\"margin:auto;width:25px;height:25px\"><div></div></div></div></div></div><script>function loadComment(){try{loadScriptFile({url:\"https://cdn1.tianli0.top/npm/twikoo@1.6.7/dist/twikoo.min.js\",loadType:\"async\",cb:()=>{twikoo.init({envId:\"https://twikoo.blog.yfun.top/\",el:\"#tcomment\",region:\"\",path:\"window.location.pathname\"})}})}catch(n){document.getElementById(\"loadCommentBtn\").innerText=\"无法加载 Twikoo 评论\",console.info(n)}}function loadTwikoo(){try{loadComment()}catch(n){console.log(n)}}var runningOnBrowser=\"undefined\"!=typeof window,isBot=runningOnBrowser&&!(\"onscroll\"in window)||\"undefined\"!=typeof navigator&&/(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent),supportsIntersectionObserver=runningOnBrowser&&\"IntersectionObserver\"in window;setTimeout(function(){var o;!isBot&&supportsIntersectionObserver?(o=new IntersectionObserver(function(n){n[0].isIntersecting&&(loadTwikoo(),o.disconnect())},{threshold:[0]})).observe(document.getElementById(\"miracle-comments\")):loadTwikoo()},1)</script></div></article>\n </div>\n <footer class=\"text-center\">\n <p style=\"display:flex;justify-content:center;align-items:center\">本网站由<a href=\"https://www.upyun.com/?utm_source=lianmeng&amp;utm_medium=referral\" target=\"_blank\" rel=\"nofollow noopener\">又拍云</a>提供 CDN 加速</p><script src=\"/sw-load.js\"></script><script src=\"/pjax.js\"></script><script>let script=document.createElement(\"script\");script.src=\"https://ca.yfun.top/link.js?cache=all\",script.onload=()=>{console.log(\"[INFO] CKY Analytics Enabled!\")},script.async=!0,script.defer=!0,script.setAttribute(\"data-website-id\",\"3e861252-ace3-4454-9a02-145c2123b0a4\"),document.body.appendChild(script)</script><p>© 2020 - 2022&nbsp;&nbsp;YFun's Blog</p><p>Powered by <a href=\"https://hexo.io\" target=\"_blank\">Hexo</a> | Theme by <a href=\"https://github.com/hifun-team/hexo-theme-miracle\" target=\"_blank\">Miracle</a></p>\n </footer>\n <div class=\"p-btn\">\n <a class=\"toc-btn\" id=\"toc-btn\"><i id=\"i-menu\"></i></a> <a class=\"toc-btn\" id=\"share-btn\"><i><svg t=\"1670124379155\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"2683\" width=\"25\" height=\"25\"><path d=\"M395.946667 234.666667v64H256v469.333333h512V522.666667h64V768a64 64 0 0 1-64 64H256a64 64 0 0 1-64-64V298.666667a64 64 0 0 1 64-64h139.946667z m335.850666-87.914667l150.848 150.826667-158.378666 158.4-45.269334-45.248L748.394667 341.333333H672c-121.685333 0-220.714667 97.024-223.914667 217.941334L448 565.333333v85.333334h-64v-85.333334C384 406.272 512.938667 277.333333 672 277.333333h99.861333l-85.312-85.333333 45.248-45.248z\" p-id=\"2684\" fill=\"var(--first-text-color)\"></path></svg> </i></a><a href=\"#top\" class=\"click-btn\"><i id=\"i-up\"></i></a>\n </div>\n ","path":"/posts/2022/pjax/index.html","extraJS":["function loadComment(){try{loadScriptFile({url:\"https://cdn1.tianli0.top/npm/twikoo@1.6.7/dist/twikoo.min.js\",loadType:\"async\",cb:()=>{twikoo.init({envId:\"https://twikoo.blog.yfun.top/\",el:\"#tcomment\",region:\"\",path:\"window.location.pathname\"})}})}catch(n){document.getElementById(\"loadCommentBtn\").innerText=\"无法加载 Twikoo 评论\",console.info(n)}}function loadTwikoo(){try{loadComment()}catch(n){console.log(n)}}var runningOnBrowser=\"undefined\"!=typeof window,isBot=runningOnBrowser&&!(\"onscroll\"in window)||\"undefined\"!=typeof navigator&&/(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent),supportsIntersectionObserver=runningOnBrowser&&\"IntersectionObserver\"in window;setTimeout(function(){var o;!isBot&&supportsIntersectionObserver?(o=new IntersectionObserver(function(n){n[0].isIntersecting&&(loadTwikoo(),o.disconnect())},{threshold:[0]})).observe(document.getElementById(\"miracle-comments\")):loadTwikoo()},1)","var postImg=document.querySelectorAll(\"article[class=page] img\");for(let t=0;t<postImg.length;t++)postImg[t].onclick=()=>{let i=document.createElement(\"div\");i.id=\"zoomImg\",i.innerHTML=`<div id=\"zoom-picture\"></div>\n <div class=\"poptrox-overlay\"\n style=\"position: fixed; left: 0px; top: 0px; z-index: 20000; width: 100%; height: 100%; text-align: center; cursor: zoom-out; opacity: 1;\">\n <div style=\"display:inline-block;height:100%;vertical-align:middle;\"></div>\n <div\n style=\"position:absolute;left:0;top:0;width:100%;height:100%;background:#000000;opacity:0;filter:alpha(opacity=0);\">\n </div>\n <div class=\"poptrox-popup\"\n style=\"display: inline-block; vertical-align: middle; position: relative; z-index: 1; cursor: zoom-out; min-width: 10px; min-height: 10px; width: auto; height: auto;\">\n <div class=\"loader\" style=\"display: none;\"></div>\n <div class=\"pic\" style=\"text-indent: 0px;\"><img\n src=\"${postImg[t].srcset||postImg[t].src}\" alt=\"Loading...\"\n style=\"vertical-align: bottom; max-width: 85vw; max-height: 85vh;\"></div>\n </div>\n </div>`,document.body.appendChild(i),document.querySelector(\"#zoomImg\").onclick=()=>{document.querySelector(\"#zoomImg\").remove()}}","try{var io=new IntersectionObserver(function(e){e.forEach(function(e){e.isIntersecting&&((e=e.target).srcset=e.getAttribute(\"data-srcset\"),e.className+=\" loaded\",io.unobserve(e))})})}catch(e){console.info(e)}query(\".lazyload-img\").forEach(function(e){try{io.observe(e)}catch(e){console.info(e)}})","query(\"#toc-btn\")[0].onclick=()=>{query(\".post-toc\")[0].innerHTML&&toggleClass(\".post-toc\",\"display-inline\")},query(\".post-toc\")[0].innerHTML||addClass(\"#toc-btn\",\"display-none\")","query(\"#share-btn\")[0].onclick=async()=>{var t=`${location.protocol}//${location.hostname}${location.port&&\":\"+location.port}${location.pathname}#read=${sessionStorage.getItem(location.pathname+\"_read_y\")||\"\"}`;try{await navigator.clipboard.writeText(t),prompt_core(\"分享链接已经复制至剪贴板\",4800,!0)}catch(o){prompt_core(\"分享链接复制失败,请手动复制<br/>\"+t,4800,!1)}}","const getScrollPosition=(e=window)=>({x:void 0!==e.pageXOffset?e.pageXOffset:e.scrollLeft,y:void 0!==e.pageYOffset?e.pageYOffset:e.scrollTop});var wx=document.getElementsByClassName(\"article-m\")[0].clientWidth,wy=document.getElementsByClassName(\"article-m\")[0].clientHeight;function windowScroll(){wx=document.getElementsByClassName(\"article-m\")[0].clientWidth,wy=document.getElementsByClassName(\"article-m\")[0].clientHeight;var e=`${Math.round(getScrollPosition().y)}:${wx}:${wy}`;sessionStorage.setItem(location.pathname+\"_read_y\",e)}setTimeout(()=>{if(1<location.hash.split(\"#read=\").length){prompt_core(\"已有阅读进度,正在跳转\",4800,!0);let e=location.hash.split(\"#read=\")[1];e=e.split(\":\"),window.scrollTo({top:Math.round(Number(e[0])*Number(e[1]*Number(e[2]/wx/wy))),behavior:\"smooth\"})}else{let e=sessionStorage.getItem(location.pathname+\"_read_y\")||\"0:0:0\";e=e.split(\":\"),\"0\"!=e[0]&&prompt_core(\"已有阅读进度,正在跳转\",4800,!0),window.scrollTo({top:Math.round(Number(e[0])*Number(e[1]*Number(e[2]/wx/wy))),behavior:\"smooth\"})}},500),window.onscroll=windowScroll"]}
{"title":"什么年代还在用传统 Pjax? —— 自定义 Pjax 提升页面加载速度 - YFun's Blog","page":"\n <div class=\"mg-top\">\n <article class=\"page\"><div id=\"post-meta-m\"><div class=\"post-meta\" id=\"post-meta\"><h3>什么年代还在用传统 Pjax? —— 自定义 Pjax 提升页面加载速度</h3><span class=\"post-meta-label\">YFun (@oCoke) </span><span class=\"post-meta-label\"><span class=\"p-dot\"></span> <time datetime=\"2022-12-15 12:30\" pubdate=\"\">2022-12-15 </time></span><span class=\"post-meta\"><span class=\"p-dot\"></span> 共 2.8k 字</span></div></div><div class=\"article-m\"><div class=\"post-toc\"><ol class=\"toc\"><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E5%89%8D%E8%A8%80\"><span class=\"toc-number\">1.</span> <span class=\"toc-text\">前言</span></a></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E5%8E%9F%E7%90%86\"><span class=\"toc-number\">2.</span> <span class=\"toc-text\">原理</span></a></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E5%88%86%E6%9E%90\"><span class=\"toc-number\">3.</span> <span class=\"toc-text\">分析</span></a></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E6%9C%80%E5%B0%8F%E5%8C%96%E7%9A%84%E6%95%B0%E6%8D%AE%E6%8E%A5%E5%8F%A3\"><span class=\"toc-number\">4.</span> <span class=\"toc-text\">最小化的数据接口</span></a><ol class=\"toc-child\"><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E8%BD%BD%E5%85%A5-HTML\"><span class=\"toc-number\">4.1.</span> <span class=\"toc-text\">载入 HTML</span></a></li><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E6%96%87%E4%BB%B6%E9%80%92%E5%BD%92\"><span class=\"toc-number\">4.2.</span> <span class=\"toc-text\">文件递归</span></a></li><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84\"><span class=\"toc-number\">4.3.</span> <span class=\"toc-text\">基本结构</span></a></li></ol></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E5%89%8D%E7%AB%AF-pjax-js\"><span class=\"toc-number\">5.</span> <span class=\"toc-text\">前端 pjax.js</span></a><ol class=\"toc-child\"><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E6%9B%BF%E6%8D%A2%E9%93%BE%E6%8E%A5\"><span class=\"toc-number\">5.1.</span> <span class=\"toc-text\">替换链接</span></a></li><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E8%B7%B3%E8%BD%AC\"><span class=\"toc-number\">5.2.</span> <span class=\"toc-text\">跳转</span></a></li><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#Prefetch-amp-Refetch\"><span class=\"toc-number\">5.3.</span> <span class=\"toc-text\">Prefetch &amp; Refetch</span></a></li><li class=\"toc-item toc-level-3\"><a class=\"toc-link\" href=\"#%E4%B8%80%E4%BA%9B%E4%BC%98%E5%8C%96\"><span class=\"toc-number\">5.4.</span> <span class=\"toc-text\">一些优化</span></a><ol class=\"toc-child\"><li class=\"toc-item toc-level-4\"><a class=\"toc-link\" href=\"#Prefetch-CSS-%E6%96%87%E4%BB%B6\"><span class=\"toc-number\">5.4.1.</span> <span class=\"toc-text\">Prefetch CSS 文件</span></a></li><li class=\"toc-item toc-level-4\"><a class=\"toc-link\" href=\"#%E5%85%B3%E4%BA%8E-Robots\"><span class=\"toc-number\">5.4.2.</span> <span class=\"toc-text\">关于 Robots</span></a></li></ol></li></ol></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E6%9C%80%E5%90%8E\"><span class=\"toc-number\">6.</span> <span class=\"toc-text\">最后</span></a></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E8%BF%98%E6%9C%89%E4%B8%80%E4%BA%9B%E9%94%99%E8%AF%AF\"><span class=\"toc-number\">7.</span> <span class=\"toc-text\">还有一些错误</span></a></li><li class=\"toc-item toc-level-2\"><a class=\"toc-link\" href=\"#%E5%B9%BF%E5%91%8A%E6%97%B6%E9%97%B4\"><span class=\"toc-number\">8.</span> <span class=\"toc-text\">广告时间</span></a></li></ol></div><div id=\"article\"><div id=\"post-content\" class=\"markdown-body textretty\"><h2 id=\"前言\"><a href=\"#前言\" class=\"headerlink\" title=\"前言\"></a>前言</h2><p>Hexo 属于静态博客,很多同学给自己的博客加上 Pjax 是为了音乐播放器等功能不中断。</p><p>之前我也想过对博客和主题加入 Pjax 支持,但经过一番分析后觉得,这不仅引入了一个巨大的 <code>jquery.pjax.js</code>,反而优化效果不明显。</p><h2 id=\"原理\"><a href=\"#原理\" class=\"headerlink\" title=\"原理\"></a>原理</h2><p>其实,Pjax 的原理并不复杂。或许说,README 一开始就告诉你了:</p><figure class=\"highlight plain\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">pjax = pushState + ajax</span><br></pre></td></tr></tbody></table></figure><p>其中 <code>ajax</code> 用于页面的新内容,<code>pushState</code> 改变浏览器状态。</p><p>很简单吧。</p><p>事实上,<code>pjax</code> 并不应该应用于整个页面当中。而应该只是局部更改。</p><p>这样,Blog 当中的导航栏、样式文件等就不需要重复下载与预览。</p><h2 id=\"分析\"><a href=\"#分析\" class=\"headerlink\" title=\"分析\"></a>分析</h2><p>以我使用 Miracle 为主题的博客为例,进入首页,按 <code>F12</code> 查看页面 Elements.</p><p><picture><source class=\"lazyload-img\" data-srcset=\"https://cdn.jsdelivr.net/npm/ocoke-osg@0.0.18/rawimg/2022-12-15_12-32-37.webp\" srcset=\"data:image/gif;base64,R0lGODlhAQABAID/AP///wAAACwAAAAAAQABAAACAkQBADs\" type=\"image/webp\"><img webp-comp=\"\" src=\"https://cdn.jsdelivr.net/npm/ocoke-osg@0.0.18/rawimg/2022-12-15_12-32-37.png\" class=\"lazyload-img\" data-srcset=\"https://cdn.jsdelivr.net/npm/ocoke-osg@0.0.18/rawimg/2022-12-15_12-32-37.png\" srcset=\"data:image/gif;base64,R0lGODlhAQABAID/AP///wAAACwAAAAAAQABAAACAkQBADs\"></picture></p><p>可以发现,页面主要更改的也就是 <code>#page-main</code> 部分,只需要实现动态刷新这部分的内容就可以了。</p><p>那怎么实现呢?</p><h2 id=\"最小化的数据接口\"><a href=\"#最小化的数据接口\" class=\"headerlink\" title=\"最小化的数据接口\"></a>最小化的数据接口</h2><p>现在生成的页面当中,有 <code>&lt;head&gt;</code> 部分声明大量样式与元信息,<code>&lt;body&gt;</code> 之下重复的页脚、导航栏,还有每个页面下方都有的一些 <code>&lt;script&gt;</code>。</p><p>很明显,我们不需要这些。我们只要 <code>#page-main</code> 中的主要内容。</p><p>最重要的是,Hexo 是静态博客,这一点只能在生成文件时进行。</p><h3 id=\"载入-HTML\"><a href=\"#载入-HTML\" class=\"headerlink\" title=\"载入 HTML\"></a>载入 HTML</h3><p>我是用 Cheerio 模块帮我完成这一工作。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">const</span> cheerio = <span class=\"built_in\">require</span>(<span class=\"string\">'cheerio'</span>);</span><br><span class=\"line\"><span class=\"keyword\">const</span> fs = <span class=\"built_in\">require</span>(<span class=\"string\">\"fs\"</span>);</span><br><span class=\"line\"><span class=\"keyword\">const</span> path = <span class=\"built_in\">require</span>(<span class=\"string\">\"path\"</span>);</span><br><span class=\"line\"><span class=\"keyword\">const</span> filePath = path.resolve(<span class=\"string\">'public/'</span>);</span><br></pre></td></tr></tbody></table></figure><p>定义一个 <code>parse function</code>,打开文件并解析相关信息,顺便把不是 HTML 的文件排除掉。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">const</span> parse = <span class=\"function\">(<span class=\"params\">filename, fullpath</span>) =&gt;</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 不是 .html 我不要</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!filename.endsWith(<span class=\"string\">\".html\"</span>)) {</span><br><span class=\"line\"> <span class=\"keyword\">return</span> <span class=\"literal\">false</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><p>然后通过 Cheerio 解析 HTML:</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">{...</span><br><span class=\"line\"> <span class=\"comment\">// 组合新文件名</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> filepath = fullpath+<span class=\"string\">\".page.json\"</span>;</span><br><span class=\"line\"> <span class=\"comment\">// 读取文件内容</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> pageContent = fs.readFileSync(fullpath).toString();</span><br><span class=\"line\"> <span class=\"comment\">// 解析页面内容</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> $pg = cheerio.load(pageContent);</span><br><span class=\"line\"> <span class=\"keyword\">let</span> rtData = {};</span><br><span class=\"line\">...}</span><br></pre></td></tr></tbody></table></figure><p>然后获取页面的标题和 <code>#page-main</code> 下的 HTML.</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">{...</span><br><span class=\"line\">\t<span class=\"comment\">// 页面标题</span></span><br><span class=\"line\"> rtData.title = $pg(<span class=\"string\">\"title\"</span>).text();</span><br><span class=\"line\"> <span class=\"comment\">// OR $pg(\"#page-main\").html()</span></span><br><span class=\"line\"> <span class=\"comment\">// 我这么写是因为主题 #page-main 下还有 script 无法执行</span></span><br><span class=\"line\"> rtData.page = <span class=\"string\">`</span></span><br><span class=\"line\"><span class=\"string\"> &lt;div class=\"mg-top\"&gt;</span></span><br><span class=\"line\"><span class=\"string\"> <span class=\"subst\">${$pg(<span class=\"string\">\".mg-top\"</span>).html() || <span class=\"string\">\"\"</span>}</span></span></span><br><span class=\"line\"><span class=\"string\"> &lt;/div&gt;</span></span><br><span class=\"line\"><span class=\"string\"> &lt;footer class=\"text-center\"&gt;</span></span><br><span class=\"line\"><span class=\"string\"> <span class=\"subst\">${$pg(<span class=\"string\">\"footer\"</span>).html() || <span class=\"string\">\"\"</span>}</span></span></span><br><span class=\"line\"><span class=\"string\"> &lt;/footer&gt;</span></span><br><span class=\"line\"><span class=\"string\"> &lt;div class=\"p-btn\"&gt;</span></span><br><span class=\"line\"><span class=\"string\"> <span class=\"subst\">${$pg(<span class=\"string\">\".p-btn\"</span>).html() || <span class=\"string\">\"\"</span>}</span></span></span><br><span class=\"line\"><span class=\"string\"> &lt;/div&gt;</span></span><br><span class=\"line\"><span class=\"string\"> `</span>;</span><br><span class=\"line\"> rtData.path = filename;</span><br><span class=\"line\">...}</span><br></pre></td></tr></tbody></table></figure><p>页面中还有一些 <code>script</code>,比如阅读进度、懒加载等。所以需要一个 <code>extraJS</code> 放置额外的 Script.</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">{...</span><br><span class=\"line\"> rtData.extraJS = []</span><br><span class=\"line\"> <span class=\"comment\">// 只解析 #page-main 下的 script</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> $pageMain = cheerio.load($pg(<span class=\"string\">\"#page-main\"</span>).html());</span><br><span class=\"line\"> $pageMain(<span class=\"string\">'script'</span>).map(<span class=\"function\"><span class=\"keyword\">function</span>(<span class=\"params\">i, el</span>) </span>{</span><br><span class=\"line\"> <span class=\"comment\">// 尝试往 extraJS 中 push 相关代码</span></span><br><span class=\"line\"> <span class=\"keyword\">try</span> {rtData.extraJS.push($pageMain(<span class=\"built_in\">this</span>)[<span class=\"number\">0</span>].children[<span class=\"number\">0</span>].data);} <span class=\"keyword\">catch</span>(e) {}</span><br><span class=\"line\"> $pageMain(<span class=\"built_in\">this</span>).remove();</span><br><span class=\"line\"> });</span><br><span class=\"line\">...}</span><br></pre></td></tr></tbody></table></figure><p>最后,将 JSON 写入文件中。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">{...</span><br><span class=\"line\"> fs.writeFileSync(filepath, <span class=\"built_in\">JSON</span>.stringify(rtData));</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><h3 id=\"文件递归\"><a href=\"#文件递归\" class=\"headerlink\" title=\"文件递归\"></a>文件递归</h3><p>我们还需要一个函数递归 <code>public</code> 目录下的所有文件,这个不用多说。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">function</span> <span class=\"title\">fileDisplay</span>(<span class=\"params\">filePath</span>) </span>{</span><br><span class=\"line\"> <span class=\"comment\">// 根据文件路径读取文件,返回文件列表</span></span><br><span class=\"line\"> fs.readdir(filePath, <span class=\"function\"><span class=\"keyword\">function</span>(<span class=\"params\">err, files</span>) </span>{</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (err) {</span><br><span class=\"line\"> <span class=\"built_in\">console</span>.warn(err, <span class=\"string\">\"读取文件夹错误!\"</span>)</span><br><span class=\"line\"> } <span class=\"keyword\">else</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 遍历读取到的文件列表</span></span><br><span class=\"line\"> files.forEach(<span class=\"function\"><span class=\"keyword\">function</span>(<span class=\"params\">filename</span>) </span>{</span><br><span class=\"line\"> <span class=\"comment\">// 获取当前文件的绝对路径</span></span><br><span class=\"line\"> <span class=\"keyword\">var</span> filedir = path.join(filePath, filename);</span><br><span class=\"line\"> <span class=\"keyword\">var</span> fullname = filedir.split(<span class=\"string\">\"public\"</span>)[<span class=\"number\">1</span>];</span><br><span class=\"line\"> fs.stat(filedir, <span class=\"function\"><span class=\"keyword\">function</span>(<span class=\"params\">eror, stats</span>) </span>{</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (eror) {</span><br><span class=\"line\"> <span class=\"built_in\">console</span>.warn(<span class=\"string\">'获取文件 Stats 失败!'</span>);</span><br><span class=\"line\"> } <span class=\"keyword\">else</span> {</span><br><span class=\"line\"> <span class=\"keyword\">var</span> isFile = stats.isFile(); <span class=\"comment\">// 是文件</span></span><br><span class=\"line\"> <span class=\"keyword\">var</span> isDir = stats.isDirectory(); <span class=\"comment\">// 是文件夹</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (isFile) {</span><br><span class=\"line\"> parse(fullname, filedir);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (isDir) {</span><br><span class=\"line\"> fileDisplay(filedir); <span class=\"comment\">// 递归,如果是文件夹,就继续遍历该文件夹下面的文件</span></span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> });</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> });</span><br><span class=\"line\">}</span><br><span class=\"line\">fileDisplay(filePath);</span><br></pre></td></tr></tbody></table></figure><p>最后运行这个 Node.js 文件,就可以看到 <code>public/</code> 目录下多出很多 <code>***.page.json</code> 文件。</p><h3 id=\"基本结构\"><a href=\"#基本结构\" class=\"headerlink\" title=\"基本结构\"></a>基本结构</h3><p>这些文件内容也很简单,基本如下:</p><figure class=\"highlight json\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> <span class=\"comment\">// 页面的标题</span></span><br><span class=\"line\"> <span class=\"attr\">\"title\"</span>: <span class=\"string\">\"Hello World\"</span>,</span><br><span class=\"line\"> <span class=\"comment\">// 内容</span></span><br><span class=\"line\"> <span class=\"attr\">\"page\"</span>: <span class=\"string\">\"...\"</span>,</span><br><span class=\"line\"> <span class=\"comment\">// 路径</span></span><br><span class=\"line\"> <span class=\"attr\">\"path\"</span>: <span class=\"string\">\"/foo/bar\"</span>,</span><br><span class=\"line\"> <span class=\"comment\">// JS</span></span><br><span class=\"line\"> <span class=\"attr\">\"extraJS\"</span>: ['alert(<span class=\"string\">\"Hello World\"</span>);']</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><h2 id=\"前端-pjax-js\"><a href=\"#前端-pjax-js\" class=\"headerlink\" title=\"前端 pjax.js\"></a>前端 <code>pjax.js</code></h2><p>新建一个 <code>pjax.js</code>。</p><h3 id=\"替换链接\"><a href=\"#替换链接\" class=\"headerlink\" title=\"替换链接\"></a>替换链接</h3><p>我们需要先将页面当中所有本站链接转为 Pjax 的 Jump 函数。</p><p>判断条件是:有链接,不带 hash,且为本站链接</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// 转换页面中的链接为 Pjax 链接</span></span><br><span class=\"line\"><span class=\"keyword\">const</span> $pjax_convertAllLinks = <span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\">\t<span class=\"comment\">// 所有的 a 标签</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> linkElements = <span class=\"built_in\">document</span>.querySelectorAll(<span class=\"string\">\"a\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">for</span> (<span class=\"keyword\">let</span> i <span class=\"keyword\">of</span> linkElements) {</span><br><span class=\"line\"> <span class=\"comment\">// 有链接,不带 hash,且为本站链接</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (i.href &amp;&amp; !i.href.includes(<span class=\"string\">\"/#\"</span>) &amp;&amp; (i.href.startsWith(<span class=\"string\">\"/\"</span>) || i.href.match(<span class=\"keyword\">new</span> <span class=\"built_in\">RegExp</span>(<span class=\"built_in\">window</span>.location.hostname)))) {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> thisLink = <span class=\"keyword\">new</span> URL(i.href).pathname+<span class=\"keyword\">new</span> URL(i.href).hash;</span><br><span class=\"line\"> i.href = <span class=\"string\">`javascript:$pjax_jump('<span class=\"subst\">${thisLink}</span>');`</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><p>另外,要转化页面链接为全路径。</p><p>这里参考了下 ChenYFan 的 Service Worker 函数,需要根据实际情况做出调整。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// 转换路径为全路径</span></span><br><span class=\"line\"><span class=\"keyword\">const</span> $pjax_fullpath = <span class=\"function\">(<span class=\"params\">path</span>) =&gt;</span> {</span><br><span class=\"line\"> path = path.split(<span class=\"string\">'?'</span>)[<span class=\"number\">0</span>].split(<span class=\"string\">'#'</span>)[<span class=\"number\">0</span>]</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (path.match(<span class=\"regexp\">/\\/$/</span>)) {</span><br><span class=\"line\"> path += <span class=\"string\">'index.html'</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!path.match(<span class=\"regexp\">/\\.[a-zA-Z]+$/</span>)) {</span><br><span class=\"line\"> path += <span class=\"string\">'/index.html'</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">return</span> path;</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">// $pjax_fullpath('/') =&gt; /index.html</span></span><br></pre></td></tr></tbody></table></figure><h3 id=\"跳转\"><a href=\"#跳转\" class=\"headerlink\" title=\"跳转\"></a>跳转</h3><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// 跳转页面</span></span><br><span class=\"line\"><span class=\"keyword\">const</span> $pjax_jump = <span class=\"keyword\">async</span> (path) =&gt; {</span><br><span class=\"line\"> <span class=\"keyword\">try</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 是 # 就别跳转了</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (path.startsWith(<span class=\"string\">\"#\"</span>)) {</span><br><span class=\"line\"> <span class=\"built_in\">window</span>.hash = path;</span><br><span class=\"line\"> <span class=\"keyword\">return</span> <span class=\"literal\">false</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"comment\">// 加载动画</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> loading = <span class=\"built_in\">document</span>.createElement(<span class=\"string\">'div'</span>);</span><br><span class=\"line\"> loading.innerHTML = <span class=\"string\">`&lt;div style=\"position: fixed;top:0;left:0;z-index:99999;display: block;width: 100%;height: 4px;overflow: hidden;background-color: rgba(63,81,181,.2);border-radius: 2px;\"&gt;&lt;div class=\"progress-indeterminate\" style=\"background-color: #3f51b5;\"&gt;&lt;/div&gt;&lt;style&gt;#page-main{transition:0.2s;}.progress-indeterminate::before{position:absolute;top:0;bottom:0;left:0;background-color:inherit;-webkit-animation:mdui-progress-indeterminate 2s linear infinite;animation:mdui-progress-indeterminate 2s linear infinite;content:' ';will-change:left,width;}.progress-indeterminate::after{position:absolute;top:0;bottom:0;left:0;background-color:inherit;-webkit-animation:mdui-progress-indeterminate-short 2s linear infinite;animation:mdui-progress-indeterminate-short 2s linear infinite;content:' ';will-change:left,width;}@keyframes mdui-progress-indeterminate{0%{left:0;width:0;}50%{left:30%;width:70%;}75%{left:100%;width:0;}}@keyframes mdui-progress-indeterminate-short{0%{left:0;width:0;}50%{left:0;width:0;}75%{left:0;width:25%;}100%{left:100%;width:0;}}&lt;/style&gt;&lt;/div&gt;`</span>;</span><br><span class=\"line\"> <span class=\"comment\">// 在 body 后加入 &lt;div&gt;</span></span><br><span class=\"line\"> <span class=\"built_in\">document</span>.body.appendChild(loading);</span><br><span class=\"line\"> <span class=\"comment\">// 如果页面中没有 page.css 或 search.css,为防止样式错乱,需要在加载过程中隐藏页面内容</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page_css\"</span>) || !<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"search_css\"</span>)) <span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page-main\"</span>).style.opacity = <span class=\"number\">0</span>;</span><br><span class=\"line\"> <span class=\"comment\">// 获取页面数据</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> pageData;</span><br><span class=\"line\"> <span class=\"comment\">// 看看 SessionStorage 里有没有缓存</span></span><br><span class=\"line\"> <span class=\"comment\">// 依赖后文的 prefetch</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (sessionStorage.getItem(<span class=\"string\">`<span class=\"subst\">${location.protocol}</span>//<span class=\"subst\">${location.hostname}</span><span class=\"subst\">${location.port ? <span class=\"string\">\":\"</span>+location.port:location.port}</span><span class=\"subst\">${$pjax_fullpath(path)}</span>`</span>)) {</span><br><span class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">\"FROM SESSIONSTORAGE\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">try</span> {</span><br><span class=\"line\"> pageData = <span class=\"built_in\">JSON</span>.parse(sessionStorage.getItem(<span class=\"string\">`<span class=\"subst\">${location.protocol}</span>//<span class=\"subst\">${location.hostname}</span><span class=\"subst\">${location.port ? <span class=\"string\">\":\"</span>+location.port:location.port}</span><span class=\"subst\">${$pjax_fullpath(path)}</span>`</span>));</span><br><span class=\"line\"> } <span class=\"keyword\">catch</span>(e) {</span><br><span class=\"line\"> <span class=\"comment\">// 还是出错就从服务器获取</span></span><br><span class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">\"FROM SERVER\"</span>);</span><br><span class=\"line\"> pageData = <span class=\"keyword\">await</span> fetch($pjax_fullpath(path) + <span class=\"string\">\".page.json\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.json());</span><br><span class=\"line\"> <span class=\"comment\">// 写到 SessionStorage 中</span></span><br><span class=\"line\"> sessionStorage.setItem(<span class=\"string\">`<span class=\"subst\">${location.protocol}</span>//<span class=\"subst\">${location.hostname}</span><span class=\"subst\">${location.port ? <span class=\"string\">\":\"</span>+location.port:location.port}</span><span class=\"subst\">${$pjax_fullpath(path)}</span>`</span>, <span class=\"built_in\">JSON</span>.stringify(pageData));</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } <span class=\"keyword\">else</span> {</span><br><span class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">\"FROM SERVER\"</span>);</span><br><span class=\"line\"> <span class=\"comment\">// fetch JSON</span></span><br><span class=\"line\"> pageData = <span class=\"keyword\">await</span> fetch($pjax_fullpath(path) + <span class=\"string\">\".page.json\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.json());</span><br><span class=\"line\"> sessionStorage.setItem(<span class=\"string\">`<span class=\"subst\">${location.protocol}</span>//<span class=\"subst\">${location.hostname}</span><span class=\"subst\">${location.port ? <span class=\"string\">\":\"</span>+location.port:location.port}</span><span class=\"subst\">${$pjax_fullpath(path)}</span>`</span>, <span class=\"built_in\">JSON</span>.stringify(pageData));</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"comment\">// 补齐页面 CSS</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"search_css\"</span>)) {</span><br><span class=\"line\"> fetch(<span class=\"string\">\"/css/search.css\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> ele = <span class=\"built_in\">document</span>.createElement(<span class=\"string\">\"style\"</span>);</span><br><span class=\"line\"> ele.innerHTML = res;</span><br><span class=\"line\"> ele.id = <span class=\"string\">\"search_css\"</span>;</span><br><span class=\"line\"> <span class=\"built_in\">document</span>.body.appendChild(ele);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page_css\"</span>)) {</span><br><span class=\"line\"> fetch(<span class=\"string\">\"/css/page.css\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> ele = <span class=\"built_in\">document</span>.createElement(<span class=\"string\">\"style\"</span>);</span><br><span class=\"line\"> ele.innerHTML = res;</span><br><span class=\"line\"> ele.id = <span class=\"string\">\"page_css\"</span>;</span><br><span class=\"line\"> <span class=\"built_in\">document</span>.body.appendChild(ele);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!pageData) <span class=\"keyword\">return</span> <span class=\"literal\">false</span>;</span><br><span class=\"line\"> <span class=\"comment\">// 组合 state</span></span><br><span class=\"line\"> <span class=\"keyword\">var</span> state = { <span class=\"attr\">title</span>: <span class=\"string\">''</span>, <span class=\"attr\">url</span>: <span class=\"built_in\">window</span>.location.href.split(<span class=\"string\">\"?\"</span>)[<span class=\"number\">0</span>] };</span><br><span class=\"line\"> <span class=\"comment\">// 利用 history.pushState() 修改地址栏而不跳转</span></span><br><span class=\"line\"> history.pushState(state, <span class=\"string\">''</span>, path);</span><br><span class=\"line\"> <span class=\"comment\">// 修改页面标题</span></span><br><span class=\"line\"> <span class=\"built_in\">document</span>.title = pageData.title;</span><br><span class=\"line\"> <span class=\"built_in\">setTimeout</span>(<span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 滚动到页面顶部</span></span><br><span class=\"line\"> <span class=\"built_in\">window</span>.scrollTo({<span class=\"attr\">top</span>: <span class=\"number\">0</span>, <span class=\"attr\">behavior</span>: <span class=\"string\">\"smooth\"</span>});</span><br><span class=\"line\"> <span class=\"comment\">// 写入 HTML</span></span><br><span class=\"line\"> <span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page-main\"</span>).innerHTML = pageData.page;</span><br><span class=\"line\"> <span class=\"built_in\">window</span>.onscroll = <span class=\"literal\">null</span>;</span><br><span class=\"line\"> <span class=\"keyword\">for</span> (<span class=\"keyword\">let</span> i <span class=\"keyword\">in</span> pageData.extraJS) {</span><br><span class=\"line\"> <span class=\"keyword\">try</span> {</span><br><span class=\"line\"> <span class=\"comment\">// eval() 执行 JS</span></span><br><span class=\"line\"> <span class=\"built_in\">eval</span>(pageData.extraJS[i]);</span><br><span class=\"line\"> } <span class=\"keyword\">catch</span>(e) {}</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">try</span>{$pjax_prefetch();}<span class=\"keyword\">catch</span>(e){}</span><br><span class=\"line\"> <span class=\"comment\">// 再次转换所有链接</span></span><br><span class=\"line\"> $pjax_convertAllLinks();</span><br><span class=\"line\"> }, <span class=\"number\">200</span>);</span><br><span class=\"line\"> <span class=\"built_in\">setTimeout</span>(<span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 重新显示页面</span></span><br><span class=\"line\"> <span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page-main\"</span>).style.opacity = <span class=\"number\">1</span>;</span><br><span class=\"line\"> loading.remove();</span><br><span class=\"line\"> }, <span class=\"number\">1000</span>);</span><br><span class=\"line\"> } <span class=\"keyword\">catch</span>(e) {</span><br><span class=\"line\"> <span class=\"comment\">// 有报错 直接跳转</span></span><br><span class=\"line\"> <span class=\"built_in\">console</span>.warn(e);</span><br><span class=\"line\"> <span class=\"built_in\">window</span>.location.href = path;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><p>如果使用 <code>window.location.href</code> 修改,那么页面就会刷新。<br>为了实现无刷新跳转,必须要使用 <code>pushState()</code> 更改。</p><p>执行 JavaScript 方面使用 <code>eval()</code> 函数。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// 组合 state</span></span><br><span class=\"line\"><span class=\"keyword\">var</span> state = { <span class=\"attr\">title</span>: <span class=\"string\">''</span>, <span class=\"attr\">url</span>: <span class=\"built_in\">window</span>.location.href.split(<span class=\"string\">\"?\"</span>)[<span class=\"number\">0</span>] };</span><br><span class=\"line\"><span class=\"comment\">// 利用 history.pushState() 修改地址栏而不跳转</span></span><br><span class=\"line\">history.pushState(state, <span class=\"string\">''</span>, path);</span><br><span class=\"line\"><span class=\"comment\">// 修改页面标题</span></span><br><span class=\"line\"><span class=\"built_in\">document</span>.title = pageData.title;</span><br><span class=\"line\"><span class=\"comment\">// 滚动到页面顶部</span></span><br><span class=\"line\"><span class=\"built_in\">window</span>.scrollTo({<span class=\"attr\">top</span>: <span class=\"number\">0</span>, <span class=\"attr\">behavior</span>: <span class=\"string\">\"smooth\"</span>});</span><br><span class=\"line\"><span class=\"comment\">// 写入 HTML</span></span><br><span class=\"line\"><span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page-main\"</span>).innerHTML = pageData.page;</span><br><span class=\"line\"><span class=\"built_in\">window</span>.onscroll = <span class=\"literal\">null</span>;</span><br><span class=\"line\"><span class=\"keyword\">for</span> (<span class=\"keyword\">let</span> i <span class=\"keyword\">in</span> pageData.extraJS) {</span><br><span class=\"line\"> <span class=\"keyword\">try</span> {</span><br><span class=\"line\"> <span class=\"comment\">// eval() 执行 JS</span></span><br><span class=\"line\"> <span class=\"built_in\">eval</span>(pageData.extraJS[i]);</span><br><span class=\"line\"> } <span class=\"keyword\">catch</span>(e) {}</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><h3 id=\"Prefetch-amp-Refetch\"><a href=\"#Prefetch-amp-Refetch\" class=\"headerlink\" title=\"Prefetch &amp; Refetch\"></a>Prefetch &amp; Refetch</h3><p>此处借鉴乐特关于 Prefetch Page 的源码,当用户打开节流模式或为低速网络时就不要 Prefetch.</p><p>Prefetch 可以提前缓存部分数据。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">const</span> $pjax_prefetch = <span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 节流和低速网络不要 Prefetch</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> nav = navigator;</span><br><span class=\"line\"> <span class=\"keyword\">const</span> { saveData, effectiveType } = nav.connection || nav.mozConnection || nav.webkitConnection || {};</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (saveData || <span class=\"regexp\">/2g/</span>.test(effectiveType)) <span class=\"keyword\">return</span> <span class=\"literal\">false</span>;</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"comment\">// 此处是 Blog 的一些常见链接</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> posts_list = <span class=\"built_in\">document</span>.querySelectorAll(<span class=\"string\">\".index-header a\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">for</span> (<span class=\"keyword\">let</span> i <span class=\"keyword\">in</span> posts_list) {</span><br><span class=\"line\"> <span class=\"comment\">// 全路径</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> thisLink = $pjax_fullpath(posts_list[i].href);</span><br><span class=\"line\"> <span class=\"comment\">// Session Storage 没有才 Fetch</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!sessionStorage.getItem(thisLink)) {</span><br><span class=\"line\"> fetch(thisLink + <span class=\"string\">\".page.json\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> sessionStorage.setItem(thisLink,res);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><p>Refetch 用于刷新已有的缓存(虽然 <code>SessionStorage</code> 关闭页面就没了)</p><p>其原理也很简单,<code>SessionStorage</code> 中所有的 Pjax 缓存重新获取就完事了。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">const</span> $pjax_refetch = <span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> sst = sessionStorage;</span><br><span class=\"line\"> <span class=\"keyword\">for</span> (<span class=\"keyword\">let</span> i <span class=\"keyword\">in</span> sst) {</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (i.startsWith(<span class=\"string\">\"http://\"</span>) || i.startsWith(<span class=\"string\">\"https://\"</span>)) {</span><br><span class=\"line\"> fetch(i + <span class=\"string\">\".page.json\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> sessionStorage.setItem(i, res);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><h3 id=\"一些优化\"><a href=\"#一些优化\" class=\"headerlink\" title=\"一些优化\"></a>一些优化</h3><h4 id=\"Prefetch-CSS-文件\"><a href=\"#Prefetch-CSS-文件\" class=\"headerlink\" title=\"Prefetch CSS 文件\"></a>Prefetch CSS 文件</h4><p>既然 CSS 文件需要补齐,那么打开页面 5s 后自动 Prefetch 可以提升速度。</p><blockquote><p>5s 后再获取是为了防止阻塞页面。</p></blockquote><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">setTimeout</span>(<span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"comment\">// Prefetch CSS 文件</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"search_css\"</span>)) {</span><br><span class=\"line\"> fetch(<span class=\"string\">\"/css/search.css\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> ele = <span class=\"built_in\">document</span>.createElement(<span class=\"string\">\"style\"</span>)</span><br><span class=\"line\"> ele.innerHTML = res;</span><br><span class=\"line\"> ele.id = <span class=\"string\">\"search_css\"</span>;</span><br><span class=\"line\"> <span class=\"built_in\">document</span>.body.appendChild(ele);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> }</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (!<span class=\"built_in\">document</span>.getElementById(<span class=\"string\">\"page_css\"</span>)) {</span><br><span class=\"line\"> fetch(<span class=\"string\">\"/css/page.css\"</span>).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> res.text()).then(<span class=\"function\"><span class=\"params\">res</span> =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> ele = <span class=\"built_in\">document</span>.createElement(<span class=\"string\">\"style\"</span>)</span><br><span class=\"line\"> ele.innerHTML = res;</span><br><span class=\"line\"> ele.id = <span class=\"string\">\"page_css\"</span>;</span><br><span class=\"line\"> <span class=\"built_in\">document</span>.body.appendChild(ele);</span><br><span class=\"line\"> });</span><br><span class=\"line\"> } </span><br><span class=\"line\">}, <span class=\"number\">5000</span>);</span><br></pre></td></tr></tbody></table></figure><h4 id=\"关于-Robots\"><a href=\"#关于-Robots\" class=\"headerlink\" title=\"关于 Robots\"></a>关于 Robots</h4><p>当你运行 <code>$pjax_convertAllLinks();</code> 后,你肯定会发现所有的链接都变成了 <code>javascript:$pjax_jump('/xxx');</code>。这对机器人来说很不友好。</p><p>所以,我们需要排除这些机器人。</p><figure class=\"highlight js\"><table><tbody><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">var</span> runningOnBrowser = <span class=\"keyword\">typeof</span> <span class=\"built_in\">window</span> !== <span class=\"string\">\"undefined\"</span>;</span><br><span class=\"line\"><span class=\"keyword\">var</span> isBot = runningOnBrowser &amp;&amp; !(<span class=\"string\">\"onscroll\"</span> <span class=\"keyword\">in</span> <span class=\"built_in\">window</span>) || <span class=\"keyword\">typeof</span> navigator !== <span class=\"string\">\"undefined\"</span> &amp;&amp; <span class=\"regexp\">/(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i</span>.test(navigator.userAgent);</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> (runningOnBrowser &amp;&amp; !isBot) {</span><br><span class=\"line\"> <span class=\"built_in\">setTimeout</span>(<span class=\"function\">() =&gt;</span> {</span><br><span class=\"line\"> <span class=\"keyword\">try</span>{$pjax_prefetch();}<span class=\"keyword\">catch</span>(e){}</span><br><span class=\"line\"> $pjax_convertAllLinks();</span><br><span class=\"line\"> }, <span class=\"number\">100</span>);</span><br><span class=\"line\">}</span><br></pre></td></tr></tbody></table></figure><h2 id=\"最后\"><a href=\"#最后\" class=\"headerlink\" title=\"最后\"></a>最后</h2><p>在启用 Pjax 后,YFun's Blog 传输大小理论上最高缩小 3/4,性能速度均有提升。</p><p>如果你也在使用 Pjax,不妨试试看。</p><h2 id=\"还有一些错误\"><a href=\"#还有一些错误\" class=\"headerlink\" title=\"还有一些错误\"></a>还有一些错误</h2><p>如果你定义了 <code>onload</code> 等事件,页面没有刷新即代表没有变化,你需要在 <code>$pjax_jump()</code> 中简单清除一下这些信息。</p><h2 id=\"广告时间\"><a href=\"#广告时间\" class=\"headerlink\" title=\"广告时间\"></a>广告时间</h2><p>我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:<a target=\"_blank\" rel=\"noopener\" href=\"https://cloud.tencent.com/developer/support-plan?invite_code=16qkaef2qdvzm\">https://cloud.tencent.com/developer/support-plan?invite_code=16qkaef2qdvzm</a></p></div></div></div><div class=\"post-category\"><div id=\"p-meta-i\"><a class=\"hover-with-bg\" href=\"/categories/%E5%8D%9A%E5%AE%A2/\">博客</a> <a class=\"hover-with-bg\" href=\"/tags/%E5%8D%9A%E5%AE%A2/\"># 博客</a> <a class=\"hover-with-bg\" href=\"/tags/JavaScript/\"># JavaScript</a> <a class=\"hover-with-bg\" href=\"/tags/Pjax/\"># Pjax</a> <a class=\"hover-with-bg\" href=\"/tags/%E4%BC%98%E5%8C%96/\"># 优化</a></div></div><div class=\"post-footer\"><div class=\"donate text-center\"><span>喜欢这篇文章?为什么不考虑打赏一下作者呢?</span><div class=\"donate-way\"><a target=\"_blank\" rel=\"noopener\" href=\"https://afdian.net/@ocoke\" class=\"donate-btn button\">爱发电 (跨年立减优惠)</a></div></div><div class=\"post-copyright\"><p style=\"margin:5px 0\">文章作者:<a href=\"/\">YFun (@oCoke)</a></p><p style=\"margin:5px 0\">文章链接:<a href=\"https://blog.yfun.top/posts/2022/pjax/\">https://blog.yfun.top/posts/2022/pjax/</a></p><p style=\"margin:5px 0\">版权声明:本博客所有文章除特别声明外,均采用 <a target=\"_blank\" href=\"https://creativecommons.org/licenses/by-sa/4.0/deed.zh\" rel=\"nofollow noopener noopener\">CC BY-SA 4.0 协议</a> ,转载请注明出处,谢谢。</p></div></div><div class=\"comments\"><div id=\"miracle-comments\"><div id=\"tcomment\"><div style=\"margin:0 auto;width:100%;text-align:center\"><div class=\"donutSpinner\" style=\"margin:auto;width:25px;height:25px\"><div></div></div></div></div></div><script>function loadComment(){try{loadScriptFile({url:\"https://cdn1.tianli0.top/npm/twikoo@1.6.7/dist/twikoo.min.js\",loadType:\"async\",cb:()=>{twikoo.init({envId:\"https://twikoo.blog.yfun.top/\",el:\"#tcomment\",region:\"\",path:\"window.location.pathname\"})}})}catch(n){document.getElementById(\"loadCommentBtn\").innerText=\"无法加载 Twikoo 评论\",console.info(n)}}function loadTwikoo(){try{loadComment()}catch(n){console.log(n)}}var runningOnBrowser=\"undefined\"!=typeof window,isBot=runningOnBrowser&&!(\"onscroll\"in window)||\"undefined\"!=typeof navigator&&/(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent),supportsIntersectionObserver=runningOnBrowser&&\"IntersectionObserver\"in window;setTimeout(function(){var o;!isBot&&supportsIntersectionObserver?(o=new IntersectionObserver(function(n){n[0].isIntersecting&&(loadTwikoo(),o.disconnect())},{threshold:[0]})).observe(document.getElementById(\"miracle-comments\")):loadTwikoo()},1)</script></div></article>\n </div>\n <footer class=\"text-center\">\n <p style=\"display:flex;justify-content:center;align-items:center\">本网站由<a href=\"https://www.upyun.com/?utm_source=lianmeng&amp;utm_medium=referral\" target=\"_blank\" rel=\"nofollow noopener\">又拍云</a>提供 CDN 加速</p><script src=\"/sw-load.js\"></script><script src=\"/pjax.js\"></script><script>let script=document.createElement(\"script\");script.src=\"https://ca.yfun.top/link.js?cache=all\",script.onload=()=>{console.log(\"[INFO] CKY Analytics Enabled!\")},script.async=!0,script.defer=!0,script.setAttribute(\"data-website-id\",\"3e861252-ace3-4454-9a02-145c2123b0a4\"),document.body.appendChild(script)</script><p>© 2020 - 2022&nbsp;&nbsp;YFun's Blog</p><p>Powered by <a href=\"https://hexo.io\" target=\"_blank\">Hexo</a> | Theme by <a href=\"https://github.com/hifun-team/hexo-theme-miracle\" target=\"_blank\">Miracle</a></p>\n </footer>\n <div class=\"p-btn\">\n <a class=\"toc-btn\" id=\"toc-btn\"><i id=\"i-menu\"></i></a> <a class=\"toc-btn\" id=\"share-btn\"><i><svg t=\"1670124379155\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"2683\" width=\"25\" height=\"25\"><path d=\"M395.946667 234.666667v64H256v469.333333h512V522.666667h64V768a64 64 0 0 1-64 64H256a64 64 0 0 1-64-64V298.666667a64 64 0 0 1 64-64h139.946667z m335.850666-87.914667l150.848 150.826667-158.378666 158.4-45.269334-45.248L748.394667 341.333333H672c-121.685333 0-220.714667 97.024-223.914667 217.941334L448 565.333333v85.333334h-64v-85.333334C384 406.272 512.938667 277.333333 672 277.333333h99.861333l-85.312-85.333333 45.248-45.248z\" p-id=\"2684\" fill=\"var(--first-text-color)\"></path></svg> </i></a><a href=\"#top\" class=\"click-btn\"><i id=\"i-up\"></i></a>\n </div>\n ","path":"/posts/2022/pjax/index.html","extraJS":["function loadComment(){try{loadScriptFile({url:\"https://cdn1.tianli0.top/npm/twikoo@1.6.7/dist/twikoo.min.js\",loadType:\"async\",cb:()=>{twikoo.init({envId:\"https://twikoo.blog.yfun.top/\",el:\"#tcomment\",region:\"\",path:\"window.location.pathname\"})}})}catch(n){document.getElementById(\"loadCommentBtn\").innerText=\"无法加载 Twikoo 评论\",console.info(n)}}function loadTwikoo(){try{loadComment()}catch(n){console.log(n)}}var runningOnBrowser=\"undefined\"!=typeof window,isBot=runningOnBrowser&&!(\"onscroll\"in window)||\"undefined\"!=typeof navigator&&/(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent),supportsIntersectionObserver=runningOnBrowser&&\"IntersectionObserver\"in window;setTimeout(function(){var o;!isBot&&supportsIntersectionObserver?(o=new IntersectionObserver(function(n){n[0].isIntersecting&&(loadTwikoo(),o.disconnect())},{threshold:[0]})).observe(document.getElementById(\"miracle-comments\")):loadTwikoo()},1)","var postImg=document.querySelectorAll(\"article[class=page] img\");for(let t=0;t<postImg.length;t++)postImg[t].onclick=()=>{let i=document.createElement(\"div\");i.id=\"zoomImg\",i.innerHTML=`<div id=\"zoom-picture\"></div>\n <div class=\"poptrox-overlay\"\n style=\"position: fixed; left: 0px; top: 0px; z-index: 20000; width: 100%; height: 100%; text-align: center; cursor: zoom-out; opacity: 1;\">\n <div style=\"display:inline-block;height:100%;vertical-align:middle;\"></div>\n <div\n style=\"position:absolute;left:0;top:0;width:100%;height:100%;background:#000000;opacity:0;filter:alpha(opacity=0);\">\n </div>\n <div class=\"poptrox-popup\"\n style=\"display: inline-block; vertical-align: middle; position: relative; z-index: 1; cursor: zoom-out; min-width: 10px; min-height: 10px; width: auto; height: auto;\">\n <div class=\"loader\" style=\"display: none;\"></div>\n <div class=\"pic\" style=\"text-indent: 0px;\"><img\n src=\"${postImg[t].srcset||postImg[t].src}\" alt=\"Loading...\"\n style=\"vertical-align: bottom; max-width: 85vw; max-height: 85vh;\"></div>\n </div>\n </div>`,document.body.appendChild(i),document.querySelector(\"#zoomImg\").onclick=()=>{document.querySelector(\"#zoomImg\").remove()}}","try{var io=new IntersectionObserver(function(e){e.forEach(function(e){e.isIntersecting&&((e=e.target).srcset=e.getAttribute(\"data-srcset\"),e.className+=\" loaded\",io.unobserve(e))})})}catch(e){console.info(e)}query(\".lazyload-img\").forEach(function(e){try{io.observe(e)}catch(e){console.info(e)}})","query(\"#toc-btn\")[0].onclick=()=>{query(\".post-toc\")[0].innerHTML&&toggleClass(\".post-toc\",\"display-inline\")},query(\".post-toc\")[0].innerHTML||addClass(\"#toc-btn\",\"display-none\")","query(\"#share-btn\")[0].onclick=async()=>{var t=`${location.protocol}//${location.hostname}${location.port&&\":\"+location.port}${location.pathname}#read=${sessionStorage.getItem(location.pathname+\"_read_y\")||\"\"}`;try{await navigator.clipboard.writeText(t),prompt_core(\"分享链接已经复制至剪贴板\",4800,!0)}catch(o){prompt_core(\"分享链接复制失败,请手动复制<br/>\"+t,4800,!1)}}","const getScrollPosition=(e=window)=>({x:void 0!==e.pageXOffset?e.pageXOffset:e.scrollLeft,y:void 0!==e.pageYOffset?e.pageYOffset:e.scrollTop});var wx=document.getElementsByClassName(\"article-m\")[0].clientWidth,wy=document.getElementsByClassName(\"article-m\")[0].clientHeight;function windowScroll(){wx=document.getElementsByClassName(\"article-m\")[0].clientWidth,wy=document.getElementsByClassName(\"article-m\")[0].clientHeight;var e=`${Math.round(getScrollPosition().y)}:${wx}:${wy}`;sessionStorage.setItem(location.pathname+\"_read_y\",e)}setTimeout(()=>{if(1<location.hash.split(\"#read=\").length){prompt_core(\"已有阅读进度,正在跳转\",4800,!0);let e=location.hash.split(\"#read=\")[1];e=e.split(\":\"),window.scrollTo({top:Math.round(Number(e[0])*Number(e[1]*Number(e[2]/wx/wy))),behavior:\"smooth\"})}else{let e=sessionStorage.getItem(location.pathname+\"_read_y\")||\"0:0:0\";e=e.split(\":\"),\"0\"!=e[0]&&prompt_core(\"已有阅读进度,正在跳转\",4800,!0),window.scrollTo({top:Math.round(Number(e[0])*Number(e[1]*Number(e[2]/wx/wy))),behavior:\"smooth\"})}},500),window.onscroll=windowScroll"]}

@@ -1,1 +0,1 @@

[{"title":"什么年代还在用传统 Pjax? —— 自定义 Pjax 提升页面加载速度","date":"2022-12-15T04:30:00.000Z","url":"/posts/2022/pjax/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["JavaScript","/tags/JavaScript/"],["Pjax","/tags/Pjax/"],["优化","/tags/%E4%BC%98%E5%8C%96/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"前言Hexo 属于静态博客,很多同学给自己的博客加上 Pjax 是为了音乐播放器等功能不中断。 之前我也想过对博客和主题加入 Pjax 支持,但经过一番分析后觉得,这不仅引入了一个巨大的 jquery.pjax.js,反而优化效果不明显。 原理其实,Pjax 的原理并不复杂。或许说,README 一开始就告诉你了: 其中 ajax 用于页面的新内容,pushState 改变浏览器状态。 很简单吧。 事实上,pjax 并不应该应用于整个页面当中。而应该只是局部更改。 这样,Blog 当中的导航栏、样式文件等就不需要重复下载与预览。 分析以我使用 Miracle 为主题的博客为例,进入首页,按 F12 查看页面 Elements. 可以发现,页面主要更改的也就是 #page-main 部分,只需要实现动态刷新这部分的内容就可以了。 那怎么实现呢? 最小化的数据接口现在生成的页面当中,有 &lt;head&gt; 部分声明大量样式与元信息,&lt;body&gt; 之下重复的页脚、导航栏,还有每个页面下方都有的一些 &lt;script&gt;。 很明显,我们不需要这些。我们只要 #page-main 中的主要内容。 最重要的是,Hexo 是静态博客,这一点只能在生成文件时进行。 载入 HTML我是用 Cheerio 模块帮我完成这一工作。 定义一个 parse function,打开文件并解析相关信息,顺便把不是 HTML 的文件排除掉。 然后通过 Cheerio 解析 HTML: 然后获取页面的标题和 #page-main 下的 HTML. 页面中还有一些 script,比如阅读进度、懒加载等。所以需要一个 extraJS 放置额外的 Script. 最后,将 JSON 写入文件中。 文件递归我们还需要一个函数递归 public 目录下的所有文件,这个不用多说。 最后运行这个 Node.js 文件,就可以看到 public/ 目录下多出很多 ***.page.json 文件。 基本结构这些文件内容也很简单,基本如下: 前端 pjax.js新建一个 pjax.js。 替换链接我们需要先将页面当中所有本站链接转为 Pjax 的 Jump 函数。 判断条件是:有链接,不带 hash,且为本站链接 另外,要转化页面链接为全路径。 这里参考了下 ChenYFan 的 Service Worker 函数,需要根据实际情况做出调整。 跳转 如果使用 window.location.href 修改,那么页面就会刷新。为了实现无刷新跳转,必须要使用 pushState() 更改。 执行 JavaScript 方面使用 eval() 函数。 Prefetch &amp; Refetch此处借鉴乐特关于 Prefetch Page 的源码,当用户打开节流模式或为低速网络时就不要 Prefetch. Prefetch 可以提前缓存部分数据。 Refetch 用于刷新已有的缓存(虽然 SessionStorage 关闭页面就没了) 其原理也很简单,SessionStorage 中所有的 Pjax 缓存重新获取就完事了。 一些优化Prefetch CSS 文件既然 CSS 文件需要补齐,那么打开页面 5s 后自动 Prefetch 可以提升速度。 5s 后再获取是为了防止阻塞页面。 关于 Robots当你运行 $pjax_convertAllLinks(); 后,你肯定会发现所有的链接都变成了 javascript:$pjax_jump(&#39;/xxx&#39;);。这对机器人来说很不友好。 所以,我们需要排除这些机器人。 最后在启用 Pjax 后,YFun&#39;s Blog 传输大小理论上最高缩小 3/4,性能速度均有提升。 如果你也在使用 Pjax,不妨试试看。 还有一些错误如果你定义了 onload 等事件,页面没有刷新即代表没有变化,你需要在 $pjax_jump() 中简单清除一下这些信息。"},{"title":"抛弃静态博客的缺点,用 ESHexoN 在线编辑!","date":"2022-12-04T16:00:00.000Z","url":"/posts/2022/eshexon/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["ESHexoN","/tags/ESHexoN/"],["集成部署","/tags/%E9%9B%86%E6%88%90%E9%83%A8%E7%BD%B2/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"什么是 ESHexoN? ESHexoN 是一款简洁、强大的 Hexo 在线编辑器。 其灵感来自于 HexoPlusPlus/HexoPlusPlus (基于 Cloudflare Workers 的无服务器 Hexo 后端) 但是,遗憾的是 HPP 突然停止维护,只留下一个 Wexagonal。 However, 这更新速度也太慢了啊喂~ Qexo 也是一个很不错的项目,但是它基于 Python + MongoDB,不属于我擅长的技术栈,有点劝退。 于是,ESHexoN 诞生了。它致力于使用 JavaScript Edge Function 构建 Hexo「后端」,实现普通静态博客所不具备的功能,解决大部分静态博客的痛点。 如何使用 ESHexoN? ESHexoN 的使用说明可以参见文档 --&gt; 文档链接 ESHexoN 代码 &amp; 原理代码目录 dist 目录存放构建的文件,src 目录为源码,tools 是一些工具,index.js 则为入口文件。 后端的主要文件src/res.jsres.js 主要负责 HTTP 响应输出,统一输出格式。 故 ESHexoN API 主要返回格式为: [Source Code] src/check_token.jscheck_token.js 主要负责 API 请求的鉴权,所有的鉴权 API 都需要携带 token 发送。 生成代码: 故 ESHexoN Token 格式为: 过期时间为一个月。 src/env.jsESHexoN 除了依靠 yfun-lab/gh-worker-kv 存储主要数据库文件外,还使用环境变量存储基本的 Token、仓库等。 ESHexoN 自设计之初就预备支持 Cloudflare Workers, Deno 等平台。 事实上,这两个平台获取环境变量的方式根本不一样。为了在一个代码下同时支持两个平台,env.js 简单的写了个判断。 是不是目录?文件遍历GitHub API 会返回当前目录下的文件和文件夹,但有人的 source/_posts 目录下还有文件夹。根据观察发现,文件夹的 type 为 dir,而普通文件为 file,那么只需要一个简单的判断,就可以返回单个目录下所有文件。 写文件在写文件之前,我们需要先获取该文件的 sha 值。 这是因为 GitHub 规定修改文件必须在 body 中携带源文件 sha. 同时,文件还必须接受 Base64 编码。这个问题很好解决,ESHexoN 的仓库依赖中已经包含。当然,我更倾向于在前端编码文本,在后端直接 PUT。 那前端呢?以上是后端的主要代码。因为 ESHexoN 是前后端分离项目,并且开放跨域,所以我们构建了一个官方前端。只需要输入你的用户名、密码和后端地址就可以开始使用。 前端的代码也是开源的,基于 Vue + Vuetify 构建,在 GitHub 上查看它 一些小细节早在 HexoPlusPlus dev 阶段的时候,ChenYFan 就在群内讨论如何解决 Markdown 编辑器的问题。在当时,EditorMD 的兼容性很差。于是,HPP 上采取的解决方案是:textarea + marked.js。 然后,HPP 编辑器就是这样的: 这并不是一个很好的主意。 于是在 ESHexoN 上,我们为 Hexo 特性定制了一款简单的编辑器。 首先,它是由 修改而来的,在部分特性上与其类似。 但是受限于页面尺寸和小屏幕优化,ESHexoN 的编辑器在大屏上是这样显示的: 双栏布局,实时预览。 为了保证在小屏幕的体验,在小屏幕上将自动收起预览,改为全屏模式。 如果需要预览,可以点击「预览」按键。 同时 ESHexoN 的编辑器有自动保存功能,即使写到一半刷新页面或是浏览器崩溃也不用怕,页面内容已经存储到 localStorage 内了。(我也不知道会不会撑炸 一些基本的搜索功能都得以保留(与 HPP 是十分甚至九分的类似 不仅仅局限于文章,配置文件、代码都可以修改: 在未来目前 ESHexoN 还处于 Beta 阶段,部分功能还没有开发完成。 关于说说、评论等也都在等待开发(最近可能不会有) 如果在使用中出现了任何的问题,欢迎提 Issue 反馈。"},{"title":"让阅读无缝衔接 —— JS 获取用户阅读进度","date":"2022-11-11T16:40:00.000Z","url":"/posts/2022/js-get-users-reading-progress/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["JavaScript","/tags/JavaScript/"],["Miracle","/tags/Miracle/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"前言很多博客中最常见的问题就是:文章很长,但是读者很忙。下次阅读的时候,可能要花一些时间才能恢复到先前的阅读位置。 如果可以设备间,识别二维码或是一个链接就可以让阅读无缝衔接,直接跳转到相应位置,那么阅读体验就会变得更加优秀。 那么,开始吧! 实践要知道阅读位置,那么就要知道当前页面的坐标。 大部分情况下,我们只用关注纵坐标。横坐标大概率为 0。 我们还需要一个页面滚动的事件,用于记录当前坐标,并储存在临时存储中。 至于为什么是 sessionStorage 而不是 localStorage,则是因为 localStorage 除手动清除外,不会自动过期。 你可能发现了,此处的变量 p,并不仅仅是「页面纵坐标」,而是「页面高度」与「纵坐标」的组合字符串。 事实上,如果单纯是纵坐标判断位置,那么在不同高度,不同宽度的设备上,就会出现错位的情况。而同时记录三个信息,就可以还原真实坐标。 到现在,我们已经完成了 URL 的解析和基本生成。 那么,URL 即为: 最后搭配生成二维码等插件效果更佳。 Miracle 主题将在下个版本中更新该功能。 "},{"title":"NPM 自动更新版本号","date":"2022-08-04T05:00:00.000Z","url":"/posts/2022/auto-update-npm-version/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["NPM","/tags/NPM/"],["Node.js","/tags/Node-js/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"前言ChenYFan 大佬的文章:SpeedUp!使用黑科技为你的网站提速 有提到过将 Hexo 博客的全部静态文件上传至 NPM 达到加速效果。 但是 NPM 版本号不能重复,而且为了精准命中缓存和防止边缘 @latest 缓存过长,就必须指定版本号。 本人在 我的图床解决方案 一文中使用的方法是 npm version patch。 但这个方法也有一些弊端,比如 patch 只会更新 z 位数的版本号,而且并不会上传至仓库,很可能会出现版本号冲突导致上传失败。这就是为什么最后还需要重新推送 Git 提交。 试试看NPM 版本号遵循 semver 规范,格式为 major.minor.patch。 为了在获取最新版本号的时候不发生混乱,我选择先获取最新的版本号。 记得把 cky-blog-static 改成自己的项目名。 获取到最新版本号后,就可以对版本号进行分析。 以 1.4.7-b541af2ea284a39da0bbf63b88fdb65c 为例,先按 major.minor.patch 分离。当然,也需要考虑版本号后还有 build metadata 的情况。 为了版本号好看些,可以通过判断实现满十进一。 最后,重新拼接版本号。如果担心服务器缓存版本号导致冲突,还可以加上一些随机字符串。 将最新的版本号信息重新写入 package.json 文件: 代码直接 CV 代码! 最后如果是 Hexo 文件部署,就可以直接把文件直接丢在项目根目录,Action 集成部署加上: Blog 半年没更新,除除草 --(...."},{"title":"在 Windows 上快速安装 Hexo · 安装 Node.js","date":"2022-01-14T10:30:00.000Z","url":"/posts/hexo-blog/install-nodejs/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["Hexo","/tags/Hexo/"],["NPM","/tags/NPM/"],["Node.js","/tags/Node-js/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"前言本文未经允许,禁止转载! 视频教程正在制作,等待更新。 文章目录 安装 Git 安装 Node.js 更多文章等待更新... 下载前往 Node.js 官网 () 下载 Node.js 安装包(推荐下载 LTS 版本) 安装双击运行安装文件。 点击 Next。 勾选同意协议,点击 Next。 修改文件安装位置,点击 Next。(也可以不用改) 点击 Next。 点击 Install 开始安装。 等待安装完成。 点击 Finish 完成安装。 测试打开 Git Bash. 在文件夹处右击,点击 Git Bash Here 进入 Git Bash。或是在开始菜单中找到 Git Bash. 输入 node -v 和 npm -v 查看版本,正确输出版本信息即代表安装成功。 "},{"title":"2022: 新年快乐!","date":"2021-12-31T16:00:00.000Z","url":"/posts/2022/","tags":[["2022","/tags/2022/"],["新年","/tags/%E6%96%B0%E5%B9%B4/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"转眼间,2021 年已经过去。这一年非常忙碌,以至于博客一周年的纪念来不及写,许多文章计划停留在了 To Do 里。 博客由于 Cloudflare Web Analytics 是在 2021 年 4 月份启用的,再加上后来又将统计代码去除,所以关于访问量的信息不可用。 截止至 2021 年 12 月 31 日,博客共发布 15 篇文章,其中 2021 年发布 11 篇文章,原创文章 10 篇。 目前采用 LeanCloud + Waline 的评论方式,自 7 月份被刷评论后将评论权限改为登录(支持 GitHub 登录),还有部分评论在迁移中丢失,实在抱歉。 主题博客自 2021 年 1 月 13 日将主题改为 Miracle,这是一款简洁、轻量的单栏 Hexo 主题。 Miralce 目前的版本是 2.0.1,全新的版本去除 jQuery,进一步提升性能。 GitHub 仓库: 更多"},{"title":"在 Windows 上快速安装 Hexo · 安装 Git","date":"2021-11-12T13:30:00.000Z","url":"/posts/hexo-blog/install-git/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["Hexo","/tags/Hexo/"],["Git","/tags/Git/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"前言本文未经允许,禁止转载! 视频教程正在制作,等待更新。 文章目录 安装 Git 更多文章等待更新... 下载前往 Git 官网 (). 点击页面中的 Download For Windows 下载 Windows 版本。 安装双击运行安装文件。 点击 Next,同意协议。 修改程序安装位置,点击 Next(直接装在 C 盘也不是不行)。 组件选项,可选桌面快捷键、右键菜单、LFS 支持、自动检查更新等。 点击 Next 进入下一步。 开始菜单文件夹名,保持默认就可以,点击 Next。 默认编辑器,保持 Vim 就可以,点击 Next。 环境变量,选择第 2 个,点击 Next。 如果选择第 1 个,则只能在 Git Bash 中使用,可能会出现问题 剩下的都可以保持默认,一路 Next,然后点击 Install。 等待安装完毕。 测试在文件夹处右击,点击 Git Bash Here 进入 Git Bash。 (或是在开始菜单中找到 Git Bash) 输入 git --version 查看版本,正确输出版本信息即代表安装成功。 大功告成!"},{"title":"使用 Python 爬取故宫壁纸","date":"2021-07-17T05:55:00.000Z","url":"/posts/2021/dpm-wallpaper/","tags":[["壁纸","/tags/%E5%A3%81%E7%BA%B8/"],["Python","/tags/Python/"],["爬虫","/tags/%E7%88%AC%E8%99%AB/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"我非常喜欢故宫壁纸,但是一张一张下载的速度就太慢了。 于是,我就写了个简单的 Python 小程序自动爬取故宫壁纸。 代码首先,我们通过简单的查看,知道故宫壁纸的页面并没有使用 JavaScript 载入。 但是故宫壁纸很多,所以还需要分类。 故宫壁纸大多都是以分类开头,如 爱上紫禁城 紫藤, 清 虚谷紫藤金鱼图轴(局部) 等等,所以一般只需要做个简单的 startswith() 判断就 OK 了。 很多壁纸的标题都是一样的,所以还可以使用 random 库在文件名末尾追加一个随机数。 后我把一些我下载下来的壁纸放在了 这里,可以直接预览并下载。 OK,又水了一篇文("},{"title":"使用 Prettier 格式化代码","date":"2021-07-15T06:45:00.000Z","url":"/posts/2021/format-code-using-prettier/","tags":[["Prettier","/tags/Prettier/"],["代码","/tags/%E4%BB%A3%E7%A0%81/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"格式化代码可以提高代码的可读性,使代码更加美观。 Prettier 支持: JavaScript (including experimental features) JSX Angular Vue Flow TypeScript CSS, Less, and SCSS HTML JSON GraphQL Markdown, including GFM and MDX YAML 安装插件你可以很方便的在 VSCode 等 IDE 上安装插件,插件名为 Prettier. 以 VSCode 为例: 如果安装了其他格式化插件,则需要点击 [格式化文档的方式],选择 Prettier。 CLI 相关的文档可参见官网: 配置参见官方文档: Prettier 的配置文件有多种写法: 以使用 YAML 书写的 .prettierrc 为例: 效果格式化前: 格式化后: "},{"title":"树莓派超频","date":"2021-06-25T07:50:00.000Z","url":"/posts/1445549919/","tags":[["树莓派","/tags/%E6%A0%91%E8%8E%93%E6%B4%BE/"],["Ubuntu","/tags/Ubuntu/"]],"categories":[["折腾","/categories/%E6%8A%98%E8%85%BE/"]],"content":" 本文操作系统为 Ubuntu Server 20.04,其他操作系统可能有所不同。 超频是有风险的,温度也会更高,需要比较良好的散热。 开始登录树莓派,进入 /boot/firmware/ 目录: 网上很多教程让你修改 config.txt 文件,但注释里已经很明确的写了: 我们修改 usercfg.txt 文件: 在文件末尾追加配置信息,以下是我的,仅供参考: 我使用的是 Raspberry Pi 4 (4GB),并且有小风扇和散热片。请根据自己的需要和配置进行更改。 重启保存文件,使用命令重启: 后重启完毕后,再使用 lscpu 命令看看 CPU 状况。"},{"title":"加速 GitHub 下载","date":"2021-06-18T09:30:00.000Z","url":"/posts/2517388641/","tags":[["GitHub","/tags/GitHub/"],["折腾","/tags/%E6%8A%98%E8%85%BE/"],["Git","/tags/Git/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"GitHub 在国内的访问情况不太稳定,仓库克隆速度也非常慢。 ✈️不必多说。 例如 为代理地址,你可以使用下方的命令更改: 如果有一天不需要了,输入下面代码取消: FastGit使用 FastGit 提供的 GitHub 镜像。 镜像地址: 支持 HTTPS 克隆,并且提供 raw.githubusercontent.com 的镜像,地址为 CNPM使用 CNPM 提供的 GitHub 镜像。 镜像地址: 支持 HTTPS 克隆。 Hosts通过更改 Hosts 文件的方式解决,但大有可能失效: GitHub 上的 521xueweihan/GitHub520 项目提供了更多关于 GitHub Hosts 列表。 521xueweihan/GitHub520 #53 Gitee网上挺多教程,使用 Gitee 的「从 GitHub 导入」功能。 不过 Gitee 限制仓库 500MB。。 Coding进入 Coding 控制台,点击「创建项目」。 项目模版选择「代码托管」就好,仅做下载的话无需开启太多。 填写项目基本信息,如名称、标识等。 新建一个镜像仓库。 「Git 仓库 URL」填写 GitHub 公开仓库 HTTPS 克隆地址,如: 创建完成后请稍等,系统就会自动导入。 导入完成后,点击「克隆」就能找到克隆地址。 克隆速度挺快的: 此外,Coding 还支持自动同步,可以设置每天的同步时间。"},{"title":"GitHub Action 监测京东商品价格","date":"2021-05-18T10:32:00.000Z","url":"/posts/1238639652/","tags":[["GitHub","/tags/GitHub/"],["京东","/tags/%E4%BA%AC%E4%B8%9C/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"前言我会在京东上买些电子产品和文具,那么如何获得商品降价信息,使用更低的价格购买呢? 开始新建一个 GitHub 仓库,创建 main.py , notify.py 和 .github/workflows/auto.yml 三个文件,内容如下: main.py (点击展开) notify.py (点击展开) .github/workflows/auto.yml (点击展开) 注意计划任务语法有 5 个字段,中间用空格分隔,每个字段代表一个时间单位。 每个时间字段的含义: 符号 描述 举例 * 任意值 * * * * * 每天每小时每分钟 , 值分隔符 1,3,4,7 * * * * 每小时的 1 3 4 7 分钟 - 范围 1-6 * * * * 每小时的 1-6 分钟 / 每 */15 * * * * 每隔 15 分钟 注:由于 GitHub Actions 的限制,如果设置为 * * * * * 实际的执行频率为每 5 分执行一次。 后新建文件 并 配置 notify.py 后,GitHub Action 就会定时执行代码。如果有降价或涨价,就会按照 notify.py 的配置进行通知。 京东也有降价提醒的功能,但我实在是不想给 京东 开通知权限,经常推送商品广告。 本文部分内容参考:justjavac/auto-green#readme"},{"title":"我的图床解决方案","date":"2021-05-08T10:30:00.000Z","url":"/posts/2876015612/","tags":[["图床","/tags/%E5%9B%BE%E5%BA%8A/"],["折腾","/tags/%E6%8A%98%E8%85%BE/"],["NPM","/tags/NPM/"],["WebP","/tags/WebP/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"博客的文章经常需要插入图片,如果我将文档与图片放在一起,那么图片的加载速度将会很慢,于是我使用了图床。 图床的选择有很多,笔者之前写过一篇关于免费图床的汇总,本文就来介绍下我采用的方案:NPM。 本文假设您已有 NPM 账号、GitHub 账号。 准备工作新建仓库新建一个 GitHub 仓库,公开或是私人都可以,此处不再赘述。 在 Git 仓库中新建 rawimg/.gitkeep 与 webpimg/.gitkeep 两个文件。 获取令牌登录 ,点击右上角的头像,进入 Access Token。 点击页面中的 Generate New Token 获取新的令牌。 选择 Automation,点击 Generate Token 生成令牌。 将生成的令牌复制下来。 进入 GitHub 仓库,点击 Settings。 点击菜单栏中的 Secrets。 点击 New repository secret 新建一个 Secret。 Secret Name 为 NPM_TOKEN,Value 是你的 NPM 令牌,点击 Add secret 添加。 在仓库中新建 package.json 文件,参考如下: 图片转换与发布为了方便多端写作,我使用 GitHub 临时存储所需的图片,GitHub Action 发布 NPM 包。 WebP 可以大大缩减图片的尺寸,我们还可以借助 GitHub Action 在发布前自动转换。 GitHub Action 代码 (点击展开) 使用上传将图片文件上传至仓库的 rawimg/ 文件夹下即可。 当然也可以使用 PicGo / UPic / HexoPlusPlus 等工具上传。 发布在 GitHub 中新建 Release,将自动修改版本号并发布 NPM 包,无需手动修改 package.json。 访问推荐的镜像 镜像推荐选择访问速度快的,比较稳定的,拉取速度快的。 我选择的是 jsDelivr,国内外速度都很优秀。 链接以 jsDelivr 为例,原图链接为: WebP 图片链接为: [值] 说明 package-name NPM 包的名称 (package.json 文件中 name 的值) version 当前版本 (package.json 文件中 version 的值,通常需要在发布 Release 1 分钟后更新) filename 文件名 suffix 文件后缀名 "},{"title":"使用 JS 检测网址能否正常加载","date":"2021-04-09T12:42:00.000Z","url":"/posts/856484826/","tags":[["JavaScript","/tags/JavaScript/"],["Web","/tags/Web/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"有时我们需要判断用户能否加载某些网页或服务,如 Google、Disqus 等。 而我们可以用 JavaScript 来实现这样的检测功能。 方法一使用 JavaScript 创建一个图片,设置图片的链接为 检测网址 + favicon.ico。 onerror 事件触发不能加载的事件,onload 事件触发可以加载的事件,并使用 setTimeout 设置超时时间。 代码: 缺点:不是每一个网站都存在 /favicon.ico 文件,但如果加载特定文件的话挺好的。 方法二使用 JavaScript 创建一个 iframe 标签,设置标签的链接为 检测网址。 onerror 事件触发不能加载的事件,onload 事件触发可以加载的事件,并使用 setTimeout 设置超时时间。 代码: 缺点:页面上的其他外部链接也会被一并的加载过来,例如 JS 文件,CSS 文件,统计代码。 您也可以 点击此链接 前往 CodePen 体验。"},{"title":"树莓派 4 揽件日志","date":"2021-03-19T11:16:00.000Z","url":"/posts/229627020/","tags":[["树莓派","/tags/%E6%A0%91%E8%8E%93%E6%B4%BE/"],["揽件","/tags/%E6%8F%BD%E4%BB%B6/"],["折腾","/tags/%E6%8A%98%E8%85%BE/"]],"categories":[["折腾","/categories/%E6%8A%98%E8%85%BE/"]],"content":"前两天在淘宝上购买了一台 4GB 版本的 树莓派 4 。 在安装系统时我选择了 Ubuntu Server 系统,因为我并不打算使用图形化界面,而且 Raspbian 系统只有 32 位的 ╮(╯▽╰)╭。 而商家发的是中通快递,速度慢不说,外包装都给我压烂了。(ó﹏ò。) 商家随主板还发货了一个闪迪 32GB TF 卡(读写速度还可以),一个电源,一个外壳还有一个小风扇。 目前先折腾折腾,玩一玩,后面继续写文章。 目前没有把网站迁移上去的想法,因为这样的话网站会变得很不稳定。"},{"title":"为网站加入友好的深色模式支持","date":"2021-01-22T04:29:38.000Z","url":"/posts/175456095/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["JavaScript","/tags/JavaScript/"],["Web","/tags/Web/"],["CSS","/tags/CSS/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":" 转载文章原文标题:你好黑暗,我的老朋友 —— 为网站添加用户友好的深色模式支持原文链接:原文作者:Sukka 前几天为我的 Hexo 主题:Miracle 加入了深色模式,但我的技术还是太辣鸡,经常出现问题。 无意间看到 Sukka 大佬的文章:「你好黑暗,我的老朋友 —— 为网站添加用户友好的深色模式支持」,跟着文章重构了主题深色模式的代码,就转载过来方便学习。 什么是「深色模式」很多操作系统在日落后会自动切换到「深色模式」、并不意味着「深色模式」就是「夜间模式」。「夜间模式」用于夜晚的弱光环境,主要目的是保护眼睛、减少强光刺激、避免影响睡眠,不难理解为什么 macOS 的 Night Shift 会自动调节屏幕色温、Android(AOSP)到了夜间可以选择启用系统级「琥珀色」滤镜。 「深色模式」更像是一个主题,即使在白天也可以使用。不论是为了在 OLED 屏幕上省电、亦或是减少白光刺激护眼、亦或是暗色模式对色盲用户更加友好,总之 macOS 率先提出了系统级的「暗色模式」、并在 WebKit 中增加了对应的 Media Query,而后 Chromium、Firefox 先后跟进,如今兼容 prefers-color-scheme 的浏览器占有率已经高达 81.82%。 利用 Media Query 简单实现深色模式CSS 媒体查询 @media 是一个足够强大的特性,可以有条件地将样式应用于文档和各种上下文中。Media Queries Level 5 草案 中提出了深色模式的判断方式 prefers-color-scheme,包含 light、dark、no-preference 三种值。而不支持 Media Queries 5 的浏览器会直接无视 CSS 中的 prefers-color-scheme Media Query,无需额外的代码即可优雅降级。 还记得我刚刚说过「深色模式更像一个主题」么?为网站新增深色模式就如同换肤功能;搭配 prefers-color-scheme,编写深色模式的思路就如同编写响应式一般、无需赘述,结合几段 Code Snippet 一笔带过: CSS Variable 的方法实现深色模式 通过维护两套 CSS Variable,可以快速切换不同的配色方案。这种方法特点是所需代码较少,缺点是 CSS Variable 的兼容性较差,可能还需要引入额外的 Polyfill。 为深色模式单独编写样式 直接维护两套样式的方法清晰直观、任何网站都可以基于这种方法进行改造。但会造成冗余代码、较难实现统一的风格、后期不易维护。 条件性加载深色模式的 CSS 文件 利用 &lt;link&gt; 标签的 Media Query,甚至可以单独加载暗色模式的 CSS 文件。 需要注意 CSS 选择器的权重,因此作为可选的 dark.css 一定要放在 main.css 之后加载。 除了上述三种方式以外,使用 CSS filter 或 mix-blend-mode 还可以实现对网站整体色调的改变,可以确保配色风格的统一性。 「深色模式」的兼容性虽然有了优雅的 prefers-color-scheme 可以识别操作系统的显示模式,但是对于用户来说,仅依赖 Media Query 的「深色模式」并不能带来很好的体验。首先是浏览器兼容性。虽然支持该特性的浏览器的市场占有率非常喜人,但是从版本号上来看却并不乐观: 考虑到使用 Chormium 70 内核甚至 Tencent X5 内核的国产浏览器,大部分用户并没有机会体验到深色模式。除此以外,操作系统级别的「深色模式」实现也会受到 OEM 厂商的影响 —— 虽然 Android 10(AOSP)提供「深色模式」,但是一加的 OxygenOS 却将其深藏在系统主题设置里,没有自动切换、在 Quick Settings 里也没有快速的切换开关。 设计一个用户友好的「深色模式」受限于兼容性和复杂的操作系统,大部分网站依然在使用更传统的「开关」切换 —— 通过 toggle &lt;html&gt; 或&lt;body&gt; 的 class 属性实现在两套样式之间切换、并将开关的状态记忆在 localStorage 中的方法虽然有效,却是无奈之举,手动切换开关相比 prefers-color-scheme 也不够优雅。如果将「开关」和 prefers-color-scheme 结合起来,就可以带来更好的用户体验: 对于不兼容的浏览器或操作系统,访客依然可以通过开关手动切换显示模式 对于兼容的浏览器或操作系统,Media Query 能够实现在两种显示模式之间切换 在兼容的浏览器或操作系统上,用户还可以通过开关 override 当前的显示模式 在将两者组合在一起时,不能简单地用「开关」覆盖 prefers-color-scheme,否则用户触发开关、状态被永久记忆在 localStorage 之后,就变成了僵硬的手动模式。举个例子。访客可能在操作系统还没有自动切换到「深色模式」时通过网站上的开关切换显示模式,经过一个夜晚后到了次日白天、访客再度访问网站时,自然希望不需要再切换开关、网站就能以常规的浅色模式显示。因此设计思路是当 prefers-color-scheme 的值发生改变(从 与用户需要的显示模式不同 变成 相同)时清空 localStorage 中储存的开关状态,此时显示模式切换回基于 Media Query 的「自动」模式。 Talk is cheap, here goes the code. 首先是 CSS: 真是令人看的头大,让我们逐行来看都是些什么: 在 :root 下定义了一个 CSS Variable --color-mode: light 和在浅色模式下用到的 CSS Variable(比如使用深色 #333 作为主要字体颜色)。 使用 prefers-color-scheme 的 Media Query 定义深色模式下的 CSS Variable: --color-mode: light 。深色模式的样式(如浅色 #eff 作为主要字体颜色)要定义在 :not([data-user-color-scheme]) 伪类下以避免「开关」的行为覆盖浏览器的样式。 为 [data-user-color-scheme=&#39;dark&#39;] 再定义一遍深色模式下用到的样式。有了这段 CSS,不难理解深色模式何时会生效:当操作系统使用「深色模式」且 &lt;html&gt; 或 &lt;body&gt; 标签上没有 data-user-color-scheme 属性时、或者存在 data-user-color-scheme 属性且值为 dark 时。 然后是困难的部分了:编写 JavaScript 为「开关」添加行为。 先定义一些常量: 接下来,用 try &#123;&#125; catch (e) &#123;&#125; 封装一下 localStorage 的操作,以应对 HTML5 Storage 被禁用、localStorage 被写满、localStorage 实现不完整的情况: 我们还需要一个函数读取当前 prefers-color-scheme 的方法。由于已经在 CSS 中定义了 --color-mode,所以在 JS 中直接读取就好了: 还记得我们需要自动取消手动模式回到 prefers-color-scheme 么?意味着我们需要一个函数清掉 LS、删掉 &lt;html&gt; 存在的 data-user-color-scheme 属性: 接下来是起主要作用的函数了,负责为 &lt;html&gt; 标签修改 data-user-color-scheme 属性: 当然,「开关」还需要一个函数,这个函数负责获取相反的显示模式,同时还要将新的模式写入 localStorage 存储起来: 相关的函数都定义完了,是时候添加函数执行了: 我的博客也使用的这种实现,通过 Navbar 中的按钮体验一下吧!"},{"title":"博客最近的一些改变","date":"2021-01-13T10:15:49.000Z","url":"/posts/1987652759/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"主题经过近一个月的开发,Miracle 主题已经发布至 v1.2.0 版本,也正式可以开始使用。 主题仓库:hifun-team/hexo-theme-miracle 主题对输出的内容等进行优化,去除不必要的资源,速度非常之快。 PS: 主要原因是主题的首页没有图片,这主要是因为我不想找图,而且会拖慢页面速度,一举两等。[doge] 评论评论更换为 Waline,这样我就有了评论后台、回复通知和关键词过滤,真不错! 已更换至 Twikoo 部署部署换回了直接部署 ( hexo d ),因为主题在 Action 部署时一直无法解析 Tag 插件,而本地又可以... 而且实际上使用云部署的次数并不多,因为我每次写完文章以后都会自己看一遍,不太必要。 已重新切换回云部署,并且已经解决无法解析 Tag 插件 的问题。"},{"title":"使用 Cloudflare 加速你的网站","date":"2020-12-15T08:19:29.000Z","url":"/posts/995700211/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["CDN","/tags/CDN/"],["CloudFlare","/tags/CloudFlare/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":" 本文部分内容已过时。 前言CloudFlare 为我们提供了免费的 CDN 服务,并拥有全球各地的优秀节点,作为国外比较优秀的 CDN 服务商。 但是,来自国内的访问都会被 CloudFlare 绕到大洋彼岸的美国。再加上某神秘的阻碍,导致在国内的访问一直不是很好。Ping 出延迟基本大于 &gt;= 200ms 。 在一次偶然的机会中,发现: CloudFlare 拥有很多节点。 CloudFlare 的节点之间速度非常之快。 CloudFlare 的节点之间几乎没有延迟。 CloudFlare 所有节点均免费,基本可以随便接入。 实践既然发现了这个好办法,那么就开始实践吧! 首先,前往「CloudFlare Partner」的站点登录。 这里要注意!CloudFlare 官方已经停止 CNAME 接入,只能前往「CloudFlare Partner」接入。 以 萌精灵 CDN 为例,打开其官网: 并登录你的 CloudFlare 账号。 然后点击页面右上角的「添加域名」并加你的域名接入 CloudFlare 。 输入域名并点击「添加」即可添加成功。 接下来点击主页列表里的「管理 DNS」进入管理界面。 接下来点击「添加新记录」添加一个新的记录。 然后进入你域名的 DNS 管理系统,注意是解析域名的地方。 找到「CNAME 接入」处的对应 CNAME 地址,将其设为相应的解析地址。 加速访问虽然默认提供给我们的节点很慢,但我们可以从 CloudFlare 公开的节点中寻找访问速度快的节点。 我找到了几个国内速度不错的节点,可以参考参考: IP 地址 运营商 104.27.169.248 默认 104.19.19.119 移动 141.101.115.11 移动 104.16.245.1 联通 104.16.246.1 联通 104.20.157.19 电信 141.101.114.202 电信 然后再将原来的 CNAME 记录改为 A 记录到以上 IP,再配合智能运营商解析,达到提速。 如果你不想一个一个设置,可以直接将「默认」记录指向 cf.record.yfun.top ,节点基本都是上面的,但偶尔会改变。 可能会遇到的情况:无法正常签发 SSL 证书 常见问题无法正常签发 SSL 证书?CloudFlare 默认会提供免费 SSL 证书服务,但使用「加速节点」可能导致无法正确签发。 解决方法:使用智能 DNS,将「境外」的记录解析至控制面板提供给您的官方地址。 开启 SSL 后访问错误?试着为源站配置 SSL 证书,然后前往官方控制台()将域名的 SSL 设为「完全」。 注意:使用「完全」方式请务必确定源站已开启 SSL! 速度"},{"title":"利用 GitHub Action 自动部署 Hexo 博客","date":"2020-11-29T07:45:00.000Z","url":"/posts/2241387868/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["Hexo","/tags/Hexo/"],["GitHub","/tags/GitHub/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"前言使用 Hexo 写博客,每次写完都要生成静态文件,而且 Hexo 基于 Node.JS,不能再手机上更新。 所以每次提交过后发现自己码错字了,或是有同学来换友链,总是要麻烦的改文件,再上传。 那么,Hexo 如何进行在线更新呢? 如果使用服务器的话,就丧失了使用 Hexo 的最大的优点——节省开支,而且还不如用 Typecho/WordPress 呢。 后来突然想到 GitHub Action 可以实现在线打包等操作,就想试试在线生成 Hexo 博客。 行动获取 GitHub 令牌登录你的 GitHub 账号,点击右上角的头像,点击「Settings」进入设置。 点击菜单栏中的「Developer settings」进入开发者设置。 点击菜单栏中的「Personal access tokens」进入令牌设置。 点击「Generate new token」新建一个令牌。 勾选全部的权限,名称随意。 并点击「Generate token」完成生成。 记得保存好这个令牌,它不会再次出现。 新建仓库使用 GitHub 新建一个存放 Hexo 文件的仓库,要选私有仓库! 不要勾选任何的初始化仓库选项! 在 Hexo 根目录中删除 .git 文件夹(隐藏文件夹),删除主题目录下的 .git 文件夹。 然后在 Hexo 根目录下使用 cmd 或终端运行以下命令: 配置 GitHub Action进入仓库页面,点击「Action」,点击「 set up a workflow yourself 」。 在左侧代码编辑器中将下方提供的代码粘贴进入代码框。 请仔细阅读注释,修改最后几行的提交设置。 确认无误后点击右上角的「Start Commit」。 此处注意!「公开的仓库名」是生成后文件提交的公开仓库,供「GitHub Pages」「Vercel」等服务使用! 使用每次更改完文件过后,在 Hexo 根目录运行以下命令: 也可直接在仓库中 改文件/写博文 效果一样,GitHub 都会为你自动生成文件。"},{"title":"更好的保存你的图片 —— 免费的图床","date":"2020-11-27T08:45:00.000Z","url":"/posts/3867215122/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["图床","/tags/%E5%9B%BE%E5%BA%8A/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"前言本文将介绍一些免费的图床,可以供博客/日常使用。 每个图床都会给出测试链接供测试,无法访问或图片丢失则代表图床失效。 公共图床SM.MS 曾经图床的域名有备案并使用国内 CDN,但后来因为滥用被吊销。 现在使用的是 CloudFlare,国内访问效果不好甚至无法访问,不建议使用。 测试图片: SM.MS 测试图片 Imgur 国外一家图片托管服务商,你可以选择注册或不注册,同样的,拖拽上传,永久保留,其SLA有着相当高的保证。 然而很可惜的是,Imgur 并不能在国内正常访问。你也可以尝试使用 #代理 解决 测试图片: Imgur 测试图片 去不图床 由 杜老师 提供的个人公益图床,存储于阿里和腾讯的对象存储。 官方保证 SLA&gt;=99% ,目前来看是一个不错的选择。 当然,毕竟是个人维护的图床,能不能永久撑下去还是个问题。 如果您需要搭配 PicGo/uPic,请参考以下的配置: 测试图片:去不图床 去不图床所使用的 CDN 面向的是中国大陆。 路过图床 支持永久存储图片,全球CDN加速。官方宣称『图床从2011年至今都稳定运行』。 测试图片: 路过图床 Postimage Postimage 是一款来自国外的图床工具,支持按照一定大小缩放图片及设置图片自动删除。 在上传完成后,Postimage 会为你生成多种链接格式以满足不同需求。 Postimage 还会生成一个用于删除当前图片的链接,你只要访问那个链接即可将图片从服务器上删除。 测试图片: Postimage 2021-03-01 更新:国内已无法正常访问。 2021-05-10 更新:时好时坏。 折腾GitHub + jsDelivr网上有许多的教程,此处不再演示。 jsDelivr 非常稳定,国外使用 CloudFlare,国内有网宿节点,速度非常之快。 测试图片: jsDelivr NPM + 镜像我们可以使用 NPM 作为图床的储存。 再搭配上各种镜像调用,速度扛扛滴。 使用方法 您应该拥有基本的 npm 环境,如没有,请安装。安装教程可以参阅互联网,有很多可供参考。 首先,新建一个文件夹存放文件。 然后打开 cmd 或 终端,进入目录并输入以下命令 登录 NPM : 如果没有账号的,请前往 NPM 注册账号 紧接着输入以下命令: 请注意,如果你之前用过淘宝镜像,那么请先手动切回官方源: 每次更新完包内存放的图片后,你应该修改 package.json 文件内的版本号(向上增加),然后再次运行 npm publish 发布包。 推荐的镜像 测试图片 图片太多,这里使用超链接,点击就可以看到。 jsDelivr:jsDelivr+NPM 知乎:知乎+NPM 百度:百度+NPM 饿了么:饿了么+NPM LeanCloud / 注册一个 LeanCloud 账号,此处不再赘述。 创建一个 App,进入 存储 -&gt; 文件,点击「上传」并上传图片。 上传成功后会在文件列表中显示,在列表中我们可以看到 URL 地址。 测试链接: 笔者使用国际版,为避免拖慢网站加载速度,使用超链接,点击即可看到。 LeanCloud 测试链接 (国际版) UniCloud 注册一个 UniCloud 账号并登录,此处不再赘述。 创建一个服务空间,选择『阿里云』并起个名字。(选择阿里云不收费) 进入「云存储」,点击「上传文件」。 点击右侧的「详情」即可查看图片地址,预览等信息。 测试图片: UniCloud代理Imgur 在国内已经无法访问了,但是,我们可以利用服务器代理啊! 我们就以代理 Imgur 的图片为例,原链接: 测试图片: 图片太多,这里使用超链接,点击就可以看到。 此处的序号对应上方代理列表的序号 [1] [2] [3] [4]"},{"title":"使用 Vercel 部署你的静态网站","date":"2020-11-23T10:22:13.000Z","url":"/posts/2979788395/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"注册账号使用浏览器访问: 点击「Cotinue with GitHub」并使用 GitHub 账号注册。 无法注册账号? Vercel 不支持 163/QQ 等国内邮箱的注册,请使用 Outlook 邮箱 再次注册 GitHub 后使用新 GitHub 账号注册! 创建代码仓库新建 GitHub 代码仓库,并在代码仓库中新建 index.html 文件,这将作为首页。 在 index.html 文件中填写基本的代码内容,如: 使用 Vercel 部署点击控制台右上角的「Import Project」 点击「Import Git Repository」下方的「Continue」 输入 GitHub 仓库的地址并点击「Continue」 经过基本配置后,项目就会自动部署。 点击项目控制台的「View Domain」进入域名配置 默认会提供 *.vercel.app 免费域名,也可以免费添加自己的域名。 目前新项目已经更换为 *.vercel.app 域名,如仍需要 *.now.sh 域名,可以直接填写,实测可以使用。例如我需要使用 test-page-123.now.sh 域名,那么我只需要在自定义域名处填写并点击「Add」即可。2021-05-10 更新:已无法再添加 *.now.sh 域名,*.now.sh会自动跳转 *.vercel.app。 "}]
[{"title":"什么年代还在用传统 Pjax? —— 自定义 Pjax 提升页面加载速度","date":"2022-12-15T04:30:00.000Z","url":"/posts/2022/pjax/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["JavaScript","/tags/JavaScript/"],["Pjax","/tags/Pjax/"],["优化","/tags/%E4%BC%98%E5%8C%96/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"前言Hexo 属于静态博客,很多同学给自己的博客加上 Pjax 是为了音乐播放器等功能不中断。 之前我也想过对博客和主题加入 Pjax 支持,但经过一番分析后觉得,这不仅引入了一个巨大的 jquery.pjax.js,反而优化效果不明显。 原理其实,Pjax 的原理并不复杂。或许说,README 一开始就告诉你了: 其中 ajax 用于页面的新内容,pushState 改变浏览器状态。 很简单吧。 事实上,pjax 并不应该应用于整个页面当中。而应该只是局部更改。 这样,Blog 当中的导航栏、样式文件等就不需要重复下载与预览。 分析以我使用 Miracle 为主题的博客为例,进入首页,按 F12 查看页面 Elements. 可以发现,页面主要更改的也就是 #page-main 部分,只需要实现动态刷新这部分的内容就可以了。 那怎么实现呢? 最小化的数据接口现在生成的页面当中,有 &lt;head&gt; 部分声明大量样式与元信息,&lt;body&gt; 之下重复的页脚、导航栏,还有每个页面下方都有的一些 &lt;script&gt;。 很明显,我们不需要这些。我们只要 #page-main 中的主要内容。 最重要的是,Hexo 是静态博客,这一点只能在生成文件时进行。 载入 HTML我是用 Cheerio 模块帮我完成这一工作。 定义一个 parse function,打开文件并解析相关信息,顺便把不是 HTML 的文件排除掉。 然后通过 Cheerio 解析 HTML: 然后获取页面的标题和 #page-main 下的 HTML. 页面中还有一些 script,比如阅读进度、懒加载等。所以需要一个 extraJS 放置额外的 Script. 最后,将 JSON 写入文件中。 文件递归我们还需要一个函数递归 public 目录下的所有文件,这个不用多说。 最后运行这个 Node.js 文件,就可以看到 public/ 目录下多出很多 ***.page.json 文件。 基本结构这些文件内容也很简单,基本如下: 前端 pjax.js新建一个 pjax.js。 替换链接我们需要先将页面当中所有本站链接转为 Pjax 的 Jump 函数。 判断条件是:有链接,不带 hash,且为本站链接 另外,要转化页面链接为全路径。 这里参考了下 ChenYFan 的 Service Worker 函数,需要根据实际情况做出调整。 跳转 如果使用 window.location.href 修改,那么页面就会刷新。为了实现无刷新跳转,必须要使用 pushState() 更改。 执行 JavaScript 方面使用 eval() 函数。 Prefetch &amp; Refetch此处借鉴乐特关于 Prefetch Page 的源码,当用户打开节流模式或为低速网络时就不要 Prefetch. Prefetch 可以提前缓存部分数据。 Refetch 用于刷新已有的缓存(虽然 SessionStorage 关闭页面就没了) 其原理也很简单,SessionStorage 中所有的 Pjax 缓存重新获取就完事了。 一些优化Prefetch CSS 文件既然 CSS 文件需要补齐,那么打开页面 5s 后自动 Prefetch 可以提升速度。 5s 后再获取是为了防止阻塞页面。 关于 Robots当你运行 $pjax_convertAllLinks(); 后,你肯定会发现所有的链接都变成了 javascript:$pjax_jump(&#39;/xxx&#39;);。这对机器人来说很不友好。 所以,我们需要排除这些机器人。 最后在启用 Pjax 后,YFun&#39;s Blog 传输大小理论上最高缩小 3/4,性能速度均有提升。 如果你也在使用 Pjax,不妨试试看。 还有一些错误如果你定义了 onload 等事件,页面没有刷新即代表没有变化,你需要在 $pjax_jump() 中简单清除一下这些信息。 广告时间我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:"},{"title":"抛弃静态博客的缺点,用 ESHexoN 在线编辑!","date":"2022-12-04T16:00:00.000Z","url":"/posts/2022/eshexon/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["ESHexoN","/tags/ESHexoN/"],["集成部署","/tags/%E9%9B%86%E6%88%90%E9%83%A8%E7%BD%B2/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"什么是 ESHexoN? ESHexoN 是一款简洁、强大的 Hexo 在线编辑器。 其灵感来自于 HexoPlusPlus/HexoPlusPlus (基于 Cloudflare Workers 的无服务器 Hexo 后端) 但是,遗憾的是 HPP 突然停止维护,只留下一个 Wexagonal。 However, 这更新速度也太慢了啊喂~ Qexo 也是一个很不错的项目,但是它基于 Python + MongoDB,不属于我擅长的技术栈,有点劝退。 于是,ESHexoN 诞生了。它致力于使用 JavaScript Edge Function 构建 Hexo「后端」,实现普通静态博客所不具备的功能,解决大部分静态博客的痛点。 如何使用 ESHexoN? ESHexoN 的使用说明可以参见文档 --&gt; 文档链接 ESHexoN 代码 &amp; 原理代码目录 dist 目录存放构建的文件,src 目录为源码,tools 是一些工具,index.js 则为入口文件。 后端的主要文件src/res.jsres.js 主要负责 HTTP 响应输出,统一输出格式。 故 ESHexoN API 主要返回格式为: [Source Code] src/check_token.jscheck_token.js 主要负责 API 请求的鉴权,所有的鉴权 API 都需要携带 token 发送。 生成代码: 故 ESHexoN Token 格式为: 过期时间为一个月。 src/env.jsESHexoN 除了依靠 yfun-lab/gh-worker-kv 存储主要数据库文件外,还使用环境变量存储基本的 Token、仓库等。 ESHexoN 自设计之初就预备支持 Cloudflare Workers, Deno 等平台。 事实上,这两个平台获取环境变量的方式根本不一样。为了在一个代码下同时支持两个平台,env.js 简单的写了个判断。 是不是目录?文件遍历GitHub API 会返回当前目录下的文件和文件夹,但有人的 source/_posts 目录下还有文件夹。根据观察发现,文件夹的 type 为 dir,而普通文件为 file,那么只需要一个简单的判断,就可以返回单个目录下所有文件。 写文件在写文件之前,我们需要先获取该文件的 sha 值。 这是因为 GitHub 规定修改文件必须在 body 中携带源文件 sha. 同时,文件还必须接受 Base64 编码。这个问题很好解决,ESHexoN 的仓库依赖中已经包含。当然,我更倾向于在前端编码文本,在后端直接 PUT。 那前端呢?以上是后端的主要代码。因为 ESHexoN 是前后端分离项目,并且开放跨域,所以我们构建了一个官方前端。只需要输入你的用户名、密码和后端地址就可以开始使用。 前端的代码也是开源的,基于 Vue + Vuetify 构建,在 GitHub 上查看它 一些小细节早在 HexoPlusPlus dev 阶段的时候,ChenYFan 就在群内讨论如何解决 Markdown 编辑器的问题。在当时,EditorMD 的兼容性很差。于是,HPP 上采取的解决方案是:textarea + marked.js。 然后,HPP 编辑器就是这样的: 这并不是一个很好的主意。 于是在 ESHexoN 上,我们为 Hexo 特性定制了一款简单的编辑器。 首先,它是由 修改而来的,在部分特性上与其类似。 但是受限于页面尺寸和小屏幕优化,ESHexoN 的编辑器在大屏上是这样显示的: 双栏布局,实时预览。 为了保证在小屏幕的体验,在小屏幕上将自动收起预览,改为全屏模式。 如果需要预览,可以点击「预览」按键。 同时 ESHexoN 的编辑器有自动保存功能,即使写到一半刷新页面或是浏览器崩溃也不用怕,页面内容已经存储到 localStorage 内了。(我也不知道会不会撑炸 一些基本的搜索功能都得以保留(与 HPP 是十分甚至九分的类似 不仅仅局限于文章,配置文件、代码都可以修改: 在未来目前 ESHexoN 还处于 Beta 阶段,部分功能还没有开发完成。 关于说说、评论等也都在等待开发(最近可能不会有) 如果在使用中出现了任何的问题,欢迎提 Issue 反馈。"},{"title":"让阅读无缝衔接 —— JS 获取用户阅读进度","date":"2022-11-11T16:40:00.000Z","url":"/posts/2022/js-get-users-reading-progress/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["JavaScript","/tags/JavaScript/"],["Miracle","/tags/Miracle/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"前言很多博客中最常见的问题就是:文章很长,但是读者很忙。下次阅读的时候,可能要花一些时间才能恢复到先前的阅读位置。 如果可以设备间,识别二维码或是一个链接就可以让阅读无缝衔接,直接跳转到相应位置,那么阅读体验就会变得更加优秀。 那么,开始吧! 实践要知道阅读位置,那么就要知道当前页面的坐标。 大部分情况下,我们只用关注纵坐标。横坐标大概率为 0。 我们还需要一个页面滚动的事件,用于记录当前坐标,并储存在临时存储中。 至于为什么是 sessionStorage 而不是 localStorage,则是因为 localStorage 除手动清除外,不会自动过期。 你可能发现了,此处的变量 p,并不仅仅是「页面纵坐标」,而是「页面高度」与「纵坐标」的组合字符串。 事实上,如果单纯是纵坐标判断位置,那么在不同高度,不同宽度的设备上,就会出现错位的情况。而同时记录三个信息,就可以还原真实坐标。 到现在,我们已经完成了 URL 的解析和基本生成。 那么,URL 即为: 最后搭配生成二维码等插件效果更佳。 Miracle 主题将在下个版本中更新该功能。 "},{"title":"NPM 自动更新版本号","date":"2022-08-04T05:00:00.000Z","url":"/posts/2022/auto-update-npm-version/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["NPM","/tags/NPM/"],["Node.js","/tags/Node-js/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"前言ChenYFan 大佬的文章:SpeedUp!使用黑科技为你的网站提速 有提到过将 Hexo 博客的全部静态文件上传至 NPM 达到加速效果。 但是 NPM 版本号不能重复,而且为了精准命中缓存和防止边缘 @latest 缓存过长,就必须指定版本号。 本人在 我的图床解决方案 一文中使用的方法是 npm version patch。 但这个方法也有一些弊端,比如 patch 只会更新 z 位数的版本号,而且并不会上传至仓库,很可能会出现版本号冲突导致上传失败。这就是为什么最后还需要重新推送 Git 提交。 试试看NPM 版本号遵循 semver 规范,格式为 major.minor.patch。 为了在获取最新版本号的时候不发生混乱,我选择先获取最新的版本号。 记得把 cky-blog-static 改成自己的项目名。 获取到最新版本号后,就可以对版本号进行分析。 以 1.4.7-b541af2ea284a39da0bbf63b88fdb65c 为例,先按 major.minor.patch 分离。当然,也需要考虑版本号后还有 build metadata 的情况。 为了版本号好看些,可以通过判断实现满十进一。 最后,重新拼接版本号。如果担心服务器缓存版本号导致冲突,还可以加上一些随机字符串。 将最新的版本号信息重新写入 package.json 文件: 代码直接 CV 代码! 最后如果是 Hexo 文件部署,就可以直接把文件直接丢在项目根目录,Action 集成部署加上: Blog 半年没更新,除除草 --(...."},{"title":"在 Windows 上快速安装 Hexo · 安装 Node.js","date":"2022-01-14T10:30:00.000Z","url":"/posts/hexo-blog/install-nodejs/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["Hexo","/tags/Hexo/"],["NPM","/tags/NPM/"],["Node.js","/tags/Node-js/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"前言本文未经允许,禁止转载! 视频教程正在制作,等待更新。 文章目录 安装 Git 安装 Node.js 更多文章等待更新... 下载前往 Node.js 官网 () 下载 Node.js 安装包(推荐下载 LTS 版本) 安装双击运行安装文件。 点击 Next。 勾选同意协议,点击 Next。 修改文件安装位置,点击 Next。(也可以不用改) 点击 Next。 点击 Install 开始安装。 等待安装完成。 点击 Finish 完成安装。 测试打开 Git Bash. 在文件夹处右击,点击 Git Bash Here 进入 Git Bash。或是在开始菜单中找到 Git Bash. 输入 node -v 和 npm -v 查看版本,正确输出版本信息即代表安装成功。 "},{"title":"2022: 新年快乐!","date":"2021-12-31T16:00:00.000Z","url":"/posts/2022/","tags":[["2022","/tags/2022/"],["新年","/tags/%E6%96%B0%E5%B9%B4/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"转眼间,2021 年已经过去。这一年非常忙碌,以至于博客一周年的纪念来不及写,许多文章计划停留在了 To Do 里。 博客由于 Cloudflare Web Analytics 是在 2021 年 4 月份启用的,再加上后来又将统计代码去除,所以关于访问量的信息不可用。 截止至 2021 年 12 月 31 日,博客共发布 15 篇文章,其中 2021 年发布 11 篇文章,原创文章 10 篇。 目前采用 LeanCloud + Waline 的评论方式,自 7 月份被刷评论后将评论权限改为登录(支持 GitHub 登录),还有部分评论在迁移中丢失,实在抱歉。 主题博客自 2021 年 1 月 13 日将主题改为 Miracle,这是一款简洁、轻量的单栏 Hexo 主题。 Miralce 目前的版本是 2.0.1,全新的版本去除 jQuery,进一步提升性能。 GitHub 仓库: 更多"},{"title":"在 Windows 上快速安装 Hexo · 安装 Git","date":"2021-11-12T13:30:00.000Z","url":"/posts/hexo-blog/install-git/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["Hexo","/tags/Hexo/"],["Git","/tags/Git/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"前言本文未经允许,禁止转载! 视频教程正在制作,等待更新。 文章目录 安装 Git 更多文章等待更新... 下载前往 Git 官网 (). 点击页面中的 Download For Windows 下载 Windows 版本。 安装双击运行安装文件。 点击 Next,同意协议。 修改程序安装位置,点击 Next(直接装在 C 盘也不是不行)。 组件选项,可选桌面快捷键、右键菜单、LFS 支持、自动检查更新等。 点击 Next 进入下一步。 开始菜单文件夹名,保持默认就可以,点击 Next。 默认编辑器,保持 Vim 就可以,点击 Next。 环境变量,选择第 2 个,点击 Next。 如果选择第 1 个,则只能在 Git Bash 中使用,可能会出现问题 剩下的都可以保持默认,一路 Next,然后点击 Install。 等待安装完毕。 测试在文件夹处右击,点击 Git Bash Here 进入 Git Bash。 (或是在开始菜单中找到 Git Bash) 输入 git --version 查看版本,正确输出版本信息即代表安装成功。 大功告成!"},{"title":"使用 Python 爬取故宫壁纸","date":"2021-07-17T05:55:00.000Z","url":"/posts/2021/dpm-wallpaper/","tags":[["壁纸","/tags/%E5%A3%81%E7%BA%B8/"],["Python","/tags/Python/"],["爬虫","/tags/%E7%88%AC%E8%99%AB/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"我非常喜欢故宫壁纸,但是一张一张下载的速度就太慢了。 于是,我就写了个简单的 Python 小程序自动爬取故宫壁纸。 代码首先,我们通过简单的查看,知道故宫壁纸的页面并没有使用 JavaScript 载入。 但是故宫壁纸很多,所以还需要分类。 故宫壁纸大多都是以分类开头,如 爱上紫禁城 紫藤, 清 虚谷紫藤金鱼图轴(局部) 等等,所以一般只需要做个简单的 startswith() 判断就 OK 了。 很多壁纸的标题都是一样的,所以还可以使用 random 库在文件名末尾追加一个随机数。 后我把一些我下载下来的壁纸放在了 这里,可以直接预览并下载。 OK,又水了一篇文("},{"title":"使用 Prettier 格式化代码","date":"2021-07-15T06:45:00.000Z","url":"/posts/2021/format-code-using-prettier/","tags":[["Prettier","/tags/Prettier/"],["代码","/tags/%E4%BB%A3%E7%A0%81/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"格式化代码可以提高代码的可读性,使代码更加美观。 Prettier 支持: JavaScript (including experimental features) JSX Angular Vue Flow TypeScript CSS, Less, and SCSS HTML JSON GraphQL Markdown, including GFM and MDX YAML 安装插件你可以很方便的在 VSCode 等 IDE 上安装插件,插件名为 Prettier. 以 VSCode 为例: 如果安装了其他格式化插件,则需要点击 [格式化文档的方式],选择 Prettier。 CLI 相关的文档可参见官网: 配置参见官方文档: Prettier 的配置文件有多种写法: 以使用 YAML 书写的 .prettierrc 为例: 效果格式化前: 格式化后: "},{"title":"树莓派超频","date":"2021-06-25T07:50:00.000Z","url":"/posts/1445549919/","tags":[["树莓派","/tags/%E6%A0%91%E8%8E%93%E6%B4%BE/"],["Ubuntu","/tags/Ubuntu/"]],"categories":[["折腾","/categories/%E6%8A%98%E8%85%BE/"]],"content":" 本文操作系统为 Ubuntu Server 20.04,其他操作系统可能有所不同。 超频是有风险的,温度也会更高,需要比较良好的散热。 开始登录树莓派,进入 /boot/firmware/ 目录: 网上很多教程让你修改 config.txt 文件,但注释里已经很明确的写了: 我们修改 usercfg.txt 文件: 在文件末尾追加配置信息,以下是我的,仅供参考: 我使用的是 Raspberry Pi 4 (4GB),并且有小风扇和散热片。请根据自己的需要和配置进行更改。 重启保存文件,使用命令重启: 后重启完毕后,再使用 lscpu 命令看看 CPU 状况。"},{"title":"加速 GitHub 下载","date":"2021-06-18T09:30:00.000Z","url":"/posts/2517388641/","tags":[["GitHub","/tags/GitHub/"],["折腾","/tags/%E6%8A%98%E8%85%BE/"],["Git","/tags/Git/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"GitHub 在国内的访问情况不太稳定,仓库克隆速度也非常慢。 ✈️不必多说。 例如 为代理地址,你可以使用下方的命令更改: 如果有一天不需要了,输入下面代码取消: FastGit使用 FastGit 提供的 GitHub 镜像。 镜像地址: 支持 HTTPS 克隆,并且提供 raw.githubusercontent.com 的镜像,地址为 CNPM使用 CNPM 提供的 GitHub 镜像。 镜像地址: 支持 HTTPS 克隆。 Hosts通过更改 Hosts 文件的方式解决,但大有可能失效: GitHub 上的 521xueweihan/GitHub520 项目提供了更多关于 GitHub Hosts 列表。 521xueweihan/GitHub520 #53 Gitee网上挺多教程,使用 Gitee 的「从 GitHub 导入」功能。 不过 Gitee 限制仓库 500MB。。 Coding进入 Coding 控制台,点击「创建项目」。 项目模版选择「代码托管」就好,仅做下载的话无需开启太多。 填写项目基本信息,如名称、标识等。 新建一个镜像仓库。 「Git 仓库 URL」填写 GitHub 公开仓库 HTTPS 克隆地址,如: 创建完成后请稍等,系统就会自动导入。 导入完成后,点击「克隆」就能找到克隆地址。 克隆速度挺快的: 此外,Coding 还支持自动同步,可以设置每天的同步时间。"},{"title":"GitHub Action 监测京东商品价格","date":"2021-05-18T10:32:00.000Z","url":"/posts/1238639652/","tags":[["GitHub","/tags/GitHub/"],["京东","/tags/%E4%BA%AC%E4%B8%9C/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"前言我会在京东上买些电子产品和文具,那么如何获得商品降价信息,使用更低的价格购买呢? 开始新建一个 GitHub 仓库,创建 main.py , notify.py 和 .github/workflows/auto.yml 三个文件,内容如下: main.py (点击展开) notify.py (点击展开) .github/workflows/auto.yml (点击展开) 注意计划任务语法有 5 个字段,中间用空格分隔,每个字段代表一个时间单位。 每个时间字段的含义: 符号 描述 举例 * 任意值 * * * * * 每天每小时每分钟 , 值分隔符 1,3,4,7 * * * * 每小时的 1 3 4 7 分钟 - 范围 1-6 * * * * 每小时的 1-6 分钟 / 每 */15 * * * * 每隔 15 分钟 注:由于 GitHub Actions 的限制,如果设置为 * * * * * 实际的执行频率为每 5 分执行一次。 后新建文件 并 配置 notify.py 后,GitHub Action 就会定时执行代码。如果有降价或涨价,就会按照 notify.py 的配置进行通知。 京东也有降价提醒的功能,但我实在是不想给 京东 开通知权限,经常推送商品广告。 本文部分内容参考:justjavac/auto-green#readme"},{"title":"我的图床解决方案","date":"2021-05-08T10:30:00.000Z","url":"/posts/2876015612/","tags":[["图床","/tags/%E5%9B%BE%E5%BA%8A/"],["折腾","/tags/%E6%8A%98%E8%85%BE/"],["NPM","/tags/NPM/"],["WebP","/tags/WebP/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"博客的文章经常需要插入图片,如果我将文档与图片放在一起,那么图片的加载速度将会很慢,于是我使用了图床。 图床的选择有很多,笔者之前写过一篇关于免费图床的汇总,本文就来介绍下我采用的方案:NPM。 本文假设您已有 NPM 账号、GitHub 账号。 准备工作新建仓库新建一个 GitHub 仓库,公开或是私人都可以,此处不再赘述。 在 Git 仓库中新建 rawimg/.gitkeep 与 webpimg/.gitkeep 两个文件。 获取令牌登录 ,点击右上角的头像,进入 Access Token。 点击页面中的 Generate New Token 获取新的令牌。 选择 Automation,点击 Generate Token 生成令牌。 将生成的令牌复制下来。 进入 GitHub 仓库,点击 Settings。 点击菜单栏中的 Secrets。 点击 New repository secret 新建一个 Secret。 Secret Name 为 NPM_TOKEN,Value 是你的 NPM 令牌,点击 Add secret 添加。 在仓库中新建 package.json 文件,参考如下: 图片转换与发布为了方便多端写作,我使用 GitHub 临时存储所需的图片,GitHub Action 发布 NPM 包。 WebP 可以大大缩减图片的尺寸,我们还可以借助 GitHub Action 在发布前自动转换。 GitHub Action 代码 (点击展开) 使用上传将图片文件上传至仓库的 rawimg/ 文件夹下即可。 当然也可以使用 PicGo / UPic / HexoPlusPlus 等工具上传。 发布在 GitHub 中新建 Release,将自动修改版本号并发布 NPM 包,无需手动修改 package.json。 访问推荐的镜像 镜像推荐选择访问速度快的,比较稳定的,拉取速度快的。 我选择的是 jsDelivr,国内外速度都很优秀。 链接以 jsDelivr 为例,原图链接为: WebP 图片链接为: [值] 说明 package-name NPM 包的名称 (package.json 文件中 name 的值) version 当前版本 (package.json 文件中 version 的值,通常需要在发布 Release 1 分钟后更新) filename 文件名 suffix 文件后缀名 "},{"title":"使用 JS 检测网址能否正常加载","date":"2021-04-09T12:42:00.000Z","url":"/posts/856484826/","tags":[["JavaScript","/tags/JavaScript/"],["Web","/tags/Web/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"有时我们需要判断用户能否加载某些网页或服务,如 Google、Disqus 等。 而我们可以用 JavaScript 来实现这样的检测功能。 方法一使用 JavaScript 创建一个图片,设置图片的链接为 检测网址 + favicon.ico。 onerror 事件触发不能加载的事件,onload 事件触发可以加载的事件,并使用 setTimeout 设置超时时间。 代码: 缺点:不是每一个网站都存在 /favicon.ico 文件,但如果加载特定文件的话挺好的。 方法二使用 JavaScript 创建一个 iframe 标签,设置标签的链接为 检测网址。 onerror 事件触发不能加载的事件,onload 事件触发可以加载的事件,并使用 setTimeout 设置超时时间。 代码: 缺点:页面上的其他外部链接也会被一并的加载过来,例如 JS 文件,CSS 文件,统计代码。 您也可以 点击此链接 前往 CodePen 体验。"},{"title":"树莓派 4 揽件日志","date":"2021-03-19T11:16:00.000Z","url":"/posts/229627020/","tags":[["树莓派","/tags/%E6%A0%91%E8%8E%93%E6%B4%BE/"],["揽件","/tags/%E6%8F%BD%E4%BB%B6/"],["折腾","/tags/%E6%8A%98%E8%85%BE/"]],"categories":[["折腾","/categories/%E6%8A%98%E8%85%BE/"]],"content":"前两天在淘宝上购买了一台 4GB 版本的 树莓派 4 。 在安装系统时我选择了 Ubuntu Server 系统,因为我并不打算使用图形化界面,而且 Raspbian 系统只有 32 位的 ╮(╯▽╰)╭。 而商家发的是中通快递,速度慢不说,外包装都给我压烂了。(ó﹏ò。) 商家随主板还发货了一个闪迪 32GB TF 卡(读写速度还可以),一个电源,一个外壳还有一个小风扇。 目前先折腾折腾,玩一玩,后面继续写文章。 目前没有把网站迁移上去的想法,因为这样的话网站会变得很不稳定。"},{"title":"为网站加入友好的深色模式支持","date":"2021-01-22T04:29:38.000Z","url":"/posts/175456095/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["JavaScript","/tags/JavaScript/"],["Web","/tags/Web/"],["CSS","/tags/CSS/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":" 转载文章原文标题:你好黑暗,我的老朋友 —— 为网站添加用户友好的深色模式支持原文链接:原文作者:Sukka 前几天为我的 Hexo 主题:Miracle 加入了深色模式,但我的技术还是太辣鸡,经常出现问题。 无意间看到 Sukka 大佬的文章:「你好黑暗,我的老朋友 —— 为网站添加用户友好的深色模式支持」,跟着文章重构了主题深色模式的代码,就转载过来方便学习。 什么是「深色模式」很多操作系统在日落后会自动切换到「深色模式」、并不意味着「深色模式」就是「夜间模式」。「夜间模式」用于夜晚的弱光环境,主要目的是保护眼睛、减少强光刺激、避免影响睡眠,不难理解为什么 macOS 的 Night Shift 会自动调节屏幕色温、Android(AOSP)到了夜间可以选择启用系统级「琥珀色」滤镜。 「深色模式」更像是一个主题,即使在白天也可以使用。不论是为了在 OLED 屏幕上省电、亦或是减少白光刺激护眼、亦或是暗色模式对色盲用户更加友好,总之 macOS 率先提出了系统级的「暗色模式」、并在 WebKit 中增加了对应的 Media Query,而后 Chromium、Firefox 先后跟进,如今兼容 prefers-color-scheme 的浏览器占有率已经高达 81.82%。 利用 Media Query 简单实现深色模式CSS 媒体查询 @media 是一个足够强大的特性,可以有条件地将样式应用于文档和各种上下文中。Media Queries Level 5 草案 中提出了深色模式的判断方式 prefers-color-scheme,包含 light、dark、no-preference 三种值。而不支持 Media Queries 5 的浏览器会直接无视 CSS 中的 prefers-color-scheme Media Query,无需额外的代码即可优雅降级。 还记得我刚刚说过「深色模式更像一个主题」么?为网站新增深色模式就如同换肤功能;搭配 prefers-color-scheme,编写深色模式的思路就如同编写响应式一般、无需赘述,结合几段 Code Snippet 一笔带过: CSS Variable 的方法实现深色模式 通过维护两套 CSS Variable,可以快速切换不同的配色方案。这种方法特点是所需代码较少,缺点是 CSS Variable 的兼容性较差,可能还需要引入额外的 Polyfill。 为深色模式单独编写样式 直接维护两套样式的方法清晰直观、任何网站都可以基于这种方法进行改造。但会造成冗余代码、较难实现统一的风格、后期不易维护。 条件性加载深色模式的 CSS 文件 利用 &lt;link&gt; 标签的 Media Query,甚至可以单独加载暗色模式的 CSS 文件。 需要注意 CSS 选择器的权重,因此作为可选的 dark.css 一定要放在 main.css 之后加载。 除了上述三种方式以外,使用 CSS filter 或 mix-blend-mode 还可以实现对网站整体色调的改变,可以确保配色风格的统一性。 「深色模式」的兼容性虽然有了优雅的 prefers-color-scheme 可以识别操作系统的显示模式,但是对于用户来说,仅依赖 Media Query 的「深色模式」并不能带来很好的体验。首先是浏览器兼容性。虽然支持该特性的浏览器的市场占有率非常喜人,但是从版本号上来看却并不乐观: 考虑到使用 Chormium 70 内核甚至 Tencent X5 内核的国产浏览器,大部分用户并没有机会体验到深色模式。除此以外,操作系统级别的「深色模式」实现也会受到 OEM 厂商的影响 —— 虽然 Android 10(AOSP)提供「深色模式」,但是一加的 OxygenOS 却将其深藏在系统主题设置里,没有自动切换、在 Quick Settings 里也没有快速的切换开关。 设计一个用户友好的「深色模式」受限于兼容性和复杂的操作系统,大部分网站依然在使用更传统的「开关」切换 —— 通过 toggle &lt;html&gt; 或&lt;body&gt; 的 class 属性实现在两套样式之间切换、并将开关的状态记忆在 localStorage 中的方法虽然有效,却是无奈之举,手动切换开关相比 prefers-color-scheme 也不够优雅。如果将「开关」和 prefers-color-scheme 结合起来,就可以带来更好的用户体验: 对于不兼容的浏览器或操作系统,访客依然可以通过开关手动切换显示模式 对于兼容的浏览器或操作系统,Media Query 能够实现在两种显示模式之间切换 在兼容的浏览器或操作系统上,用户还可以通过开关 override 当前的显示模式 在将两者组合在一起时,不能简单地用「开关」覆盖 prefers-color-scheme,否则用户触发开关、状态被永久记忆在 localStorage 之后,就变成了僵硬的手动模式。举个例子。访客可能在操作系统还没有自动切换到「深色模式」时通过网站上的开关切换显示模式,经过一个夜晚后到了次日白天、访客再度访问网站时,自然希望不需要再切换开关、网站就能以常规的浅色模式显示。因此设计思路是当 prefers-color-scheme 的值发生改变(从 与用户需要的显示模式不同 变成 相同)时清空 localStorage 中储存的开关状态,此时显示模式切换回基于 Media Query 的「自动」模式。 Talk is cheap, here goes the code. 首先是 CSS: 真是令人看的头大,让我们逐行来看都是些什么: 在 :root 下定义了一个 CSS Variable --color-mode: light 和在浅色模式下用到的 CSS Variable(比如使用深色 #333 作为主要字体颜色)。 使用 prefers-color-scheme 的 Media Query 定义深色模式下的 CSS Variable: --color-mode: light 。深色模式的样式(如浅色 #eff 作为主要字体颜色)要定义在 :not([data-user-color-scheme]) 伪类下以避免「开关」的行为覆盖浏览器的样式。 为 [data-user-color-scheme=&#39;dark&#39;] 再定义一遍深色模式下用到的样式。有了这段 CSS,不难理解深色模式何时会生效:当操作系统使用「深色模式」且 &lt;html&gt; 或 &lt;body&gt; 标签上没有 data-user-color-scheme 属性时、或者存在 data-user-color-scheme 属性且值为 dark 时。 然后是困难的部分了:编写 JavaScript 为「开关」添加行为。 先定义一些常量: 接下来,用 try &#123;&#125; catch (e) &#123;&#125; 封装一下 localStorage 的操作,以应对 HTML5 Storage 被禁用、localStorage 被写满、localStorage 实现不完整的情况: 我们还需要一个函数读取当前 prefers-color-scheme 的方法。由于已经在 CSS 中定义了 --color-mode,所以在 JS 中直接读取就好了: 还记得我们需要自动取消手动模式回到 prefers-color-scheme 么?意味着我们需要一个函数清掉 LS、删掉 &lt;html&gt; 存在的 data-user-color-scheme 属性: 接下来是起主要作用的函数了,负责为 &lt;html&gt; 标签修改 data-user-color-scheme 属性: 当然,「开关」还需要一个函数,这个函数负责获取相反的显示模式,同时还要将新的模式写入 localStorage 存储起来: 相关的函数都定义完了,是时候添加函数执行了: 我的博客也使用的这种实现,通过 Navbar 中的按钮体验一下吧!"},{"title":"博客最近的一些改变","date":"2021-01-13T10:15:49.000Z","url":"/posts/1987652759/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"]],"categories":[["博客","/categories/%E5%8D%9A%E5%AE%A2/"]],"content":"主题经过近一个月的开发,Miracle 主题已经发布至 v1.2.0 版本,也正式可以开始使用。 主题仓库:hifun-team/hexo-theme-miracle 主题对输出的内容等进行优化,去除不必要的资源,速度非常之快。 PS: 主要原因是主题的首页没有图片,这主要是因为我不想找图,而且会拖慢页面速度,一举两等。[doge] 评论评论更换为 Waline,这样我就有了评论后台、回复通知和关键词过滤,真不错! 已更换至 Twikoo 部署部署换回了直接部署 ( hexo d ),因为主题在 Action 部署时一直无法解析 Tag 插件,而本地又可以... 而且实际上使用云部署的次数并不多,因为我每次写完文章以后都会自己看一遍,不太必要。 已重新切换回云部署,并且已经解决无法解析 Tag 插件 的问题。"},{"title":"使用 Cloudflare 加速你的网站","date":"2020-12-15T08:19:29.000Z","url":"/posts/995700211/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["CDN","/tags/CDN/"],["CloudFlare","/tags/CloudFlare/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":" 本文部分内容已过时。 前言CloudFlare 为我们提供了免费的 CDN 服务,并拥有全球各地的优秀节点,作为国外比较优秀的 CDN 服务商。 但是,来自国内的访问都会被 CloudFlare 绕到大洋彼岸的美国。再加上某神秘的阻碍,导致在国内的访问一直不是很好。Ping 出延迟基本大于 &gt;= 200ms 。 在一次偶然的机会中,发现: CloudFlare 拥有很多节点。 CloudFlare 的节点之间速度非常之快。 CloudFlare 的节点之间几乎没有延迟。 CloudFlare 所有节点均免费,基本可以随便接入。 实践既然发现了这个好办法,那么就开始实践吧! 首先,前往「CloudFlare Partner」的站点登录。 这里要注意!CloudFlare 官方已经停止 CNAME 接入,只能前往「CloudFlare Partner」接入。 以 萌精灵 CDN 为例,打开其官网: 并登录你的 CloudFlare 账号。 然后点击页面右上角的「添加域名」并加你的域名接入 CloudFlare 。 输入域名并点击「添加」即可添加成功。 接下来点击主页列表里的「管理 DNS」进入管理界面。 接下来点击「添加新记录」添加一个新的记录。 然后进入你域名的 DNS 管理系统,注意是解析域名的地方。 找到「CNAME 接入」处的对应 CNAME 地址,将其设为相应的解析地址。 加速访问虽然默认提供给我们的节点很慢,但我们可以从 CloudFlare 公开的节点中寻找访问速度快的节点。 我找到了几个国内速度不错的节点,可以参考参考: IP 地址 运营商 104.27.169.248 默认 104.19.19.119 移动 141.101.115.11 移动 104.16.245.1 联通 104.16.246.1 联通 104.20.157.19 电信 141.101.114.202 电信 然后再将原来的 CNAME 记录改为 A 记录到以上 IP,再配合智能运营商解析,达到提速。 如果你不想一个一个设置,可以直接将「默认」记录指向 cf.record.yfun.top ,节点基本都是上面的,但偶尔会改变。 可能会遇到的情况:无法正常签发 SSL 证书 常见问题无法正常签发 SSL 证书?CloudFlare 默认会提供免费 SSL 证书服务,但使用「加速节点」可能导致无法正确签发。 解决方法:使用智能 DNS,将「境外」的记录解析至控制面板提供给您的官方地址。 开启 SSL 后访问错误?试着为源站配置 SSL 证书,然后前往官方控制台()将域名的 SSL 设为「完全」。 注意:使用「完全」方式请务必确定源站已开启 SSL! 速度"},{"title":"利用 GitHub Action 自动部署 Hexo 博客","date":"2020-11-29T07:45:00.000Z","url":"/posts/2241387868/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["Hexo","/tags/Hexo/"],["GitHub","/tags/GitHub/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"前言使用 Hexo 写博客,每次写完都要生成静态文件,而且 Hexo 基于 Node.JS,不能再手机上更新。 所以每次提交过后发现自己码错字了,或是有同学来换友链,总是要麻烦的改文件,再上传。 那么,Hexo 如何进行在线更新呢? 如果使用服务器的话,就丧失了使用 Hexo 的最大的优点——节省开支,而且还不如用 Typecho/WordPress 呢。 后来突然想到 GitHub Action 可以实现在线打包等操作,就想试试在线生成 Hexo 博客。 行动获取 GitHub 令牌登录你的 GitHub 账号,点击右上角的头像,点击「Settings」进入设置。 点击菜单栏中的「Developer settings」进入开发者设置。 点击菜单栏中的「Personal access tokens」进入令牌设置。 点击「Generate new token」新建一个令牌。 勾选全部的权限,名称随意。 并点击「Generate token」完成生成。 记得保存好这个令牌,它不会再次出现。 新建仓库使用 GitHub 新建一个存放 Hexo 文件的仓库,要选私有仓库! 不要勾选任何的初始化仓库选项! 在 Hexo 根目录中删除 .git 文件夹(隐藏文件夹),删除主题目录下的 .git 文件夹。 然后在 Hexo 根目录下使用 cmd 或终端运行以下命令: 配置 GitHub Action进入仓库页面,点击「Action」,点击「 set up a workflow yourself 」。 在左侧代码编辑器中将下方提供的代码粘贴进入代码框。 请仔细阅读注释,修改最后几行的提交设置。 确认无误后点击右上角的「Start Commit」。 此处注意!「公开的仓库名」是生成后文件提交的公开仓库,供「GitHub Pages」「Vercel」等服务使用! 使用每次更改完文件过后,在 Hexo 根目录运行以下命令: 也可直接在仓库中 改文件/写博文 效果一样,GitHub 都会为你自动生成文件。"},{"title":"更好的保存你的图片 —— 免费的图床","date":"2020-11-27T08:45:00.000Z","url":"/posts/3867215122/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"],["图床","/tags/%E5%9B%BE%E5%BA%8A/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"前言本文将介绍一些免费的图床,可以供博客/日常使用。 每个图床都会给出测试链接供测试,无法访问或图片丢失则代表图床失效。 公共图床SM.MS 曾经图床的域名有备案并使用国内 CDN,但后来因为滥用被吊销。 现在使用的是 CloudFlare,国内访问效果不好甚至无法访问,不建议使用。 测试图片: SM.MS 测试图片 Imgur 国外一家图片托管服务商,你可以选择注册或不注册,同样的,拖拽上传,永久保留,其SLA有着相当高的保证。 然而很可惜的是,Imgur 并不能在国内正常访问。你也可以尝试使用 #代理 解决 测试图片: Imgur 测试图片 去不图床 由 杜老师 提供的个人公益图床,存储于阿里和腾讯的对象存储。 官方保证 SLA&gt;=99% ,目前来看是一个不错的选择。 当然,毕竟是个人维护的图床,能不能永久撑下去还是个问题。 如果您需要搭配 PicGo/uPic,请参考以下的配置: 测试图片:去不图床 去不图床所使用的 CDN 面向的是中国大陆。 路过图床 支持永久存储图片,全球CDN加速。官方宣称『图床从2011年至今都稳定运行』。 测试图片: 路过图床 Postimage Postimage 是一款来自国外的图床工具,支持按照一定大小缩放图片及设置图片自动删除。 在上传完成后,Postimage 会为你生成多种链接格式以满足不同需求。 Postimage 还会生成一个用于删除当前图片的链接,你只要访问那个链接即可将图片从服务器上删除。 测试图片: Postimage 2021-03-01 更新:国内已无法正常访问。 2021-05-10 更新:时好时坏。 折腾GitHub + jsDelivr网上有许多的教程,此处不再演示。 jsDelivr 非常稳定,国外使用 CloudFlare,国内有网宿节点,速度非常之快。 测试图片: jsDelivr NPM + 镜像我们可以使用 NPM 作为图床的储存。 再搭配上各种镜像调用,速度扛扛滴。 使用方法 您应该拥有基本的 npm 环境,如没有,请安装。安装教程可以参阅互联网,有很多可供参考。 首先,新建一个文件夹存放文件。 然后打开 cmd 或 终端,进入目录并输入以下命令 登录 NPM : 如果没有账号的,请前往 NPM 注册账号 紧接着输入以下命令: 请注意,如果你之前用过淘宝镜像,那么请先手动切回官方源: 每次更新完包内存放的图片后,你应该修改 package.json 文件内的版本号(向上增加),然后再次运行 npm publish 发布包。 推荐的镜像 测试图片 图片太多,这里使用超链接,点击就可以看到。 jsDelivr:jsDelivr+NPM 知乎:知乎+NPM 百度:百度+NPM 饿了么:饿了么+NPM LeanCloud / 注册一个 LeanCloud 账号,此处不再赘述。 创建一个 App,进入 存储 -&gt; 文件,点击「上传」并上传图片。 上传成功后会在文件列表中显示,在列表中我们可以看到 URL 地址。 测试链接: 笔者使用国际版,为避免拖慢网站加载速度,使用超链接,点击即可看到。 LeanCloud 测试链接 (国际版) UniCloud 注册一个 UniCloud 账号并登录,此处不再赘述。 创建一个服务空间,选择『阿里云』并起个名字。(选择阿里云不收费) 进入「云存储」,点击「上传文件」。 点击右侧的「详情」即可查看图片地址,预览等信息。 测试图片: UniCloud代理Imgur 在国内已经无法访问了,但是,我们可以利用服务器代理啊! 我们就以代理 Imgur 的图片为例,原链接: 测试图片: 图片太多,这里使用超链接,点击就可以看到。 此处的序号对应上方代理列表的序号 [1] [2] [3] [4]"},{"title":"使用 Vercel 部署你的静态网站","date":"2020-11-23T10:22:13.000Z","url":"/posts/2979788395/","tags":[["博客","/tags/%E5%8D%9A%E5%AE%A2/"]],"categories":[["极客","/categories/%E6%9E%81%E5%AE%A2/"]],"content":"注册账号使用浏览器访问: 点击「Cotinue with GitHub」并使用 GitHub 账号注册。 无法注册账号? Vercel 不支持 163/QQ 等国内邮箱的注册,请使用 Outlook 邮箱 再次注册 GitHub 后使用新 GitHub 账号注册! 创建代码仓库新建 GitHub 代码仓库,并在代码仓库中新建 index.html 文件,这将作为首页。 在 index.html 文件中填写基本的代码内容,如: 使用 Vercel 部署点击控制台右上角的「Import Project」 点击「Import Git Repository」下方的「Continue」 输入 GitHub 仓库的地址并点击「Continue」 经过基本配置后,项目就会自动部署。 点击项目控制台的「View Domain」进入域名配置 默认会提供 *.vercel.app 免费域名,也可以免费添加自己的域名。 目前新项目已经更换为 *.vercel.app 域名,如仍需要 *.now.sh 域名,可以直接填写,实测可以使用。例如我需要使用 test-page-123.now.sh 域名,那么我只需要在自定义域名处填写并点击「Add」即可。2021-05-10 更新:已无法再添加 *.now.sh 域名,*.now.sh会自动跳转 *.vercel.app。 "}]

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc