关于 李瑞豪

一名前端開發工程師,hugo-fixit 的創建者,經常在 菠菜眾長 1 和 FixIt2 上撰寫文章和文檔。

RSS 地址: https://lruihao.cn/index.xml

请复制 RSS 到你的阅读器,或快速订阅到 :

李瑞豪 RSS 预览

现代 CSS 解决方案:CSS 四舍五入数值单位

2024-07-29 20:32:39

<p>本文将介绍另外一个非常实用的 CSS 数学函数 - <code>round()</code> 及其实际应用场景。</p> <h2 id="何为-css-round-函数" class="heading-element"><span>1 何为 CSS round 函数</span> <a href="#%e4%bd%95%e4%b8%ba-css-round-%e5%87%bd%e6%95%b0" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><blockquote><p>这是一个比较新的 CSS 函数,在 <a href="https://drafts.csswg.org/css-values/#funcdef-round"target="_blank" rel="external nofollow noopener noreferrer">CSS Values and Units Module Level 4<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a> 规范中提出,自 2024 年 5 月起,此功能适用于最新设备和浏览器版本。此功能可能无法在较旧的设备或浏览器中使用。</p> </blockquote><p>CSS <code>round()</code> 函数<strong>根据选定的舍入策略返回舍入数</strong>。</p> <p>作者应使用自定义 CSS 属性(例如 <code>--my-property</code>)作为舍入值、间隔或两者兼而有之;如果这些函数具有已知值,使用 <code>round()</code> 函数显然不太必要。</p> <h3 id="语法规则" class="heading-element"><span>1.1 语法规则</span> <a href="#%e8%af%ad%e6%b3%95%e8%a7%84%e5%88%99" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h3><p><code>round()</code> 的完整语法规则还是比较复杂的。完整的介绍可以看 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/round"target="_blank" rel="external nofollow noopener noreferrer">MDN - round()<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a>。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="o">&lt;</span><span class="nt">round</span><span class="o">()&gt;</span> <span class="o">=</span> <span class="nt">round</span><span class="o">(</span> <span class="o">&lt;</span><span class="nt">rounding-strategy</span><span class="o">&gt;?,</span> <span class="o">&lt;</span><span class="nt">valueToRound</span><span class="o">&gt;</span> <span class="o">,</span> <span class="o">&lt;</span><span class="nt">roundingInterval</span><span class="o">&gt;</span> <span class="o">)</span></span></span></code></pre></td></tr></table> </div> </div><p><code>round(&lt;rounding-strategy&gt;, valueToRound, roundingInterval)</code> 函数指定可选的舍入策略、要舍入的值(或数学表达式)和舍入间隔(或数学表达式)。根据舍入策略,<code>valueToRound</code> 四舍五入到 <code>roundingInterval</code> 的最接近整数倍。</p> <ul> <li><code>&lt;rounding-strategy&gt;</code>: 可选参数,表示舍入策略。这可能是以下值之一: <ul> <li><code>up</code>: 相当于 JavaScript Math.ceil() 方法,将 valueToRound 向上舍入到 roundingInterval 最接近的整数倍。这相当于 JavaScript Math.ceil() 方法。</li> <li><code>down</code>: 将 valueToRound 向下舍入为 roundingInterval 最接近的整数倍。这相当于 JavaScript Math.floor() 方法。</li> <li><code>nearest</code>: 将 valueToRound 舍入为 roundingInterval 的最接近的整数倍,该倍数可以高于或低于该值。如果 valueToRound 是上方和下方舍入目标之间的一半,则会向上舍入。相当于 JavaScript Math.round()。</li> <li><code>to-zero</code>: 将 valueToRound 舍入为 roundingInterval 接近/接近零的最接近整数倍。这相当于 JavaScript Math.trunc() 方法。</li> </ul> </li> <li><code>&lt;valueToRound&gt;</code>: 需要被四舍五入的值。必须是 <code>&lt;number&gt;</code>、<code>&lt;dimension&gt;</code> 或 <code>&lt;percentage&gt;</code>,或者解析为这些值之一的数学表达式。</li> <li><code>&lt;roundingInterval&gt;</code>: 舍入的间隔规则。这是一个 <code>&lt;number&gt;</code>、<code>&lt;dimension&gt;</code> 或 <code>&lt;percentage&gt;</code>,或者解析为这些值之一的数学表达式。</li> </ul> <h3 id="示例" class="heading-element"><span>1.2 示例</span> <a href="#%e7%a4%ba%e4%be%8b" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h3><div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">width</span><span class="o">:</span> <span class="nt">round</span><span class="o">(</span><span class="nt">var</span><span class="o">(</span><span class="nt">--width</span><span class="o">),</span> <span class="nt">50px</span><span class="o">);</span> </span></span><span class="line"><span class="cl"><span class="nt">width</span><span class="o">:</span> <span class="nt">round</span><span class="o">(</span><span class="nt">up</span><span class="o">,</span> <span class="nt">101px</span><span class="o">,</span> <span class="nt">var</span><span class="o">(</span><span class="nt">--interval</span><span class="o">));</span> </span></span><span class="line"><span class="cl"><span class="nt">width</span><span class="o">:</span> <span class="nt">round</span><span class="o">(</span><span class="nt">down</span><span class="o">,</span> <span class="nt">var</span><span class="o">(</span><span class="nt">--height</span><span class="o">),</span> <span class="nt">var</span><span class="o">(</span><span class="nt">--interval</span><span class="o">));</span> </span></span><span class="line"><span class="cl"><span class="nt">margin</span><span class="o">:</span> <span class="nt">round</span><span class="o">(</span><span class="nt">to-zero</span><span class="o">,</span> <span class="nt">-105px</span><span class="o">,</span> <span class="nt">10px</span><span class="o">);</span></span></span></code></pre></td></tr></table> </div> </div><p>MDN 官方写了一个完整的例子,可以看 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/round"target="_blank" rel="external nofollow noopener noreferrer">Playground | MDN<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a>。</p> <h2 id="有什么用" class="heading-element"><span>2 有什么用</span> <a href="#%e6%9c%89%e4%bb%80%e4%b9%88%e7%94%a8" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>上面讲了一大堆概念,那这玩意到底有什么用勒?</p> <p>好好好,那就再回想一下在此之前我们开发中遇到的痛点吧:</p> <ul> <li>解决基于 transform 的模糊问题</li> <li>使用 <code>round()</code> 模拟步骤缓动动画</li> <li>解决百分比或者 <code>rem</code> 单位的四舍五入问题</li> </ul> <p>前两点可以看 ChokCoco 的文章 <a href="https://www.cnblogs.com/coco1s/p/17676226.html"target="_blank" rel="external nofollow noopener noreferrer">现代 CSS 解决方案:数学函数 Round<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a>,里面有详细的讲解。</p> <p>而我遇到的主要问题就是第三点,也就是由于浏览器渲染机制,导致有时百分比或者 <code>rem</code> 的单位实际计算值为小数的情况引起的系列问题。</p> <p>造成这个现象的主要原因有:</p> <ul> <li>像素单位和设备像素比(DPR)</li> <li>浏览器的子像素渲染偏差</li> </ul> <p>举个例子吧:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">.</span><span class="nc">container</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">width</span><span class="p">:</span> <span class="mi">100</span><span class="kt">px</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">child</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">width</span><span class="p">:</span> <span class="mf">33.33</span><span class="kt">%</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>如果容器 <code>.container</code> 中有三个 <code>.child</code> 元素,那么每个 <code>.child</code> 的理论宽度应该是 <code>33.33px</code>。然而,由于不能将像素拆分,浏览器采取四舍五入方式处理,从而得到了 <code>33px</code> 或 <code>34px</code> 的结果。然而,为了保持布局的精确性,浏览器实际上以子像素的方式保存了这些值,并且在渲染时考虑了这部分差异。</p> <p>但是!!!坑爹的是,不同浏览器的处理方式也会不同,还有就是子孙节点继承宽度时可能会有四舍五入导致子孙节点宽度大于父节点宽度的问题。</p> <h2 id="实际场景应用" class="heading-element"><span>3 实际场景应用</span> <a href="#%e5%ae%9e%e9%99%85%e5%9c%ba%e6%99%af%e5%ba%94%e7%94%a8" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>在 FixIt 主题中,页面内容分为左、中、右三栏:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="nc">.wrapper</span> <span class="nt">main</span><span class="nc">.container</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">display</span><span class="o">:</span> <span class="ni">flex</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">align-items</span><span class="o">:</span> <span class="ni">flex-start</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">justify-content</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">padding-inline</span><span class="o">:</span> <span class="mi">1</span><span class="kt">rem</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">gap</span><span class="o">:</span> <span class="mi">0</span><span class="mf">.5</span><span class="kt">rem</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="nc">.page</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">width</span><span class="o">:</span> <span class="mi">56</span><span class="kt">%</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="nn">#comments</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">width</span><span class="o">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="c1">// 评论区域 iframe </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nt">iframe</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">width</span><span class="o">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>例如在 <code>1440px</code> 的屏幕上,中间内容宽度应该为 <code>(1440 - 2 * 16) * 0.56 = 788.48px</code>,实际渲染结果却是 <code>788.48px</code>。</p> <p>问题来了,在内容的最后加上一个 giscus 评论区域,评论区域容器 <code>iframe</code> 和 <code>iframe</code> 里面的内容按理说应该都是 <code>788.48px</code>,但是实际渲染结果却是:</p> <ul> <li>评论容器 <code>iframe</code>:<code>788.48px</code></li> <li><code>iframe</code> 内评论内容 HTML:<code>789px</code></li> </ul> <p>怎么说?无奈不,四舍五入,你舍掉其实这个场景我也就不纠结了,恰好它是符合<strong>五入</strong>的规则,向上 <code>1px</code> 取整了,导致的视觉上的影响就是 giscus 评论区域右侧的边框恰巧不见了。—T_T—</p> <p>那怎么搞勒,治标不治本的做法就是,把评论区 <code>#comments</code> 的宽度缩小一些,但是我不想这样做。</p> <p>我想既然问题是小数点造成的,避免产生小数点不就好了,然后就用到了 <code>round()</code> 函数:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">.</span><span class="nc">page</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">width</span><span class="p">:</span> <span class="nf">round</span><span class="p">(</span><span class="mi">56</span><span class="kt">%</span><span class="p">,</span> <span class="mi">2</span><span class="kt">px</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>我希望页面中间永远是偶数,这样可以同时避免左、中、右三个部分出现小数,所以这里的 <code>roundingInterval</code> 设置为 <code>2px</code>,表示四舍五入到 <code>2px</code> 的整数倍,这样就避免了小数点的问题。</p> <p>好了,问题就这样愉快地解决了……吗?</p> <p>好吧,并没有。—T_T—</p> <h2 id="兼容性" class="heading-element"><span>4 兼容性</span> <a href="#%e5%85%bc%e5%ae%b9%e6%80%a7" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>开头说了这是一个比较新的 CSS 函数,所以兼容性肯定是个问题。尽管<strong>截至 2024 年 7 月 29 日</strong>,<code>round()</code> 函数覆盖了 <strong>67.63%</strong> 的浏览器,并且在各类浏览器中的最新几个版本都得到了完全支持(忽略 IE),但是在一些老版本的浏览器中还是不支持的,如下图。</p> <p class="ciu-embed" data-feature="mdn-css_types_round" data-past="2" data-future="1" data-observer="true" data-theme=""></p> <p>那又怎么搞勒?要崩溃了,最讨厌兼容性了,也讨厌 Polyfill,但是毕竟 FixIt 主题不是我一个人在用,还是加一下 Polyfill 吧。</p> <p>好家伙!没有 Polyfill 可用。真要崩溃了,前面都白折腾了?—T_T—</p> <p>也不是没有办法,自己写一个,利用 <code>@supports</code> 写一些兼容性代码吧,不支持的就不用 <code>round()</code> 函数了。</p> <p>上面简化后的例子兼容性可以这样写:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="c1">// 顺便提一下,大写 ROUND 是为了避免和 Sass 的 round 函数冲突,CSS 中对函数关键词大小写不敏感。 </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nc">.wrapper</span> <span class="nt">main</span><span class="nc">.container</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">display</span><span class="o">:</span> <span class="ni">flex</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">align-items</span><span class="o">:</span> <span class="ni">flex-start</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">justify-content</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">padding-inline</span><span class="o">:</span> <span class="mi">1</span><span class="kt">rem</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">gap</span><span class="o">:</span> <span class="mi">0</span><span class="mf">.5</span><span class="kt">rem</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="nc">.page</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">width</span><span class="o">:</span> <span class="nf">ROUND</span><span class="p">(</span><span class="mi">56</span><span class="kt">%</span><span class="o">,</span> <span class="mi">2</span><span class="kt">px</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nn">#comments</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">width</span><span class="o">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="c1">// 评论区域 iframe </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nt">iframe</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">width</span><span class="o">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// 如果不支持 round() 函数,回退到设定固定值 56% </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">@supports</span> <span class="nt">not</span> <span class="o">(</span><span class="nt">width</span><span class="nd">:</span> <span class="nt">ROUND</span><span class="o">(</span><span class="nt">56</span><span class="err">%</span><span class="o">,</span> <span class="nt">2px</span><span class="o">))</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nc">.page</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">width</span><span class="o">:</span> <span class="mi">56</span><span class="kt">%</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="cm">/* ... */</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>好了,跌跌撞撞就这样了,等过两年 <code>round()</code> 函数覆盖率更高了我第一件事就是把上面的兼容性代码删掉。&#x1f602;</p>

Git 统计代码量

2024-07-17 16:06:08

<p>使用 Git 命令统计在某段时间内项目中的代码量。</p> <h2 id="统计-commit-数" class="heading-element"><span>1 统计 commit 数</span> <a href="#%e7%bb%9f%e8%ae%a1-commit-%e6%95%b0" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git log --author<span class="o">=</span><span class="s2">&#34;[email protected]&#34;</span> --since<span class="o">=</span><span class="s2">&#34;2023-01-01&#34;</span> --until<span class="o">=</span><span class="s2">&#34;2023-12-31&#34;</span> --oneline <span class="p">|</span> wc -l</span></span></code></pre></td></tr></table> </div> </div><h2 id="统计行数" class="heading-element"><span>2 统计行数</span> <a href="#%e7%bb%9f%e8%ae%a1%e8%a1%8c%e6%95%b0" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git log --author<span class="o">=</span><span class="s2">&#34;[email protected]&#34;</span> --pretty<span class="o">=</span>tformat: --since<span class="o">=</span><span class="s2">&#34;2023-01-01&#34;</span> --until<span class="o">=</span><span class="s2">&#34;2023-12-31&#34;</span> --numstat -- . <span class="s2">&#34;:(exclude)build&#34;</span> <span class="s2">&#34;:(exclude)dist&#34;</span> <span class="s2">&#34;:(exclude)node_modules&#34;</span> <span class="s2">&#34;:(exclude)test&#34;</span> <span class="s2">&#34;:(exclude)static&#34;</span> -numstat <span class="p">|</span> awk <span class="s1">&#39;{ add += $1; subs += $2; loc += $1 - $2 } END { printf &#34;added lines: %s, removed lines: %s, total lines: %s\n&#34;, add, subs, loc }&#39;</span></span></span></code></pre></td></tr></table> </div> </div><h2 id="整合成一个脚本" class="heading-element"><span>3 整合成一个脚本</span> <a href="#%e6%95%b4%e5%90%88%e6%88%90%e4%b8%80%e4%b8%aa%e8%84%9a%e6%9c%ac" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>统计所有作者移除 <code>--author=&quot;$author_email&quot;</code></p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash </span></span></span><span class="line"><span class="cl"><span class="cp"></span> </span></span><span class="line"><span class="cl"><span class="nv">project_name</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span><span class="s2">&#34;</span><span class="k">)</span> </span></span><span class="line"><span class="cl"><span class="nv">author_email</span><span class="o">=</span><span class="s2">&#34;[email protected]&#34;</span> </span></span><span class="line"><span class="cl"><span class="c1"># since_date=&#34;1 year ago&#34;</span> </span></span><span class="line"><span class="cl"><span class="nv">since_date</span><span class="o">=</span><span class="s2">&#34;2023-01-01&#34;</span> </span></span><span class="line"><span class="cl"><span class="nv">until_date</span><span class="o">=</span><span class="s2">&#34;2023-12-31&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># 统计 commit 数量</span> </span></span><span class="line"><span class="cl"><span class="nv">commit_count</span><span class="o">=</span><span class="k">$(</span>git log --author<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$author_email</span><span class="s2">&#34;</span> --since<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$since_date</span><span class="s2">&#34;</span> --until<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$until_date</span><span class="s2">&#34;</span> --oneline <span class="p">|</span> wc -l<span class="k">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># 统计代码行数(排除目录 build、dist、node_modules、test、static)</span> </span></span><span class="line"><span class="cl"><span class="nv">line_stats</span><span class="o">=</span><span class="k">$(</span>git log --author<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$author_email</span><span class="s2">&#34;</span> --pretty<span class="o">=</span>tformat: --since<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$since_date</span><span class="s2">&#34;</span> --until<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$until_date</span><span class="s2">&#34;</span> --numstat -- . <span class="s2">&#34;:(exclude)build&#34;</span> <span class="s2">&#34;:(exclude)dist&#34;</span> <span class="s2">&#34;:(exclude)node_modules&#34;</span> <span class="s2">&#34;:(exclude)test&#34;</span> <span class="s2">&#34;:(exclude)static&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{ add += $1; subs += $2; } END { printf &#34;%s ++\t%s --\n&#34;, add, subs }&#39;</span><span class="k">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$project_name</span><span class="s2">:\t</span><span class="nv">$commit_count</span><span class="s2"> commits\t</span><span class="nv">$line_stats</span><span class="s2">&#34;</span></span></span></code></pre></td></tr></table> </div> </div><p>例如在 FixIt 项目中截至 2024-07-17 为止我的代码统计如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-plain" data-lang="plain"><span class="line"><span class="cl">FixIt: 1022 commits 82040 ++ 103942 --</span></span></code></pre></td></tr></table> </div> </div>

现代 CSS 解决方案之异形元素怎么设置阴影?

2024-07-15 10:40:12

<p>今天记录一个 CSS 小知识点,如何给异形元素设置阴影。</p> <h2 id="遇到的问题" class="heading-element"><span>1 遇到的问题</span> <a href="#%e9%81%87%e5%88%b0%e7%9a%84%e9%97%ae%e9%a2%98" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>之前给博客头部设置了一个 <a href="https://lruihao.cn/images/drop.responsive.svg">异形元素</a>,当时给它父元素设置了如下 <code>box-shadow</code>:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">header</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">box-shadow</span><span class="p">:</span> <span class="mi">0</span> <span class="mi">0</span> <span class="mf">1.5</span><span class="kt">rem</span> <span class="mi">0</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>发现只有矩形部分有阴影,异形部分没有阴影。是因为 <code>box-shadow</code> 只能给盒子模型设置阴影,异形元素无法设置阴影的。</p> <p>那怎么给异形元素设置阴影呢?</p> <h2 id="解决方法" class="heading-element"><span>2 解决方法</span> <a href="#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%b3%95" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>可以使用 <code>filter</code> 属性的 <code>drop-shadow</code> 函数来给异形元素设置阴影。</p> <p><code>drop-shadow</code> 绘制的投影实际上是输入图像的 alpha 蒙版的一个模糊的、偏移的版本,用特定的颜色绘制并合成在图像下面。</p> <div class="details admonition note open"> <div class="details-summary admonition-title"> <i class="icon fa-solid fa-pencil-alt fa-fw" aria-hidden="true"></i>备注<i class="details-icon fa-solid fa-angle-right fa-fw" aria-hidden="true"></i> </div> <div class="details-content"> <div class="admonition-content">这个函数有点类似于 <code>box-shadow</code> 属性。<code>box-shadow</code> 属性在元素的整个框后面创建一个矩形阴影,而 <code>drop-shadow()</code> 过滤器则是创建一个符合图像本身形状 (alpha 通道) 的阴影。</div> </div> </div> <p>语法如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">drop-shadow</span><span class="o">(</span><span class="nt">offset-x</span> <span class="nt">offset-y</span> <span class="nt">standard-deviation</span> <span class="nt">color</span><span class="o">)</span></span></span></code></pre></td></tr></table> </div> </div><p>可以看出,<code>drop-shadow</code> 比 <code>box-shadow</code> 少了一个阴影的扩展半径 <code>spread-radius</code> 参数(或者说尚未实现)。</p> <p>回到我的问题,给异形元素设置阴影的代码如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">header</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">filter</span><span class="p">:</span> <span class="nb">drop-shadow</span><span class="p">(</span><span class="mi">0</span> <span class="mi">0</span> <span class="mf">0.75</span><span class="kt">rem</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">));</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><h2 id="浏览器支持" class="heading-element"><span>3 浏览器支持</span> <a href="#%e6%b5%8f%e8%a7%88%e5%99%a8%e6%94%af%e6%8c%81" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p class="ciu-embed" data-feature="mdn-css_types_filter-function_drop-shadow" data-past="2" data-future="1" data-observer="true" data-theme=""></p> <h2 id="参考链接" class="heading-element"><span>4 参考链接</span> <a href="#%e5%8f%82%e8%80%83%e9%93%be%e6%8e%a5" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><ul> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/drop-shadow"target="_blank" rel="external nofollow noopener noreferrer">drop-shadow() - CSS: Cascading Style Sheets | MDN<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a></li> </ul>

CSS 实现时间轴、背景图 loading 和渐变边框

2024-07-14 13:03:31

<img src="https://lruihao.cn/posts/fixit-docs-bookmark/images/featured-image.webp" alt="featured image" referrerpolicy="no-referrer"><p>本文将通过一个实际应用场景,展示如何使用现代 CSS 实现时间轴、背景图 loading 效果、渐变边框等效果。</p> <h2 id="背景" class="heading-element"><span>1 背景</span> <a href="#%e8%83%8c%e6%99%af" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>最近在调整 FixIt 主题的官方文档,调整过程中总觉得首页空荡荡的少了些内容,然后就在脑海里构思了如本文封面图所示的效果,希望引导用户阅读文档。</p> <p>需求分析:</p> <ol> <li>整体布局:左右两栏,左侧为主题文档大纲,右侧为网站预览图</li> <li>左侧需要显示时间轴,时间轴带有跑马灯动画效果(暗指文档阅读顺序)</li> <li>右侧加载网站预览图时,需要有 loading 效果</li> <li>整体边框需要渐变效果</li> </ol> <p>第一点很简单,一个 <code>flex</code> 布局就能搞定了,这里不再展开。</p> <p>接下来我们重点看看如何实现时间轴、背景图 loading 效果和渐变边框。</p> <h2 id="时间轴" class="heading-element"><span>2 时间轴</span> <a href="#%e6%97%b6%e9%97%b4%e8%bd%b4" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>先睹为快,效果如下:</p> <p><figure><a class="lightgallery" href="https://lruihao.cn/posts/fixit-docs-bookmark/images/demo-outline.gif?size=large" data-thumbnail="/posts/fixit-docs-bookmark/images/demo-outline.gif?size=small" data-sub-html="<h2>时间轴动画</h2><p>从上到下依次点亮时间轴</p>"><img loading="lazy" src="https://lruihao.cn/posts/fixit-docs-bookmark/images/demo-outline.gif" alt="时间轴动画" srcset="https://lruihao.cn/posts/fixit-docs-bookmark/images/demo-outline.gif?size=small, https://lruihao.cn/posts/fixit-docs-bookmark/images/demo-outline.gif?size=medium 1.5x, https://lruihao.cn/posts/fixit-docs-bookmark/images/demo-outline.gif?size=large 2x" data-title="从上到下依次点亮时间轴" style="--width: 264px;--aspect-ratio: 264 / 192;background: url(/images/loading.min.svg) no-repeat center;" onload="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}this.dataset.lazyloaded='';" onerror="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}"/></a><figcaption class="image-caption">时间轴动画</figcaption> </figure></p> <p>我不希望时间轴的实现和其他 UI 框架一样拥有复杂的 DOM 结构,所以使用最简单的 <code>ul</code> 和 <code>li</code> 即可,关键代码如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">ul</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;docs-outline&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;{{ $page1.Permalink }}&#34;</span><span class="p">&gt;</span>{{ $page1.LinkTitle }}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;{{ $page2.Permalink }}&#34;</span><span class="p">&gt;</span>{{ $page2.LinkTitle }}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;{{ $page3.Permalink }}&#34;</span><span class="p">&gt;</span>{{ $page3.LinkTitle }}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;{{ $page4.Permalink }}&#34;</span><span class="p">&gt;</span>{{ $page4.LinkTitle }}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span></span></span></code></pre></td></tr></table> </div> </div><p>DOM 结构就这么简单,重点在于 CSS 的实现,实现思路如下:</p> <ul> <li>先把 <code>ul</code> 设置 <code>list-style: none</code>,去掉默认的 <code>li</code> 样式</li> <li>利用 <code>::before</code> 和 <code>::after</code> 伪元素实现时间轴的小圆点和连接线</li> <li>动画效果:四个小圆点默认和连接线一样灰色,然后依次点亮,可以使用 <code>animation-delay</code> 属性实现</li> </ul> <p>动画效果关键帧代码如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="nt">li</span><span class="nd">::before</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">border</span><span class="o">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">timeline-width</span><span class="p">)</span> <span class="ni">solid</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">timeline-color</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="na">animation-name</span><span class="o">:</span> <span class="n">border-color-fade</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">animation-duration</span><span class="o">:</span> <span class="mi">2</span><span class="kt">s</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">animation-iteration-count</span><span class="o">:</span> <span class="ni">infinite</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">animation-delay</span><span class="o">:</span> <span class="nf">calc</span><span class="p">(</span><span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">timeline-index</span><span class="p">)</span> <span class="o">*</span> <span class="mi">0</span><span class="mf">.3</span><span class="kt">s</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">@keyframes</span> <span class="nt">border-color-fade</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">0</span><span class="err">%</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">border-color</span><span class="o">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">timeline-color</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="nt">50</span><span class="err">%</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">border-color</span><span class="o">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">timeline-circle-color</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="nt">100</span><span class="err">%</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">border-color</span><span class="o">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">timeline-color</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>通过 <code>nth-child</code> 选择器来设置 <code>--timeline-index</code> 来线性增加每个小圆点的动画延迟时间,从而在视觉上出现依次点亮的效果。</p> <p>如果使用 SCSS 可以简化代码,如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span><span class="lnt">9 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="k">@for</span> <span class="nv">$i</span> <span class="ow">from</span> <span class="mi">1</span> <span class="ow">through</span> <span class="mi">4</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">li</span><span class="nd">:nth-child</span><span class="o">(</span><span class="si">#{</span><span class="nv">$i</span><span class="si">}</span><span class="o">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">--timeline-index</span><span class="o">:</span> <span class="si">#{</span><span class="nv">$i</span><span class="si">}</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">&amp;</span><span class="nd">::before</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">content</span><span class="o">:</span> <span class="s1">&#39;</span><span class="si">#{</span><span class="nv">$i</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>如果需要在小圆点的内部显示序号,可以像设置 <code>--timeline-index</code> 一样依次设置 <code>content</code>。</p> <p>然后在小圆点点亮动画过程中同时转变 <code>color: transparent</code> 到具体的颜色即可。</p> <h2 id="背景图-loading-效果" class="heading-element"><span>3 背景图 loading 效果</span> <a href="#%e8%83%8c%e6%99%af%e5%9b%be-loading-%e6%95%88%e6%9e%9c" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p><figure><a class="lightgallery" href="https://lruihao.cn/posts/fixit-docs-bookmark/images/demo-loading.gif?size=large" data-thumbnail="/posts/fixit-docs-bookmark/images/demo-loading.gif?size=small" data-sub-html="<h2>背景图 loading 效果</h2><p>先加载 loading 图再加重预览图</p>"><img loading="lazy" src="https://lruihao.cn/posts/fixit-docs-bookmark/images/demo-loading.gif" alt="背景图 loading 效果" srcset="https://lruihao.cn/posts/fixit-docs-bookmark/images/demo-loading.gif?size=small, https://lruihao.cn/posts/fixit-docs-bookmark/images/demo-loading.gif?size=medium 1.5x, https://lruihao.cn/posts/fixit-docs-bookmark/images/demo-loading.gif?size=large 2x" data-title="先加载 loading 图再加重预览图" style="--width: 815px;--aspect-ratio: 815 / 300;background: url(/images/loading.min.svg) no-repeat center;" onload="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}this.dataset.lazyloaded='';" onerror="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}"/></a><figcaption class="image-caption">背景图 loading 效果</figcaption> </figure></p> <p>如果只是想实现图片的 loading 效果其实很简单,之前在「<a href="https://lruihao.cn/posts/native-img-loading-lazy/">浏览器 IMG 图片原生懒加载 Loading=&ldquo;lazy&rdquo;</a>」中有介绍过,但是这次我是把图片当作背景图片使用的,那问题来了,不通过 JS 背景图片的 loading 效果怎么实现呢?</p> <p>还是可以利用 <code>::before</code> 和 <code>::after</code> 伪元素,一个伪元素用来显示 loading 图,另一个伪元素用来显示背景图片。</p> <p>原理:利用两张图片加载的时间差,由于 loading 图片很小,所以加载很快,而背景图片加载较慢,然后默认不设置 <code>z-index</code> 的情况下,后面的元素会在上层,所以在背景图片加载完成前,loading 图片会一直显示。</p> <p>关键代码如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="nc">.docs-preview</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">&amp;</span><span class="o">::</span><span class="n">before</span><span class="o">,</span> </span></span><span class="line"><span class="cl"> <span class="o">&amp;::</span><span class="n">after</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">content</span><span class="o">:</span> <span class="s1">&#39;&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">position</span><span class="o">:</span> <span class="ni">absolute</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">top</span><span class="o">:</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">left</span><span class="o">:</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">width</span><span class="o">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">height</span><span class="o">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">background-position</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">background-repeat</span><span class="o">:</span> <span class="ni">no-repeat</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">&amp;</span><span class="nd">::before</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">background-color</span><span class="o">:</span> <span class="nf">rgba</span><span class="p">(</span><span class="mi">204</span><span class="o">,</span> <span class="mi">204</span><span class="o">,</span> <span class="mi">204</span><span class="o">,</span> <span class="mi">0</span><span class="mf">.1</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="na">background-image</span><span class="o">:</span> <span class="sx">url(/images/loading.min.svg)</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">background-size</span><span class="o">:</span> <span class="mi">60</span><span class="kt">px</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">&#39;dark&#39;</span><span class="o">]</span> <span class="k">&amp;</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">background-color</span><span class="o">:</span> <span class="nf">rgba</span><span class="p">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="mf">.1</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">&amp;</span><span class="nd">::after</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">background-image</span><span class="o">:</span> <span class="sx">url(/images/apple-devices-preview.webp)</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">background-size</span><span class="o">:</span> <span class="mi">130</span><span class="kt">%</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><h2 id="全尺寸带圆角渐变边框" class="heading-element"><span>4 全尺寸带圆角渐变边框</span> <a href="#%e5%85%a8%e5%b0%ba%e5%af%b8%e5%b8%a6%e5%9c%86%e8%a7%92%e6%b8%90%e5%8f%98%e8%be%b9%e6%a1%86" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>最后实现一个全尺寸带圆角渐变边框效果,一句话概括为利用线性渐变 <code>linear-gradient</code> 分别设置 <code>padding-box</code> 和 <code>border-box</code> 的背景,然后 <code>border</code> 颜色设置为透明即可实现。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="nc">.docs-navigation</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">border-radius</span><span class="o">:</span> <span class="mi">2</span><span class="mf">.5</span><span class="kt">px</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">background</span><span class="o">:</span> <span class="nf">linear-gradient</span><span class="p">(</span><span class="mh">#fff</span><span class="o">,</span> <span class="mh">#fff</span><span class="p">)</span> <span class="ni">padding-box</span><span class="o">,</span> <span class="nf">linear-gradient</span><span class="p">(</span><span class="mi">45</span><span class="kt">deg</span><span class="o">,</span> <span class="mh">#42d392</span><span class="o">,</span> <span class="mh">#FF7359</span><span class="p">)</span> <span class="ni">border-box</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">border</span><span class="o">:</span> <span class="mi">0</span><span class="mf">.25</span><span class="kt">rem</span> <span class="ni">solid</span> <span class="ni">transparent</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>值得一提的是,这里面利用到的一个核心概念是 <code>background-clip</code> 属性,详见 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/background-clip"target="_blank" rel="external nofollow noopener noreferrer">background-clip - CSS: Cascading Style Sheets | MDN<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a>:</p> <ul> <li><code>padding-box</code> 表示背景延伸到内边距边界</li> <li><code>border-box</code> 表示背景延伸到边框边界。</li> </ul> <p>另外,如果想实现渐变边框的动画效果,用 SCSS 可以这样做(虽然我不认为这是一个好的做法):</p> <div class="highlight" data-open="false"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="nc">.docs-navigation</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">border-radius</span><span class="o">:</span> <span class="mi">2</span><span class="mf">.5</span><span class="kt">px</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">background</span><span class="o">:</span> <span class="nf">linear-gradient</span><span class="p">(</span><span class="mh">#fff</span><span class="o">,</span> <span class="mh">#fff</span><span class="p">)</span> <span class="ni">padding-box</span><span class="o">,</span> <span class="nf">linear-gradient</span><span class="p">(</span><span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">gradient-angle</span><span class="p">)</span><span class="o">,</span> <span class="mh">#42d392</span><span class="o">,</span> <span class="mh">#FF7359</span><span class="p">)</span> <span class="ni">border-box</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">border</span><span class="o">:</span> <span class="mi">0</span><span class="mf">.25</span><span class="kt">rem</span> <span class="ni">solid</span> <span class="ni">transparent</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">transition</span><span class="o">:</span> <span class="n">background-color</span> <span class="mi">0</span><span class="mf">.5</span><span class="kt">s</span><span class="o">,</span> <span class="n">border-color</span> <span class="mi">0</span><span class="mf">.5</span><span class="kt">s</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">--gradient-angle</span><span class="o">:</span> <span class="mi">45</span><span class="kt">deg</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">animation</span><span class="o">:</span> <span class="n">gradient-angle-change</span> <span class="mi">10</span><span class="kt">s</span> <span class="ni">infinite</span> <span class="ni">linear</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// 分的越细,动画效果越平滑 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">@keyframes</span> <span class="nt">gradient-angle-change</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">@for</span> <span class="nv">$i</span> <span class="ow">from</span> <span class="mi">0</span> <span class="ow">through</span> <span class="mi">100</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="si">#{</span><span class="nv">$i</span> <span class="o">*</span> <span class="mi">1</span><span class="kt">%</span><span class="si">}</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">--gradient-angle</span><span class="o">:</span> <span class="si">#{</span><span class="mi">45</span> <span class="o">+</span> <span class="nv">$i</span> <span class="o">*</span> <span class="mi">4</span><span class="si">}</span><span class="n">deg</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><h2 id="最后的效果" class="heading-element"><span>5 最后的效果</span> <a href="#%e6%9c%80%e5%90%8e%e7%9a%84%e6%95%88%e6%9e%9c" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>为了方便使用 FixIt 主题的用户在自己的笔记内插入 FixIt 官方文档的书签,我把这个效果封装成了一个独立的组件,你可以在 <a href="https://github.com/hugo-fixit/shortcode-docs-bookmark"target="_blank" rel="external nofollow noopener noreferrer">hugo-fixit/shortcode-docs-bookmark<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a> 中找到完整代码及食用方法。</p> <div class="fixit-docs-navigation"><ul class="fixit-docs-outline"><li><a href="https://fixit.lruihao.cn/zh-cn/documentation/installation/" title="只需几个步骤即可为你的 Hugo 站点安装 FixIt 主题。">安装篇</a></li><li><a href="https://fixit.lruihao.cn/zh-cn/documentation/getting-started/" title="安装和使用 FixIt 主题的快速入门和指南。">入门篇</a></li><li><a href="https://fixit.lruihao.cn/zh-cn/documentation/content-management/" title="了解如何在 FixIt 主题中快速,直观地创建和组织内容。">内容管理</a></li><li><a href="https://fixit.lruihao.cn/zh-cn/documentation/advanced/" title="探索 Hugo - FixIt 主题的的进阶使用。">进阶篇</a></li></ul><div class="fixit-docs-preview"></div></div> <h2 id="总结" class="heading-element"><span>6 总结</span> <a href="#%e6%80%bb%e7%bb%93" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>很多时候都感觉写 CSS 就像在写诗,相同的效果 CSS 实现往往会比 JS 更加优雅。</p>

标准滚动条控制规范

2024-06-28 22:29:03

<p>使用 <code>scrollbar-width</code> 和 <code>scrollbar-color</code> 属性设置滚动条的样式。</p> <h2 id="简介" class="heading-element"><span>1 简介</span> <a href="#%e7%ae%80%e4%bb%8b" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>从 Chrome 版本 2 开始,可以使用 <code>::-webkit-scrollbar-*</code> 伪元素设置滚动条的样式。此方法在 Chrome 和 Safari 中都很有效,但 CSS 工作组从未标准化。</p> <blockquote><p>MDN - ::-webkit-scrollbar Non-standard: This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.</p> </blockquote><p>实现标准化的是 <code>scrollbar-width</code> 和 <code>scrollbar-color</code> 属性,它们是 <a href="https://drafts.csswg.org/css-scrollbars/#scrollbar-width"target="_blank" rel="external nofollow noopener noreferrer">CSS Scrollbars Styling Module Level 1<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a> 的一部分。从 Chrome 121 开始,这些属性受支持。</p> <h2 id="滚动条入门指南" class="heading-element"><span>2 滚动条入门指南</span> <a href="#%e6%bb%9a%e5%8a%a8%e6%9d%a1%e5%85%a5%e9%97%a8%e6%8c%87%e5%8d%97" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><h3 id="滚动条剖析" class="heading-element"><span>2.1 滚动条剖析</span> <a href="#%e6%bb%9a%e5%8a%a8%e6%9d%a1%e5%89%96%e6%9e%90" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h3><p>滚动条至少由一个轨迹和一个滑块组成。滑道是拇指可以移动的区域。轨迹表示整个滚动距离。滑块表示可滚动区域内的当前位置。滚动时,它会在轨道内移动。拇指通常也是可拖动的。</p> <p>不过,滚动条可以有多个部分,而不仅仅是滑块和滑道。例如,滚动条可以包含一个或多个用于递增或递减滚动偏移的按钮。滚动条的组成部分由底层操作系统决定。</p> <p><figure><a class="lightgallery" href="https://lruihao.cn/posts/scrollbar-styling/images/24_1719585398.png?size=large" data-thumbnail="/posts/scrollbar-styling/images/24_1719585398.png?size=small" data-sub-html="<h2>组成滚动条的各个部分的图示</h2><p>左侧插图是一个最小的滚动条,其中只有轨迹和拇指。右边的按钮也有一些按钮。</p>"><img loading="lazy" src="https://lruihao.cn/posts/scrollbar-styling/images/24_1719585398.png" alt="组成滚动条的各个部分的图示" srcset="https://lruihao.cn/posts/scrollbar-styling/images/24_1719585398.png?size=small, https://lruihao.cn/posts/scrollbar-styling/images/24_1719585398.png?size=medium 1.5x, https://lruihao.cn/posts/scrollbar-styling/images/24_1719585398.png?size=large 2x" data-title="左侧插图是一个最小的滚动条,其中只有轨迹和拇指。右边的按钮也有一些按钮。" style="--width: 4000px;--aspect-ratio: 4000 / 2250;background: url(/images/loading.min.svg) no-repeat center;" onload="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}this.dataset.lazyloaded='';" onerror="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}"/></a><figcaption class="image-caption">组成滚动条的各个部分的图示</figcaption> </figure></p> <h3 id="传统滚动条和重叠式滚动条" class="heading-element"><span>2.2 传统滚动条和重叠式滚动条</span> <a href="#%e4%bc%a0%e7%bb%9f%e6%bb%9a%e5%8a%a8%e6%9d%a1%e5%92%8c%e9%87%8d%e5%8f%a0%e5%bc%8f%e6%bb%9a%e5%8a%a8%e6%9d%a1" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h3><p>在介绍如何设置滚动条的样式之前,请务必先了解两种滚动条之间的区别。</p> <table> <thead> <tr> <th style="text-align: left">操作系统</th> <th style="text-align: left">默认滚动条</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">Mac</td> <td style="text-align: left">叠加滚动条(Overlay scrollbars)</td> </tr> <tr> <td style="text-align: left">Windows</td> <td style="text-align: left">经典滚动条(Classic scrollbars)</td> </tr> </tbody> </table> <h4 id="叠加滚动条" class="heading-element"><span>2.2.1 叠加滚动条</span> <a href="#%e5%8f%a0%e5%8a%a0%e6%bb%9a%e5%8a%a8%e6%9d%a1" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h4><p>叠加层滚动条是在下方内容之上的浮动滚动条。默认情况下,这些按钮不会显示,只有当主动滚动时才会显示。为了让内容保持可见状态,它们通常采用半透明形式,但这由操作系统来决定。在与它们互动时,它们的大小也可能有所变化。</p> <p><figure><a class="lightgallery" href="https://lruihao.cn/posts/scrollbar-styling/images/24_1719585770.png?size=large" data-thumbnail="/posts/scrollbar-styling/images/24_1719585770.png?size=small" data-sub-html="<h2>带有叠加滚动条的浏览器</h2><p>滚动条会叠加在内容上;滑块是部分透明的。</p>"><img loading="lazy" src="https://lruihao.cn/posts/scrollbar-styling/images/24_1719585770.png" alt="带有叠加滚动条的浏览器" srcset="https://lruihao.cn/posts/scrollbar-styling/images/24_1719585770.png?size=small, https://lruihao.cn/posts/scrollbar-styling/images/24_1719585770.png?size=medium 1.5x, https://lruihao.cn/posts/scrollbar-styling/images/24_1719585770.png?size=large 2x" data-title="滚动条会叠加在内容上;滑块是部分透明的。" style="--width: 4000px;--aspect-ratio: 4000 / 2250;background: url(/images/loading.min.svg) no-repeat center;" onload="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}this.dataset.lazyloaded='';" onerror="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}"/></a><figcaption class="image-caption">带有叠加滚动条的浏览器</figcaption> </figure></p> <h4 id="经典滚动条" class="heading-element"><span>2.2.2 经典滚动条</span> <a href="#%e7%bb%8f%e5%85%b8%e6%bb%9a%e5%8a%a8%e6%9d%a1" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h4><p>传统滚动条是放置在专用_滚动条边线_中的滚动条。滚动条边线是内边框边缘与外内边距边缘之间的空间。这些滚动条通常是不透明的(不透明),并会占用相邻内容的某些空间。</p> <p><figure><a class="lightgallery" href="https://lruihao.cn/posts/scrollbar-styling/images/24_1719585901.png?size=large" data-thumbnail="/posts/scrollbar-styling/images/24_1719585901.png?size=small" data-sub-html="<h2>包含传统滚动条的浏览器图示</h2><p>滚动条位于内容旁边的专用区域中;内容的可用宽度会缩小(相对于使用叠加层滚动条时的可用宽度)。</p>"><img loading="lazy" src="https://lruihao.cn/posts/scrollbar-styling/images/24_1719585901.png" alt="包含传统滚动条的浏览器图示" srcset="https://lruihao.cn/posts/scrollbar-styling/images/24_1719585901.png?size=small, https://lruihao.cn/posts/scrollbar-styling/images/24_1719585901.png?size=medium 1.5x, https://lruihao.cn/posts/scrollbar-styling/images/24_1719585901.png?size=large 2x" data-title="滚动条位于内容旁边的专用区域中;内容的可用宽度会缩小(相对于使用叠加层滚动条时的可用宽度)。" style="--width: 4000px;--aspect-ratio: 4000 / 2250;background: url(/images/loading.min.svg) no-repeat center;" onload="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}this.dataset.lazyloaded='';" onerror="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}"/></a><figcaption class="image-caption">包含传统滚动条的浏览器图示</figcaption> </figure></p> <h2 id="scrollbar-color-和-scrollbar-width-属性" class="heading-element"><span>3 scrollbar-color 和 scrollbar-width 属性</span> <a href="#scrollbar-color-%e5%92%8c-scrollbar-width-%e5%b1%9e%e6%80%a7" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><h3 id="scrollbar-color-设置滚动条颜色" class="heading-element"><span>3.1 scrollbar-color 设置滚动条颜色</span> <a href="#scrollbar-color-%e8%ae%be%e7%bd%ae%e6%bb%9a%e5%8a%a8%e6%9d%a1%e9%a2%9c%e8%89%b2" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h3><blockquote><p>参考 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/scrollbar-color"target="_blank" rel="external nofollow noopener noreferrer">https://developer.mozilla.org/zh-CN/docs/Web/CSS/scrollbar-color<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a></p> </blockquote><p>借助 <code>scrollbar-color</code> 属性,可以更改滚动条的配色方案。该属性接受两个 <code>&lt;color&gt;</code> 值。</p> <ul> <li>第一个值用于确定滑块(thumb)的颜色</li> <li>第二个值用于确定要用于轨道(track)的颜色</li> </ul> <p>如需使用操作系统提供的默认呈现方式,请使用 <code>auto</code> 作为其值。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="c">/* 关键字值 */</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-color</span><span class="o">:</span> <span class="nt">auto</span><span class="o">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c">/* &lt;color&gt; 值 */</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-color</span><span class="o">:</span> <span class="nt">rebeccapurple</span> <span class="nt">green</span><span class="o">;</span> <span class="c">/* 两个有效的颜色。 </span></span></span><span class="line"><span class="cl"><span class="c">第一个应用于滚动条的滑块,第二个应用于轨道。 */</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c">/* 全局值 */</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-color</span><span class="o">:</span> <span class="nt">inherit</span><span class="o">;</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-color</span><span class="o">:</span> <span class="nt">initial</span><span class="o">;</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-color</span><span class="o">:</span> <span class="nt">revert</span><span class="o">;</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-color</span><span class="o">:</span> <span class="nt">revert-layer</span><span class="o">;</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-color</span><span class="o">:</span> <span class="nt">unset</span><span class="o">;</span></span></span></code></pre></td></tr></table> </div> </div><p>默认情况下,使用叠加滚动条时,轨迹的颜色不起作用。不过,将鼠标悬停在滚动条上时,系统会显示航迹。</p> <h3 id="scrollbar-width-设置滚动条粗细" class="heading-element"><span>3.2 scrollbar-width 设置滚动条粗细</span> <a href="#scrollbar-width-%e8%ae%be%e7%bd%ae%e6%bb%9a%e5%8a%a8%e6%9d%a1%e7%b2%97%e7%bb%86" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h3><blockquote><p>参考 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/scrollbar-width"target="_blank" rel="external nofollow noopener noreferrer">https://developer.mozilla.org/zh-CN/docs/Web/CSS/scrollbar-width<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a></p> </blockquote><p>借助 <code>scrollbar-width</code> 属性,可以选择较窄的滚动条,甚至可以完全隐藏滚动条而不影响可滚动性。</p> <p>接受的值包括 <code>auto</code>、<code>thin</code> 和 <code>none</code>。</p> <ul> <li><code>auto</code>:平台提供的默认滚动条宽度。</li> <li><code>thin</code>:平台提供的滚动条的细变体,或比默认平台滚动条更细的自定义滚动条。</li> <li><code>none</code>:有效隐藏滚动条。不过,此元素仍然可滚动。</li> </ul> <p>无法使用 <code>&lt;length&gt;</code>(例如 <code>16px</code>)作为 <code>scrollbar-width</code> 的值。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="c">/* 关键字值 */</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-width</span><span class="o">:</span> <span class="nt">auto</span><span class="o">;</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-width</span><span class="o">:</span> <span class="nt">thin</span><span class="o">;</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-width</span><span class="o">:</span> <span class="nt">none</span><span class="o">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c">/* 全局值 */</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-width</span><span class="o">:</span> <span class="nt">inherit</span><span class="o">;</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-width</span><span class="o">:</span> <span class="nt">initial</span><span class="o">;</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-width</span><span class="o">:</span> <span class="nt">revert</span><span class="o">;</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-width</span><span class="o">:</span> <span class="nt">revert-layer</span><span class="o">;</span> </span></span><span class="line"><span class="cl"><span class="nt">scrollbar-width</span><span class="o">:</span> <span class="nt">unset</span><span class="o">;</span></span></span></code></pre></td></tr></table> </div> </div><p>使用叠加滚动条时,仅当你主动滚动可滚动区域时,才会显示滚动条滑块。</p> <h2 id="支持旧版浏览器" class="heading-element"><span>4 支持旧版浏览器</span> <a href="#%e6%94%af%e6%8c%81%e6%97%a7%e7%89%88%e6%b5%8f%e8%a7%88%e5%99%a8" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>为了满足不支持 <code>scrollbar-color</code> 和 <code>scrollbar-width</code> 的浏览器版本,可以同时使用新的 <code>scrollbar-*</code> 和 <code>::-webkit-scrollbar-*</code> 属性。</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span><span class="lnt">30 </span><span class="lnt">31 </span><span class="lnt">32 </span><span class="lnt">33 </span><span class="lnt">34 </span><span class="lnt">35 </span><span class="lnt">36 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="c">/* Modern browsers with `scrollbar-*` support (high priority) */</span> </span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">supports</span> <span class="o">(</span><span class="nt">scrollbar-width</span><span class="o">:</span> <span class="nt">auto</span><span class="o">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="o">*</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">scrollbar-color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">scrollbar</span><span class="o">-</span><span class="n">thumb</span><span class="o">-</span><span class="kc">color</span><span class="p">)</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">scrollbar</span><span class="o">-</span><span class="n">track</span><span class="o">-</span><span class="kc">color</span><span class="p">);;</span> </span></span><span class="line"><span class="cl"> <span class="n">scrollbar-width</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">scrollbar</span><span class="o">-</span><span class="n">width</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c">/* Legacy browsers with `::-webkit-scrollbar-*` support */</span> </span></span><span class="line"><span class="cl"><span class="p">::</span><span class="nd">-webkit-scrollbar</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">height</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">scrollbar</span><span class="o">-</span><span class="n">width</span><span class="o">-</span><span class="n">legacy</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">width</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">scrollbar</span><span class="o">-</span><span class="n">width</span><span class="o">-</span><span class="n">legacy</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="k">overflow</span><span class="p">:</span> <span class="kc">visible</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">::</span><span class="nd">-webkit-scrollbar-button</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">height</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">width</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">::</span><span class="nd">-webkit-scrollbar-corner</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">background-color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">scrollbar</span><span class="o">-</span><span class="n">track</span><span class="o">-</span><span class="kc">color</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">::</span><span class="nd">-webkit-scrollbar-thumb</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">background-color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">scrollbar</span><span class="o">-</span><span class="n">thumb</span><span class="o">-</span><span class="kc">color</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">::</span><span class="nd">-webkit-scrollbar-thumb</span><span class="p">:</span><span class="nd">hover</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">background-color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">scrollbar</span><span class="o">-</span><span class="n">thumb</span><span class="o">-</span><span class="n">hover</span><span class="o">-</span><span class="kc">color</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">::</span><span class="nd">-webkit-scrollbar-track</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">background-color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">scrollbar</span><span class="o">-</span><span class="n">track</span><span class="o">-</span><span class="kc">color</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">::</span><span class="nd">-webkit-scrollbar-thumb</span><span class="o">,</span> </span></span><span class="line"><span class="cl"><span class="p">::</span><span class="nd">-webkit-scrollbar-track</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">background-clip</span><span class="p">:</span> <span class="kc">padding-box</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">border</span><span class="p">:</span> <span class="mi">3</span><span class="kt">px</span> <span class="kc">solid</span> <span class="kc">transparent</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">border-radius</span><span class="p">:</span> <span class="mi">100</span><span class="kt">px</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>请注意,当设置 <code>::-webkit-scrollbar</code> 的 <code>width</code> 或 <code>height</code> 时,系统始终会显示叠加层滚动条,实际上会变为经典滚动条。</p> <p class="ciu-embed" data-feature="css-scrollbar" data-past="2" data-future="1" data-observer="true" data-theme=""></p> <h2 id="总结一下" class="heading-element"><span>5 总结一下</span> <a href="#%e6%80%bb%e7%bb%93%e4%b8%80%e4%b8%8b" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>可以看到,其实就目前 <code>scrollbar-width</code> 而言,其能力还是属于比较鸡肋的。相对正常的样式,仅仅多了一种瘦版样式选择以及提供了无滚动条模式。</p> <p>当然,整个 <code>scrollbar-color</code> 和 <code>scrollbar-width</code> 相较于非标准的 <code>::-webkit-scrollbar</code> 规范已经是非常大的一步跨越。只是其功能的丰富性和全面性还需要等待。</p>

如何实现 VSCode 编辑器窗口边界拖拽类似功能

2024-06-13 21:03:12

<p>边界拖拽调整窗口大小功能是一个很常见的功能,比如浏览器、编辑器等很多场景都有应用,这种功能不仅提高了用户体验,也增强了应用的灵活性。</p> <h2 id="效果演示" class="heading-element"><span>1 效果演示</span> <a href="#%e6%95%88%e6%9e%9c%e6%bc%94%e7%a4%ba" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p><a href="https://lruihao.github.io/vue-el-demo/#/aside-toggle-drag"target="_blank" rel="external nofollow noopener noreferrer">vue-el-demo/#/aside-toggle-drag<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a></p> <h2 id="实现代码" class="heading-element"><span>2 实现代码</span> <a href="#%e5%ae%9e%e7%8e%b0%e4%bb%a3%e7%a0%81" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p><a href="https://github.com/Lruihao/vue-el-demo/blob/main/src/components/AsideToggler/index.vue"target="_blank" rel="external nofollow noopener noreferrer">@/components/AsideToggler<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a></p> <h2 id="实现思路" class="heading-element"><span>3 实现思路</span> <a href="#%e5%ae%9e%e7%8e%b0%e6%80%9d%e8%b7%af" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>怎么说呢,写这篇文章就是想记录一下思路,本来想画个图说明一下的,但是懒得画了,随便说几句吧。</p> <p>实现边界拖拽调整窗口大小功能,主要是通过鼠标按下、移动、松开事件来实现的,主要思路如下:</p> <ol> <li>在 <code>mousedown</code> 事件中,我们记录下鼠标的初始位置和元素的初始宽度。</li> <li>在 <code>mousemove</code> 事件中,我们根据鼠标的新位置计算出新的宽度,并使用 <code>clamp()</code> 函数将其限制在最小宽度和最大宽度之间。</li> <li>同时,我们还需要根据鼠标位置的变化,动态更新鼠标样式,以提示用户当前的拖拽状态。</li> <li>在 <code>mouseup</code> 事件中,我们清除之前设置的事件监听器,并恢复鼠标样式。</li> </ol> <p>实现过程中,有两个比较巧妙的点:</p> <ul> <li>计算宽度的时候,没有使用 JS 计算,而是直接使用了 CSS 的 <code>clamp()</code> 函数,一目了然。</li> <li>为了鼠标移动到可拖拽边界时显示一条蓝色的线,但是又不想改变元素的宽度,所以 <code>resize-bar</code> 元素的使用了 <code>position: absolute</code>,并且设置了 <code>translateX(-50%)</code> 来让其居中显示。当鼠标移入时,通过线性渐变的背景色巧妙地来实现蓝色线条的效果。</li> </ul> <p>正是因为第二点的实现方式,使得在边界线左右两侧都能拖拽,这一点是优于 VSCode 的,因为 VSCode 只能在左侧拖拽 😂。</p>

架构之基:从根儿上了解设计原则

2024-06-09 02:24:31

<p>本文节选自 <strong>奔波儿灞取经</strong> 的《<a href="https://juejin.cn/book/7196580339181944872"target="_blank" rel="external nofollow noopener noreferrer">程序员的必修课<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a>》,文中的“我”指原作者奔波儿灞取经。</p> <hr> <blockquote><p>设计模式不是必须的,但是如果你的代码是“非一次性的”,那么它就是必须的。</p> </blockquote><p>对于大多数开发者来说,代码都是需要维护的,而不是写一遍就放起来不管了。既然需要维护,就要不断地读读改改,那就不是一次性的,那么怎么让他“读读改改”起来方便些呢?嗯,<strong>设计模式</strong>!</p> <p>设计模式切记不要死记硬背,不要生搬硬套,否则不如不学。而且不要一上来就说:要用 xxx 模式!需求还没出,你猴急个锤子,难不成是你刚学会这个模式,想拿来练练手,就想把它硬塞到需求里去吗?</p> <p>那么,如果需求已经出了呢?也别急,先想想,想好了怎么写,选择哪种设计模式;如果没有合适的,套不进去,那就别套了,只要方便维护,就是好的设计,不一定非要去套现有的设计模式。</p> <p>当然,要想正确地使用设计模式,还是先得透彻地了解了它们。因此,接下来我们就先来了解下设计模式的“祖宗”:<strong>六大设计原则</strong>。</p> <h2 id="单一职责原则srp" class="heading-element"><span>1 单一职责原则(SRP)</span> <a href="#%e5%8d%95%e4%b8%80%e8%81%8c%e8%b4%a3%e5%8e%9f%e5%88%99srp" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><blockquote><p>单一职责原则(Single Responsibility Principle,简称 SRP):一个类只干一件事。</p> </blockquote><p>可以看到,单一职责的核心就是:<strong>只做一件事</strong>。不过,关键点就是这个“事”的范围不好定义。</p> <p>比如,写一个音乐播放器,一个类负责播放,一个类负责停止,这也满足单一职责。但是,这个职责太小了。那么,如果把播放、停止、甚至下载歌曲,都塞进一个类里面呢,那就太大了。所以,职责的范围很重要,只要确定好了这个范围,那这个原则就已经实现了 90%。</p> <p>那么,这个范围怎么确定呢?我们可以这么理解:<strong>一些相关的、关联性比较强的,就把它们当作同一种职责,放到一个单独的类(文件)里</strong>。</p> <p>那么,怎么确定是否相关呢?看需求!这个只能看需求,没有别的方法。如果需求没有明确,那么我们就要联系现实来决定,毕竟程序的本质就是模拟现实。</p> <p>比如,我在 2015 年实习的时候,IBM 公司有个考勤系统,需要添加一个指纹打卡功能。需求是这样的:<code>部门主管以下的员工可以用指纹来打卡</code>。</p> <p>那么,这个“打卡功能”是属于员工的,是属于打卡器的?换句话说,这个打卡的函数,是写在员工类里面呢,还是写在打卡器类里面呢?需求没说啊。</p> <p>那么,我们就联系现实来决定。</p> <p>在现实生活中,应该是一个打卡器放在门口,员工向打卡器录入指纹,来进行打卡,说白了就是:“员工使用打卡器来打卡”,也就是:“员工使用打卡器”“打卡器打卡”,所以,打卡功能是打卡器的,员工只是使用它的这个功能。所以,这个函数应该定义在打卡器里面,员工调用打卡器的这个函数来进行打卡。</p> <p>如果有人不爽,非要定义在员工类里面呢?你可以这么干。不过,后来需求改变成:非员工,比如保洁人员,也需要每天打卡签到。这时候,那位非常有个性有特色的人,估计脑瓜子嗡嗡的了吧。</p> <p><strong>单一职责不仅可以用在类(文件)里面,也可以用在函数里面。</strong></p> <p>比如,现在需要写一个校验函数,校验用户的性别和年龄,必须是 18 岁及以上的男性才有资格,很简单的我们可以这么写:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">checkSexAndAge</span><span class="p">(</span><span class="kt">boolean</span><span class="w"> </span><span class="n">isMan</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">age</span><span class="p">){</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">isMan</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">age</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">18</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>使用:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">login</span><span class="p">(){</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="n">checkSexAndAge</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span><span class="w"> </span><span class="n">17</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">tips</span><span class="p">(</span><span class="s">&#34;不是 18 岁以上的男性&#34;</span><span class="p">)</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>这里有人有意见了,说这样写不太好,因为每个校验的地方都要自己弹出提示,这样就是很多重复的代码,所以提示这个逻辑应该放在<code>checkSexAndAge()</code>这个函数里面去,也就是下面这样:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">checkSexAndAge</span><span class="p">(</span><span class="kt">boolean</span><span class="w"> </span><span class="n">isMan</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">age</span><span class="p">){</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="n">isMan</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">age</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">18</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">tips</span><span class="p">(</span><span class="s">&#34;不是 18 岁以上的男性&#34;</span><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>这样简直美滋滋,任何时候只要直接调<code>checkSexAndAge()</code>就行,判断了还自带提示。这在目前当然是完美的,虽然方法名不太合适。</p> <p>如果有一天,我们的需求变成了:年龄不满足就开启未成年人保护模式,不需要弹出提示。我们直接删除<code>tips()</code>这个调用吗?这样不太好,如果别的地方也调用了这个方法,并且需要提示,就完了。所以我们应该有两个方法:方法 A 只检测,方法 B 使用 A 的检测结果并弹出提示。代码如下所示:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 判断加提示</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">checkAndTips</span><span class="p">(</span><span class="kt">boolean</span><span class="w"> </span><span class="n">isMan</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">age</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">checkSexAndAge</span><span class="p">(</span><span class="n">isMan</span><span class="p">,</span><span class="w"> </span><span class="n">age</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">tips</span><span class="p">(</span><span class="s">&#34;不是 18 岁以上的男性&#34;</span><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// 新方法,只做逻辑判断</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">boolean</span><span class="w"> </span><span class="nf">checkSexAndAge</span><span class="p">(</span><span class="kt">boolean</span><span class="w"> </span><span class="n">isMan</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">age</span><span class="p">){</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">isMan</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">age</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">18</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>这里我们只抽离了一个方法,但是却反映出了单一职责的好处:职责越单一,因为修改而引起的问题就越少。换句话说就是:<strong>需求的粒度跟单一职责的优势成正比,需求越详细,越能看出单一职责的好处</strong>。所以我们要尽量避免大方法、大类、大模块,因为一个类越大,涉及的东西就越多,用到它的地方就越多,那么这个类就不能轻易修改,因为一旦修改,涉及的地方就越多,就越危险,所以我们一定要尽量避免。其实 MVC 就是一个宏观的、大的单一职责思想。</p> <blockquote><p>单一职责不仅适用于类和文件,还适用于函数、模块等,这是一种思想,一定要掌握。</p> </blockquote><h2 id="里氏置换原则lsp" class="heading-element"><span>2 里氏置换原则(LSP)</span> <a href="#%e9%87%8c%e6%b0%8f%e7%bd%ae%e6%8d%a2%e5%8e%9f%e5%88%99lsp" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><blockquote><p>里氏置换原则(Liskov Substitution Principle,简称 LSP):凡是使用基类的地方都必须能透明地使用子类。</p> </blockquote><p>用人话说就是:用子类替换父类不会改变原有逻辑。众所周知,面向对象有三大基本原则:封装、继承和多态。子类本来就继承了父类,用到父类的地方替换成子类肯定没问题啊,这个原则不是废话吗,不一定!因为子类有自己的特色,也就是多态,如果这个特色太特色的话,就不适合了。</p> <p>比如,“我用电脑工作和游戏”,改成“我用苹果电脑工作,用联想电脑打游戏”,没问题!</p> <p>但是如果“我开车上班,坐车下班”,改成“我开玩具车上班,坐遥控车下班”,这个可能吗?</p> <p>但是,玩具车和遥控车也是“车”的子类啊,它俩也是车啊。</p> <p>那么这个问题出在哪里呢?明明所有的定义都是 OK 的。这是因为<strong>子类太特色了</strong>。</p> <p>我们定义的<strong>车</strong>,其出发点是“能跑”,也就是说,只要能跑的都是“车”,都是它的子类,所以,玩具车和遥控车都能跑,也都是车的子类。但是,车都能载人吗?猛一看,都能!仔细一想,玩具车不能!所以,我们上述 Demo 中用到的是车的“载人”功能,而不是车的“能跑”功能,所以,玩具车就不合适了。</p> <p>那么,怎么改呢?有如下两种方法:</p> <ul> <li>提取一个可载人的接口 <code>interface IManned</code>,明确表示哪些车可以载人;</li> <li>提取一个二级父类 <code>class MannedCar</code>,表示该类车可以载人。</li> </ul> <p>公共点就是:<strong>把“可载人”这个点明确出来</strong>。</p> <p>所以,里氏置换更简洁的说法就是:<strong>子类可以有自己的特色,但是不能太反常,如果子类的特色跟父类差太多,那么就应该细化父类或者剥离接口</strong>。</p> <blockquote><p>可以看到,里氏置换原则就是对继承的校验,不恰当的继承关系就不满足里氏置换原则,所以,如果我们无法确定某两个类之间是否应该用继承关系时,就可以套用里氏置换原则来校验下。</p> </blockquote><h2 id="依赖倒置原则dip" class="heading-element"><span>3 依赖倒置原则(DIP)</span> <a href="#%e4%be%9d%e8%b5%96%e5%80%92%e7%bd%ae%e5%8e%9f%e5%88%99dip" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><blockquote><p>依赖倒置原则(Dipendence Inversion Principle,简称 DIP):面向接口编程或面向抽象编程。</p> </blockquote><p>依赖倒置的官方定义:高层不应该依赖底层,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。其实狭义的说就是:面向接口编程,广义的说就是:面向抽象编程。也就是说,我们在使用类的时候,优先考虑使用抽象类或接口。具体一点就是:成员变量、函数参数和返回值都尽量定义成接口。</p> <p>为什么要这么干呢?这么干有什么好处呢?</p> <p>我们知道,接口都是抽象的,抽象的就是不确定的,不确定的就是可变的。而我们的大部分代码都是“非一次性的”,也都是需要改变的,所以,接口正合适。</p> <p>换句话说,<strong>接口就是具有某种功能的某种东西</strong>,是什么我不管,只要具有这种功能就行,而我们需要的,也就是具有这种功能的东西。</p> <p>比如,我需要给手机充个电,我需要的是一个“能充电的东西”,而你却对外说:“我需要个充电宝!”如果有人没有充电宝,只有电源呢,他就不认你了。在这里,你把我需要的东西<strong>具象化</strong>了,也就是把范围缩小了,范围越小越精确,就越不容易改变,这明显是不对的。</p> <p>再比如,现在我要提供一个音乐播放器,我直接使用移动端的 <code>MediaPlayer</code>,很容易就写出了如下代码:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">MediaPlayer</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">play</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">path</span><span class="p">)</span><span class="w"> </span><span class="p">{}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">stop</span><span class="p">(){}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">pause</span><span class="p">(){}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">resume</span><span class="p">(){}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>三分钟就写完了,使用方直接调用:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">User</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="n">MediaPlayer</span><span class="w"> </span><span class="n">mediaPlayer</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">play</span><span class="p">(){</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">mediaplayer</span><span class="p">.</span><span class="na">play</span><span class="p">(</span><span class="s">&#34;xxx&#34;</span><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>完事之后某一天,主管又问:“咱们的播放器不好用,能用那个开源的吗?”</p> <p>当然可以,于是就去改,但是发现,要改的地方太多了,我不但要改<code>MedidPlayer</code>这个类,甚至调用我播放器的人也需要改他的<code>User</code>类,我在别人眼里的段位又低了!</p> <p>这时候就应该反思了,其实<code>User</code>这个类,不在乎你的播放器是怎么写的,它只关心能不能播放、停止、暂停、恢复,说白了,它要的是一个<strong>具有这种功能的某种东西</strong>,而不是具有这种功能的这种东西。</p> <p>好,上接口!</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">interface</span> <span class="nc">IPlayer</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">play</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">path</span><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">stop</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">pause</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">resume</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p><code>User</code>使用:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">User</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="n">IPlayer</span><span class="w"> </span><span class="n">player</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">play</span><span class="p">(){</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">player</span><span class="p">.</span><span class="na">play</span><span class="p">(</span><span class="s">&#34;xxx&#34;</span><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>此时<code>User</code>只依赖于<code>IPlayer</code>,而不依赖具体的实现。不管你是啥,只要具有播放器的功能就行,后面不管你怎么改变<code>IPlayer</code>的实现,<code>User</code>都不需要改变。</p> <p>所以,我们可以看到面向接口的好处:<strong>低耦合,易拓展</strong>。因为接口是抽象的,依赖接口就是依赖抽象,不依赖细节,所以实现的细节怎么改都对我无影响,所以耦合就低;又因为接口是顶层的,就更容易拓展下层的细节实现。</p> <h2 id="接口隔离原则isp" class="heading-element"><span>4 接口隔离原则(ISP)</span> <a href="#%e6%8e%a5%e5%8f%a3%e9%9a%94%e7%a6%bb%e5%8e%9f%e5%88%99isp" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><blockquote><p>接口隔离原则(Interface Segregation Principle,简称 ISP):接口尽量小,尽量单一,说白了就是接口粒度要细。</p> </blockquote><p>接口隔离要求接口的功能要单一,这听起来怎么就是单一职责原则呢,它们有区别吗?</p> <p>有!</p> <p>单一职责原则针对的是“职责”,说白了就是功能块,一个职责可能有多个功能;接口隔离原则针对的是“功能”,也就是一个接口只负责一个“功能”,比如,老师的职责是讲课和改作业,如果用单一职责原则就是一个接口里面包含了讲课和改作业这两个方法;如果用接口隔离原则就是两个接口,一个讲课的接口和一个改作业的接口。换句话说就是:接口隔离原则是单一职责的单一职责原则。</p> <p>举个例子,还是音乐播放器,我们定义了一个接口:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">interface</span> <span class="nc">IPlayer</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//开始</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">play</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">url</span><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//停止</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">stop</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//暂停</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">pause</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//复原</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">resume</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//获取歌曲时长</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getSongTime</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>这正是单一职责原则,因为这个接口只定义了音乐播放相关的东西,但是却不满足接口隔离原则,因为一个接口干了多件事,假如我们现在有个歌曲展示器<code>SongDisplayer</code>,只需要展示歌曲的时长,也就是只需要<code>getSongTime()</code>这个函数,我们让它直接实现<code>IPlayer</code>接口吗?肯定不行!因为里面的其他函数是不需要的,也不应该有的。这就要用到接口隔离原则了,我们直接将<code>IPlayer</code>接口再进行拆分,如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">//音乐播放器就仅限于对播放的控制</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">interface</span> <span class="nc">IPlayer</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//开始</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">play</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">path</span><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//停止</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">stop</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//暂停</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">pause</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//复原</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">resume</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">...</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">//歌曲展示器就仅限于对歌曲信息的展示</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">interface</span> <span class="nc">ISongDisplayer</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//获取歌曲时长</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getSongTime</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//获取歌曲名字</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getSongName</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//其他</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">...</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>这样拆分后,我们的播放器就同时实现上面两个接口,而歌曲展示器只需要实现<code>ISongDisplayer</code>即可。</p> <p>但是,我们根本不知道将来会出什么样的需求,怎么能提前预测并做好接口隔离呢?</p> <p>不需要提前做!因为接口隔离更多时候是个后置操作,说白了,更多时候是在问题发生的时候再去拆接口,所以是个后置操作,就像我们上面的修改,也没费多大劲,顶多就是多写一个接口,复制一部分代码,修改几个实现关系而已,根本没动业务上的代码,所以不必纠结,大多时候我们保证单一职责即可。</p> <blockquote><p>总之一句话:接口要尽量小,尽量单一。</p> </blockquote><h2 id="最少知识原则lkp" class="heading-element"><span>5 最少知识原则(LKP)</span> <a href="#%e6%9c%80%e5%b0%91%e7%9f%a5%e8%af%86%e5%8e%9f%e5%88%99lkp" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><blockquote><p>最少知识原则(Least Knowledge Principle,简称 LKP),也叫迪米特法则(LOD):一个对象应该对其他对象有最少的了解,说白了就是,只关联自己需要的。</p> </blockquote><p>就像语文老师,只关心语文成绩即可,非要关心数学,怪不得头发都掉光了。</p> <p>废话不说,我们来看个 Demo,又是那个音乐播放器,原本应该是这样的:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">interface</span> <span class="nc">IPlayer</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">play</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">path</span><span class="p">)</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">....</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">class</span> <span class="nc">User</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">....</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">play</span><span class="p">(){</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">player</span><span class="p">.</span><span class="na">play</span><span class="p">(</span><span class="n">song</span><span class="p">.</span><span class="na">path</span><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">....</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">class</span> <span class="nc">Song</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">path</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">....</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>可以看到,播放时,只需要一个<code>path</code>即可。但是有人聪明,我直接把<code>Song</code>给他传过去不行吗?这样后面万一需要<code>Song</code>里面的其他变量,比如<code>name</code>啥的,我也不用改函数了,好有道理啊!</p> <p>突然有一天,要求可以播放用户通过聊天发送过来的歌曲,这个歌曲没有名字,点击就下载到本地,只有一个路径了,这个时候你怎么办呢?你当然可以用这个路径去创建一个<code>Song</code>然后丢进去,但是这样绕了一圈不就增加了复杂度吗?再万一将来某天要修改<code>Song</code>这个类呢,你的播放器也跟着修改了。</p> <p>其实,播放器需要的只是一个播放的路径,至于其他的,它根本不关心。如果真的需要,你再提供,但也只需要提供它需要的,不要有任何附加内容。否则,一旦那些附加内容变化了,也间接导致播放器自身的变化,这是不应该的。</p> <blockquote><p>我们应该只关联自己直接用到的,而不关联那些不需要的,如此一来,那些发生在我们关联范围外的事,就不会引起我们的任何改变,这样就大大提升了代码的健壮性。</p> </blockquote><h2 id="开放闭合原则ocp" class="heading-element"><span>6 开放闭合原则(OCP)</span> <a href="#%e5%bc%80%e6%94%be%e9%97%ad%e5%90%88%e5%8e%9f%e5%88%99ocp" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><blockquote><p>开放闭合原则(Open Close Principle,简称 OCP):一个类应该对扩展开放,对修改关闭。换句话说就是:应该多扩展代码,少修改代码。</p> </blockquote><p>开闭原则是<strong>最理想</strong>的原则,是所有设计模式的最终目标,基本不可能实现。它要求我们的任何改动都不修改老代码,而只添加新代码,这样就不会对老逻辑有任何影响,从而使得代码更加安全。</p> <p>有人说,我们的代码不是一次性的,肯定是要修改的,怎么可能不修改呢?没错,肯定是需要修改的,但是合理运用开闭原则可以做到少修改,改得越少风险越小。</p> <p>举个例子,比如我在面试百度的时候,要手写一个计算器,只需要支持简单的加减法就行,如下:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span><span class="lnt">9 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Calculator</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">calculate</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">left</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">right</span><span class="p">,</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">option</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//加法</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="s">&#34;+&#34;</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">option</span><span class="p">))</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">left</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">right</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//减法</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="s">&#34;-&#34;</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">option</span><span class="p">))</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">left</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">right</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">&#34;不支持的运算&#34;</span><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>代码简单粗暴,直接使用<code>if</code>判断就完事。但是,如果将来要支持其他运算呢?嗯,继续添加<code>if</code>分支?可以,但是不太好,谁能保证你下次添加别的运算符的时候,不会手残改了别的运算呢?那么,我们能不能将新的运算不放在这个类里面呢?可以!</p> <p>我们可以将每个运算定义成一个单独的类型,后面新增其他运算,只需要新加一个类就可以了。我们知道,基本的数学运算都是需要两个操作数和一个运算符的,我们可以定义一个公有的父类,来保存操作数和运算符。</p> <p>定义公共父类:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span><span class="lnt">30 </span><span class="lnt">31 </span><span class="lnt">32 </span><span class="lnt">33 </span><span class="lnt">34 </span><span class="lnt">35 </span><span class="lnt">36 </span><span class="lnt">37 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">abstract</span><span class="w"> </span><span class="n">Calculator</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 左操作数</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">protected</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">leftOpt</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 右操作数</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">protected</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">rightOpt</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 操作符</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">protected</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">operator</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 设置左操作数</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">setLeftOpt</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">leftOpt</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="na">leftOpt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">leftOpt</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 设置右操作数</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">setRightOpt</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">rightOpt</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="na">rightOpt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rightOpt</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 计算,提供一个模板函数,供子类实现</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">protected</span><span class="w"> </span><span class="kd">abstract</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">calculate</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 对外公开的获取结果的 Api</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getResult</span><span class="p">(){</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 计算结果</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">calculate</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 清空操作数</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">clear</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 返回结果</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">retrun</span><span class="w"> </span><span class="n">result</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//清空操作数</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">clear</span><span class="p">(){</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">leftOpt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">rightOpt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>加法器:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">PlusCalculator</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">Calculator</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">OPERATOR</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;+&#34;</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="nf">PlusCalculator</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">super</span><span class="p">();</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="na">operator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">OPERATOR</span><span class="p">;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// 加法</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nd">@Override</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">calculate</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">String</span><span class="p">.</span><span class="na">valueOf</span><span class="p">(</span><span class="n">Integer</span><span class="p">.</span><span class="na">parseInt</span><span class="p">(</span><span class="n">leftOpt</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Integer</span><span class="p">.</span><span class="na">parseInt</span><span class="p">(</span><span class="n">rightOpt</span><span class="p">));</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>减法器:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">public</span> <span class="kr">class</span> <span class="nx">SubCalculator</span> <span class="kr">extends</span> <span class="nx">Calculator</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kr">public</span> <span class="kr">static</span> <span class="nb">String</span> <span class="nx">OPERATOR</span> <span class="o">=</span> <span class="s2">&#34;-&#34;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kr">public</span> <span class="nx">SubCalculator() {</span> </span></span><span class="line"><span class="cl"> <span class="kr">super</span><span class="p">();</span> </span></span><span class="line"><span class="cl"> <span class="k">this</span><span class="p">.</span><span class="nx">operator</span> <span class="o">=</span> <span class="nx">OPERATOR</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// 减法 </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kd">@Override</span> </span></span><span class="line"><span class="cl"> <span class="kr">public</span> <span class="nb">String</span> <span class="nx">calculate() {</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nb">String</span><span class="p">.</span><span class="nx">valueOf</span><span class="p">(</span><span class="nx">Integer</span><span class="p">.</span><span class="nb">parseInt</span><span class="p">(</span><span class="nx">leftOpt</span><span class="p">)</span> <span class="o">-</span> <span class="nx">Integer</span><span class="p">.</span><span class="nb">parseInt</span><span class="p">(</span><span class="nx">rightOpt</span><span class="p">));</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>这里我们为不同的运算符提供了不同的实现类,每个类只负责自己的计算逻辑,如果将来有其他新运算加入,我们直接再添加一个新的类即可,完全不需要修改其他类的代码。</p> <p>而且我们可以看到,开闭原则中用到了单一职责(每个类只做自己的运算),还用到了最少知识(每个类只关心自己的操作数和运算符),其实就是一句话:<strong>越单纯,越干净,越好!</strong> 因为这样自己的责任就越少,就越不容易被牵连,也就越稳定,越安全。</p> <h2 id="设计模式" class="heading-element"><span>7 设计模式</span> <a href="#%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><blockquote><p>该小节非原文内容,仅为个人补充。</p> </blockquote><p>在掘金上有一个不错的专栏<a href="https://juejin.cn/column/7069912176978296839"target="_blank" rel="external nofollow noopener noreferrer">「手撕设计模式」<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a>,里面详细介绍了 23 种设计模式中的好几种,可以作为进一步学习的参考。</p> <h2 id="总结" class="heading-element"><span>8 总结</span> <a href="#%e6%80%bb%e7%bb%93" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>本节我们从宏观层面讲解了六大设计原则,这是 23 种设计模式的祖宗,或者说:<strong>设计模式就是这六大设计原则的具体实现,六大设计原则就是设计模式的抽象</strong>。</p> <p>对于设计模式,我认为正确的学习顺序是:</p> <ol> <li>学习设计原则,这是对设计思想的宏观认识。</li> <li>学习设计模式,这是对设计思想的具体认识。</li> <li>再学习设计原则,这是对设计思想的自我抽象。</li> </ol> <p>这就像我们看书的时候,先看目录,对整本书有个宏观的认识;然后仔细看每一章节,对每个模块进行具体了解;最后,也是最难的一点,就是:用自己的语言对整本书进行整体概括,然后尝试列出目录,这是对整本书的自我升华,或者叫自我抽象,这样,我们才能读到书的精髓。学习设计模式亦是如此,我们切记不要死记硬背,不要生搬硬套,不刻意设计的设计才是最好的设计。</p>

怎么生成暗黑模式和明亮模式的 SVG 图片?

2024-06-06 22:13:26

<p>在做博客顶部栏下落奶油图的时候,就在想怎么适配暗黑模式和明亮模式呢?本文将记录两个思路。</p> <h2 id="方案一通过模板生成两张图片" class="heading-element"><span>1 方案一:通过模板生成两张图片</span> <a href="#%e6%96%b9%e6%a1%88%e4%b8%80%e9%80%9a%e8%bf%87%e6%a8%a1%e6%9d%bf%e7%94%9f%e6%88%90%e4%b8%a4%e5%bc%a0%e5%9b%be%e7%89%87" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>这是最容易想到的,也是我最初的想法,通过两张图片来实现暗黑模式和明亮模式的切换。</p> <p>假设我们已经有了两张图片,<code>drop.min.svg</code> 和 <code>drop-dark.min.svg</code>,那么我们可以通过 CSS 来实现切换:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="nn">#header-desktop</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">&amp;</span><span class="nd">::after</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">background-image</span><span class="o">:</span> <span class="sx">url(/images/drop.min.svg)</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">&#39;dark&#39;</span><span class="o">]</span> <span class="k">&amp;</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">background-image</span><span class="o">:</span> <span class="sx">url(/images/drop-dark.min.svg)</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>再创建一个模板文件 <code>drop.template.svg</code>:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$color</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">cond</span><span class="w"> </span><span class="na">.isDark</span><span class="w"> </span><span class="s">&#34;#252627&#34;</span><span class="w"> </span><span class="s">&#34;#e6e5f8&#34;</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">svg</span> <span class="na">viewBox</span><span class="o">=</span><span class="s">&#34;0 0 778.95 302.64&#34;</span> <span class="na">xmlns</span><span class="o">=</span><span class="s">&#34;http://www.w3.org/2000/svg&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">path</span> </span></span><span class="line"><span class="cl"> <span class="na">d</span><span class="o">=</span><span class="s">&#34;m28 14.56h778.71c-8.44 1.45-18.8 3-29.06 5-13.35 2.63-25.55 8.28-37.25 15-9.4 5.44-15.62 13.66-19.51 23.52-8.79 22.33-10 45.36-6 68.89 2.21 13.09 4.06 26.26 5.47 39.45a36.37 36.37 0 0 1 -1.59 13.5c-1.76 6.12-6.65 9.46-12 9.4s-9.52-3.32-11.77-9.32c-3.27-8.7-3.39-17.75-2-26.7 3-18.85 5.71-37.7 4.73-56.86a46.33 46.33 0 0 0 -1.6-9.56c-1.36-5.06-3.63-9.74-9.74-10s-10.86 2.71-13.77 8.06c-5 9.18-5.52 19.16-4.79 29.32.76 10.6 2 21.21 2 31.81 0 5.9-1.82 11.95-3.68 17.65s-6.11 8.1-11.52 7.92c-5-.17-8.76-3.08-10.72-8.53-2.42-6.69-1.42-13.44 0-20.13 2.61-12 5.78-23.85 7.89-35.92 2.43-13.92 3.11-27.94-2.19-41.55-4.23-10.85-14.09-12.81-21.12-3.56a61.82 61.82 0 0 1 -18.93 16.39c-3.95 2.27-7.46 5.32-11.13 8.07-7.31 5.46-13.69 4.33-18.88-3.22-5.52-8-9-16.89-11.55-26.27a56.4 56.4 0 0 0 -4.49-12c-3.1-5.81-9.92-6.05-12.45-.07a62 62 0 0 0 -4.39 18.75c-1.64 19.65 3.05 38.79 5.56 58.1.68 5.24.44 10.61.49 15.92a10.44 10.44 0 0 1 -.83 3.43c-1.68 4.76-5.19 7.58-9 7.29s-7.57-4-7.63-9.07c-.09-7.8.77-15.61 1-23.42s.69-15.38.32-23c-.19-3.87-1.42-8.17-6.18-9.32a8.82 8.82 0 0 0 -10.4 5.05c-3 6.5-6.06 13.34-7.05 20.33-3.34 23.67-2.93 47.47-.37 71.23 2.06 19.08 4.48 38.13 6.07 57.25 1.1 13.29.43 26.63-3.49 39.55a30.52 30.52 0 0 1 -3.69 8c-4.75 7.12-13 7.49-18.15.65a28.17 28.17 0 0 1 -4.42-9.55c-4.22-15.75-3.16-31.62-.46-47.48 4.92-29 11.36-57.79 9.32-87.49-.87-12.69-3.56-24.86-11.22-35.45-5.59-7.73-12.08-10.24-20.8-6.34-8 3.55-15.42 4.52-24 2.25-8.4-2.23-16.74 3.72-20.65 13.16-4.89 11.8-4.15 24-2.15 36.22 1.35 8.29 3.32 16.52 1.46 25-1.65 7.55-5.75 12.08-11.15 11.76s-9.66-5.63-9-13.27c.81-9.36 2.87-18.62 4.68-27.87 2-10.34 4.32-20.77 2-31.18-1.3-5.86-4.33-11.49-7.4-16.74-1.31-2.25-4.51-3.84-7.19-4.76-6.55-2.24-11.76 1.36-11.86 8.29-.07 4.84.91 9.68 1.25 14.54a73 73 0 0 1 .33 11.91c-.55 5.75-4.3 9.55-9.36 10.42-4.85.83-10.69-2-12.48-7.22a27 27 0 0 1 -1.22-12c1.62-11.68 4.51-23.21 5.7-34.92.64-6.26-.52-13-2.12-19.14-1.86-7.18-9.18-10.05-16-7a53.2 53.2 0 0 0 -8.63 5.33c-2.77 2-5.25 4.35-8 6.35-7 5.13-11.33 4.93-18.63.1-3.4-2.25-7.09-4.61-11-5.49-14.83-3.34-25.13 5.83-25.77 22.26-.71 18.29.73 36.32 5.61 54 1.64 6 .52 11.74-2.34 17.18-3.89 7.4-9.56 10.74-15.92 9.39-5.95-1.27-11.29-7.9-11-15.5.35-9 2-17.87 2.82-26.83.58-6.59 1.46-13.31.8-19.83-1-10.3-8.28-14.9-18.33-12.24-14.63 3.87-24.42 11.86-27.78 28-4.53 21.8-3.6 43.53-2.49 65.36 1.36 26.85 9.17 52.61 13.89 78.9 2.22 12.38 3.17 25.1-1.34 37.26-1.46 3.94-4.1 8.12-7.4 10.58-10 7.42-22.1 2.86-23.9-9.4a110.27 110.27 0 0 1 -.57-30c3.9-29.74 9.37-59.37 8-89.47-1-22.07-1.74-44.34-8.78-65.68-3.29-10-8.3-18.25-18-23.72-5.32-3-9.91-7.88-13.77-12.76-4-5.1-8.66-8-15-7.87a52.19 52.19 0 0 0 -11 1c-14 3.26-23.37-3.18-30.62-14.24-2.06-3.41-3.85-7.19-6.43-10.41-15-18.79-35-29.58-58.16-34.93-1.69-.39-3.39-.76-5.08-1.13z&#34;</span> </span></span><span class="line"><span class="cl"> <span class="na">transform</span><span class="o">=</span><span class="s">&#34;translate(-27.76 -14.56)&#34;</span> </span></span><span class="line"><span class="cl"> <span class="p">/&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span></span></span></code></pre></td></tr></table> </div> </div><p>然后,我们可以通过 Hugo 的模板引擎来生成两张图片:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$template</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">resources</span><span class="na">.Get</span><span class="w"> </span><span class="s">&#34;images/drop.template.svg&#34;</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$resource</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">(</span><span class="nx">$template</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">resources</span><span class="na">.ExecuteAsTemplate</span><span class="w"> </span><span class="s">&#34;images/drop.svg&#34;</span><span class="w"> </span><span class="o">(</span><span class="nx">dict</span><span class="w"> </span><span class="s">&#34;isDark&#34;</span><span class="w"> </span><span class="k">false</span><span class="o">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">minify</span><span class="o">)</span><span class="na">.RelPermalink</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$resourceDark</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">(</span><span class="nx">$template</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">resources</span><span class="na">.ExecuteAsTemplate</span><span class="w"> </span><span class="s">&#34;images/drop-dark.svg&#34;</span><span class="w"> </span><span class="o">(</span><span class="nx">dict</span><span class="w"> </span><span class="s">&#34;isDark&#34;</span><span class="w"> </span><span class="k">true</span><span class="o">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">minify</span><span class="o">)</span><span class="na">.RelPermalink</span><span class="w"> </span><span class="cp">-}}</span></span></span></code></pre></td></tr></table> </div> </div><p>这样,我们就得到了暗黑模式和明亮模式的两张图片,正如现在博客所看到的一样。</p> <h2 id="方案二通过-css-实现响应式" class="heading-element"><span>2 方案二:通过 CSS 实现响应式</span> <a href="#%e6%96%b9%e6%a1%88%e4%ba%8c%e9%80%9a%e8%bf%87-css-%e5%ae%9e%e7%8e%b0%e5%93%8d%e5%ba%94%e5%bc%8f" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>然后,我在想这两张图除了背景色,其他都一毛一样,能不能通过 CSS 来实现呢?</p> <p>一番尝试过后,答案是肯定的。</p> <p>假设我们只有一张图片,<code>drop.responsive.svg</code>,还是通过 CSS 来实现切换:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="lnt">6 </span><span class="lnt">7 </span><span class="lnt">8 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="nn">#header-desktop</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">&amp;</span><span class="nd">::after</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">background-image</span><span class="o">:</span> <span class="sx">url(/images/drop.responsive.svg)</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">&#39;dark&#39;</span><span class="o">]</span> <span class="k">&amp;</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">background-image</span><span class="o">:</span> <span class="sx">url(/images/drop.responsive.svg#drop-dark-only)</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>再创建一个 SVG 文件 <code>drop.responsive.svg</code>:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-svg" data-lang="svg"><span class="line"><span class="cl"><span class="nt">&lt;svg</span> <span class="na">id=</span><span class="s">&#34;drop-dark-only&#34;</span> <span class="na">viewBox=</span><span class="s">&#34;0 0 778.95 302.64&#34;</span> <span class="na">xmlns=</span><span class="s">&#34;http://www.w3.org/2000/svg&#34;</span><span class="nt">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="nt">&lt;style&gt;</span> </span></span><span class="line"><span class="cl"> #header-drop { </span></span><span class="line"><span class="cl"> fill: #e6e5f8; </span></span><span class="line"><span class="cl"> } </span></span><span class="line"><span class="cl"> #drop-dark-only:target #header-drop { </span></span><span class="line"><span class="cl"> fill: #252627; </span></span><span class="line"><span class="cl"> } </span></span><span class="line"><span class="cl"> <span class="nt">&lt;/style&gt;</span> </span></span><span class="line"><span class="cl"> <span class="nt">&lt;path</span> </span></span><span class="line"><span class="cl"> <span class="na">id=</span><span class="s">&#34;header-drop&#34;</span> </span></span><span class="line"><span class="cl"> <span class="na">d=</span><span class="s">&#34;m28 14.56h778.71c-8.44 1.45-18.8 3-29.06 5-13.35 2.63-25.55 8.28-37.25 15-9.4 5.44-15.62 13.66-19.51 23.52-8.79 22.33-10 45.36-6 68.89 2.21 13.09 4.06 26.26 5.47 39.45a36.37 36.37 0 0 1 -1.59 13.5c-1.76 6.12-6.65 9.46-12 9.4s-9.52-3.32-11.77-9.32c-3.27-8.7-3.39-17.75-2-26.7 3-18.85 5.71-37.7 4.73-56.86a46.33 46.33 0 0 0 -1.6-9.56c-1.36-5.06-3.63-9.74-9.74-10s-10.86 2.71-13.77 8.06c-5 9.18-5.52 19.16-4.79 29.32.76 10.6 2 21.21 2 31.81 0 5.9-1.82 11.95-3.68 17.65s-6.11 8.1-11.52 7.92c-5-.17-8.76-3.08-10.72-8.53-2.42-6.69-1.42-13.44 0-20.13 2.61-12 5.78-23.85 7.89-35.92 2.43-13.92 3.11-27.94-2.19-41.55-4.23-10.85-14.09-12.81-21.12-3.56a61.82 61.82 0 0 1 -18.93 16.39c-3.95 2.27-7.46 5.32-11.13 8.07-7.31 5.46-13.69 4.33-18.88-3.22-5.52-8-9-16.89-11.55-26.27a56.4 56.4 0 0 0 -4.49-12c-3.1-5.81-9.92-6.05-12.45-.07a62 62 0 0 0 -4.39 18.75c-1.64 19.65 3.05 38.79 5.56 58.1.68 5.24.44 10.61.49 15.92a10.44 10.44 0 0 1 -.83 3.43c-1.68 4.76-5.19 7.58-9 7.29s-7.57-4-7.63-9.07c-.09-7.8.77-15.61 1-23.42s.69-15.38.32-23c-.19-3.87-1.42-8.17-6.18-9.32a8.82 8.82 0 0 0 -10.4 5.05c-3 6.5-6.06 13.34-7.05 20.33-3.34 23.67-2.93 47.47-.37 71.23 2.06 19.08 4.48 38.13 6.07 57.25 1.1 13.29.43 26.63-3.49 39.55a30.52 30.52 0 0 1 -3.69 8c-4.75 7.12-13 7.49-18.15.65a28.17 28.17 0 0 1 -4.42-9.55c-4.22-15.75-3.16-31.62-.46-47.48 4.92-29 11.36-57.79 9.32-87.49-.87-12.69-3.56-24.86-11.22-35.45-5.59-7.73-12.08-10.24-20.8-6.34-8 3.55-15.42 4.52-24 2.25-8.4-2.23-16.74 3.72-20.65 13.16-4.89 11.8-4.15 24-2.15 36.22 1.35 8.29 3.32 16.52 1.46 25-1.65 7.55-5.75 12.08-11.15 11.76s-9.66-5.63-9-13.27c.81-9.36 2.87-18.62 4.68-27.87 2-10.34 4.32-20.77 2-31.18-1.3-5.86-4.33-11.49-7.4-16.74-1.31-2.25-4.51-3.84-7.19-4.76-6.55-2.24-11.76 1.36-11.86 8.29-.07 4.84.91 9.68 1.25 14.54a73 73 0 0 1 .33 11.91c-.55 5.75-4.3 9.55-9.36 10.42-4.85.83-10.69-2-12.48-7.22a27 27 0 0 1 -1.22-12c1.62-11.68 4.51-23.21 5.7-34.92.64-6.26-.52-13-2.12-19.14-1.86-7.18-9.18-10.05-16-7a53.2 53.2 0 0 0 -8.63 5.33c-2.77 2-5.25 4.35-8 6.35-7 5.13-11.33 4.93-18.63.1-3.4-2.25-7.09-4.61-11-5.49-14.83-3.34-25.13 5.83-25.77 22.26-.71 18.29.73 36.32 5.61 54 1.64 6 .52 11.74-2.34 17.18-3.89 7.4-9.56 10.74-15.92 9.39-5.95-1.27-11.29-7.9-11-15.5.35-9 2-17.87 2.82-26.83.58-6.59 1.46-13.31.8-19.83-1-10.3-8.28-14.9-18.33-12.24-14.63 3.87-24.42 11.86-27.78 28-4.53 21.8-3.6 43.53-2.49 65.36 1.36 26.85 9.17 52.61 13.89 78.9 2.22 12.38 3.17 25.1-1.34 37.26-1.46 3.94-4.1 8.12-7.4 10.58-10 7.42-22.1 2.86-23.9-9.4a110.27 110.27 0 0 1 -.57-30c3.9-29.74 9.37-59.37 8-89.47-1-22.07-1.74-44.34-8.78-65.68-3.29-10-8.3-18.25-18-23.72-5.32-3-9.91-7.88-13.77-12.76-4-5.1-8.66-8-15-7.87a52.19 52.19 0 0 0 -11 1c-14 3.26-23.37-3.18-30.62-14.24-2.06-3.41-3.85-7.19-6.43-10.41-15-18.79-35-29.58-58.16-34.93-1.69-.39-3.39-.76-5.08-1.13z&#34;</span> </span></span><span class="line"><span class="cl"> <span class="na">transform=</span><span class="s">&#34;translate(-27.76 -14.56)&#34;</span> </span></span><span class="line"><span class="cl"> <span class="nt">/&gt;</span> </span></span><span class="line"><span class="cl"><span class="nt">&lt;/svg&gt;</span></span></span></code></pre></td></tr></table> </div> </div><p>注意,这个图片没有任何模板执行的内容,只是一个 SVG 图片,那我们就可以拿来直接使用了,例如:</p> <table> <thead> <tr> <th style="text-align: center">主题</th> <th style="text-align: center">图片</th> </tr> </thead> <tbody> <tr> <td style="text-align: center">明亮</td> <td style="text-align: center"><a href="https://lruihao.cn/images/drop.responsive.svg">light image</a></td> </tr> <tr> <td style="text-align: center">暗黑</td> <td style="text-align: center"><a href="https://lruihao.cn/images/drop.responsive.svg#drop-dark-only">dark image</a></td> </tr> </tbody> </table> <p>但是!这个方案有一个缺点,在 Safari 浏览器下,切换时会出现卡顿,不够丝滑。</p> <p>唉,真实遗憾,所以还是继续使用方案一了。</p>

利用 Vercel 反代 Gravatar 实现镜像加速

2024-04-17 12:51:35

<p>在开发和部署网站时,经常会遇到加载外部资源较慢的问题。其中之一就是加载 Gravatar 头像图片时可能会受到网络延迟的影响。为了解决这个问题,我们可以利用 Vercel 平台的反向代理功能来实现镜像加速。</p> <h2 id="思路" class="heading-element"><span>1 思路</span> <a href="#%e6%80%9d%e8%b7%af" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><ol> <li>解析请求的 URL,并将其 host 修改为我们要代理的目标域名(这里是 <code>www.gravatar.com</code>)。</li> <li>获取原始请求的方法、头部信息,并创建一个新的请求头部对象。</li> <li>将新请求头部对象中的 Host 字段设置为目标域名,并将 Referer 字段设置为原始请求的 URL。</li> <li>使用修改后的 URL、方法和头部信息发送请求到目标域名。</li> <li>获取原始响应的状态码、头部信息和响应体,并克隆原始响应对象。</li> <li>检查请求的 Referer 来源域名是否合法,如果不合法则返回一个 403 Forbidden 的响应。</li> <li>设置新的响应头部信息,包括允许的请求方法、请求头部和缓存控制策略。</li> <li>构造最终的响应对象,其中响应体为原始响应的内容,状态码和头部信息为修改后的值。</li> <li>返回最终的响应对象。</li> </ol> <p>安装上面的思路,理论上我们可以反代任何一个网站,并且支持设置 CORS 策略。GitHub 上也有类似的项目,比如 <a href="https://github.com/gaowanlu/google"target="_blank" rel="external nofollow noopener noreferrer">https://github.com/gaowanlu/google<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a>.</p> <blockquote><p>当然,网络不是非法之地,不要滥用这个功能反代一些不合法的网站哦。</p> </blockquote><h2 id="实现" class="heading-element"><span>2 实现</span> <a href="#%e5%ae%9e%e7%8e%b0" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><img alt="Lruihao gravatar" src="https://gravatar.lruihao.cn/avatar/fee47a2f4f2cc71f99a02b0a73ecfee0?s=128" /> <p>⬆️ <code>https://gravatar.lruihao.cn/avatar/fee47a2f4f2cc71f99a02b0a73ecfee0?s=64</code></p> <p>实现 API 代码:</p> <div class="highlight" title="api/gravatar.js"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt"> 1 </span><span class="lnt"> 2 </span><span class="lnt"> 3 </span><span class="lnt"> 4 </span><span class="lnt"> 5 </span><span class="lnt"> 6 </span><span class="lnt"> 7 </span><span class="lnt"> 8 </span><span class="lnt"> 9 </span><span class="lnt">10 </span><span class="lnt">11 </span><span class="lnt">12 </span><span class="lnt">13 </span><span class="lnt">14 </span><span class="lnt">15 </span><span class="lnt">16 </span><span class="lnt">17 </span><span class="lnt">18 </span><span class="lnt">19 </span><span class="lnt">20 </span><span class="lnt">21 </span><span class="lnt">22 </span><span class="lnt">23 </span><span class="lnt">24 </span><span class="lnt">25 </span><span class="lnt">26 </span><span class="lnt">27 </span><span class="lnt">28 </span><span class="lnt">29 </span><span class="lnt">30 </span><span class="lnt">31 </span><span class="lnt">32 </span><span class="lnt">33 </span><span class="lnt">34 </span><span class="lnt">35 </span><span class="lnt">36 </span><span class="lnt">37 </span><span class="lnt">38 </span><span class="lnt">39 </span><span class="lnt">40 </span><span class="lnt">41 </span><span class="lnt">42 </span><span class="lnt">43 </span><span class="lnt">44 </span><span class="lnt">45 </span><span class="lnt">46 </span><span class="lnt">47 </span><span class="lnt">48 </span><span class="lnt">49 </span><span class="lnt">50 </span><span class="lnt">51 </span><span class="lnt">52 </span><span class="lnt">53 </span><span class="lnt">54 </span><span class="lnt">55 </span><span class="lnt">56 </span><span class="lnt">57 </span><span class="lnt">58 </span><span class="lnt">59 </span><span class="lnt">60 </span><span class="lnt">61 </span><span class="lnt">62 </span><span class="lnt">63 </span><span class="lnt">64 </span><span class="lnt">65 </span><span class="lnt">66 </span><span class="lnt">67 </span><span class="lnt">68 </span><span class="lnt">69 </span><span class="lnt">70 </span><span class="lnt">71 </span><span class="lnt">72 </span><span class="lnt">73 </span><span class="lnt">74 </span><span class="lnt">75 </span><span class="lnt">76 </span><span class="lnt">77 </span><span class="lnt">78 </span><span class="lnt">79 </span><span class="lnt">80 </span><span class="lnt">81 </span><span class="lnt">82 </span><span class="lnt">83 </span><span class="lnt">84 </span><span class="lnt">85 </span><span class="lnt">86 </span><span class="lnt">87 </span><span class="lnt">88 </span><span class="lnt">89 </span><span class="lnt">90 </span><span class="lnt">91 </span><span class="lnt">92 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">allowedReferrers</span> <span class="o">=</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;lruihao.cn&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;gravatar-x.vercel.app&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;-lrh-dev.vercel.app&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;-cell-x.vercel.app&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"><span class="p">];</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">upstream</span> <span class="o">=</span> <span class="s2">&#34;www.gravatar.com&#34;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="cm">/** </span></span></span><span class="line"><span class="cl"><span class="cm"> * whether the hostname is allowed </span></span></span><span class="line"><span class="cl"><span class="cm"> * @param {String} hostname </span></span></span><span class="line"><span class="cl"><span class="cm"> * @returns </span></span></span><span class="line"><span class="cl"><span class="cm"> */</span> </span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">isAllowedHost</span><span class="p">(</span><span class="nx">hostname</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">regExp</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="nx">allowedReferrers</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="s2">&#34;|&#34;</span><span class="p">),</span> <span class="s2">&#34;g&#34;</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="c1">// if hostname matches allowed referrers </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">hostname</span> <span class="o">||</span> <span class="nx">regExp</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">hostname</span><span class="p">))</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="kc">true</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">referrer</span> <span class="k">of</span> <span class="nx">allowedReferrers</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// if hostname ends with allowed referrers </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="nx">hostname</span><span class="p">.</span><span class="nx">endsWith</span><span class="p">(</span><span class="nx">referrer</span><span class="p">))</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="kc">true</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="kc">false</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">fetchAndApply</span><span class="p">(</span><span class="nx">request</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">response</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">url</span><span class="p">.</span><span class="nx">host</span> <span class="o">=</span> <span class="nx">upstream</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">method</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">method</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">request_headers</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">new_request_headers</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Headers</span><span class="p">(</span><span class="nx">request_headers</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="nx">new_request_headers</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s2">&#34;Host&#34;</span><span class="p">,</span> <span class="nx">upstream</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="nx">new_request_headers</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s2">&#34;Referer&#34;</span><span class="p">,</span> <span class="nx">url</span><span class="p">.</span><span class="nx">href</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">original_response</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">href</span><span class="p">,</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">method</span><span class="o">:</span> <span class="nx">method</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nx">headers</span><span class="o">:</span> <span class="nx">new_request_headers</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="p">});</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">original_response_clone</span> <span class="o">=</span> <span class="nx">original_response</span><span class="p">.</span><span class="nx">clone</span><span class="p">();</span> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">original_text</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">response_headers</span> <span class="o">=</span> <span class="nx">original_response</span><span class="p">.</span><span class="nx">headers</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">new_response_headers</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Headers</span><span class="p">(</span><span class="nx">response_headers</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">status</span> <span class="o">=</span> <span class="nx">original_response</span><span class="p">.</span><span class="nx">status</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">hostname</span> <span class="o">=</span> <span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">try</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">&#34;Referer&#34;</span><span class="p">)).</span><span class="nx">hostname</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="s2">&#34;&#34;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">})();</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isAllowedHost</span><span class="p">(</span><span class="nx">hostname</span><span class="p">))</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="k">new</span> <span class="nx">Response</span><span class="p">(</span><span class="sb">`403 Forbidden: </span><span class="si">${</span><span class="nx">hostname</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">headers</span><span class="o">:</span> <span class="p">{</span> <span class="s2">&#34;Content-Type&#34;</span><span class="o">:</span> <span class="s2">&#34;text/html&#34;</span> <span class="p">},</span> </span></span><span class="line"><span class="cl"> <span class="nx">status</span><span class="o">:</span> <span class="mi">403</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nx">statusText</span><span class="o">:</span> <span class="s2">&#34;Forbidden&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="p">});</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">// new_response_headers.set(&#34;access-control-allow-origin&#34;, &#34;https://lruihao.cn&#34;); </span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">new_response_headers</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s2">&#34;Access-Control-Allow-Methods&#34;</span><span class="p">,</span> <span class="s2">&#34;GET, POST, OPTIONS&#34;</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="nx">new_response_headers</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s2">&#34;Access-Control-Allow-Headers&#34;</span><span class="p">,</span> <span class="s2">&#34;Content-Type&#34;</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="nx">new_response_headers</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;Cache-Control&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;max-age=600, s-maxage=2592000, stale-while-revalidate&#34;</span> </span></span><span class="line"><span class="cl"> <span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="nx">new_response_headers</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="s2">&#34;link&#34;</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">original_text</span> <span class="o">=</span> <span class="nx">original_response_clone</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nx">response</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Response</span><span class="p">(</span><span class="nx">original_text</span><span class="p">,</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">status</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nx">headers</span><span class="o">:</span> <span class="nx">new_response_headers</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="p">});</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nx">response</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">config</span> <span class="o">=</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">runtime</span><span class="o">:</span> <span class="s2">&#34;experimental-edge&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"><span class="p">};</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">req</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nx">fetchAndApply</span><span class="p">(</span><span class="nx">req</span><span class="p">);</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>配置 <code>vercel.json</code> 文件:</p> <div class="highlight" title="vercel.json"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span><span class="lnt">2 </span><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;rewrites&#34;</span><span class="p">:</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;/avatar/(.*)&#34;</span><span class="p">,</span> <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;api/gravatar&#34;</span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">]</span> </span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table> </div> </div><p>最后部署到 Vercel 平台即可。</p> <h2 id="源码" class="heading-element"><span>3 源码</span> <a href="#%e6%ba%90%e7%a0%81" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><div class="gh-repo-card-container single"> <div class="gh-repo-card"> <div class="repo-card-content"> <div class="repo-name"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-repo mr-1 color-fg-muted"> <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z"></path> </svg> <a href="https://github.com/Lruihao/vercel-gravatar" class="repo-url" title="Lruihao/vercel-gravatar" target="_blank"> <span>Lruihao/</span><span>vercel-gravatar</span> </a><span class="repo-visibility" data-archived="false">Public</span> </div><p class="repo-desc">Gravatar Proxy powered by Vercel</p> <p class="repo-statistics"><span class="repo-lang"> <span class="repo-lang-color" style="background-color: #f1e05a;"></span> <span itemprop="programmingLanguage">JavaScript</span> </span><a href="https://github.com/Lruihao/vercel-gravatar/stargazers" title="1 stars" class="repo-stars" target="_blank"> <svg aria-label="stars" role="img" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-star"> <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z"></path> </svg>1</a><a href="https://github.com/Lruihao/vercel-gravatar/forks" title="1 forks" class="repo-forks" target="_blank"> <svg aria-label="forks" role="img" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-repo-forked"> <path d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"></path> </svg>1</a></p> </div> </div> </div>

临时决定再写一个小工具 - 网站预览图生成器

2024-04-04 13:23:37

<img src="https://lruihao.cn/projects/apple-devices-preview/images/cover.webp" alt="featured image" referrerpolicy="no-referrer"><p>开发完 <a href="https://github.com/Lruihao/CoverView"target="_blank" rel="external nofollow noopener noreferrer">CoverView<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a>之后,在调整博客文章封面图的时候,我发现首页的多端缩略图还是只能自己 P 图诶,于是我又决定再写一个小工具,用来生成网站预览图。。</p> <h2 id="在线体验" class="heading-element"><span>1 在线体验</span> <a href="#%e5%9c%a8%e7%ba%bf%e4%bd%93%e9%aa%8c" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><ul> <li><a href="https://lruihao.github.io/vue-el-demo/#/apple-devices-preview"target="_blank" rel="external nofollow noopener noreferrer">vue-el-demo#apple-devices-preview<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a></li> <li><a href="https://github.com/Lruihao/vue-el-demo/blob/main/src/views/apple-devices-preview.vue"target="_blank" rel="external nofollow noopener noreferrer">源码<i class="fa-solid fa-external-link-alt fa-fw fa-xs ms-1 text-secondary" aria-hidden="true"></i></a></li> </ul> <h2 id="实现原理" class="heading-element"><span>2 实现原理</span> <a href="#%e5%ae%9e%e7%8e%b0%e5%8e%9f%e7%90%86" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>实现原理很简单,就是先写好布局,然后每个设备里面套一个 iframe,然后通过 iframe 的 <code>src</code> 属性来加载网页。</p> <h2 id="卡壳点" class="heading-element"><span>3 卡壳点</span> <a href="#%e5%8d%a1%e5%a3%b3%e7%82%b9" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><ul> <li>iframe 存在跨域问题。</li> <li>在将 DOM 转图片的时候,iframe 里面的内容无法转换,尝试了 <code>html2canvas</code> 和 <code>dom-to-image-more</code> 都不行,放弃了,改为用浏览器插件 <code>Fireshot</code> 截图。</li> </ul> <p>一个未尝试的思路,如果跨域问题得以解决,转换图片的步骤可以分解为:</p> <ol> <li>拿到每个 iframe 里的 body 内容,转为图片,然后将图片相对定位到对应的设备 iframe 里</li> <li>隐藏原来的 iframe</li> <li>最后将父容器的 DOM 转为图片</li> </ol> <h2 id="效果图" class="heading-element"><span>4 效果图</span> <a href="#%e6%95%88%e6%9e%9c%e5%9b%be" class="heading-mark"> <svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg> </a> </h2><p>输入不同设备的 URL,选择背景颜色,点击预览,然后自行截图即可,比如用 <code>Fireshot</code> 插件捕获可见区域。</p> <p><a class="lightgallery" href="https://lruihao.cn/projects/apple-devices-preview/images/screenshot.webp?size=large" data-thumbnail="/projects/apple-devices-preview/images/screenshot.webp?size=small" data-sub-html="<h2>screenshot</h2>"><img loading="lazy" src="https://lruihao.cn/projects/apple-devices-preview/images/screenshot.webp" alt="screenshot" srcset="https://lruihao.cn/projects/apple-devices-preview/images/screenshot.webp?size=small, https://lruihao.cn/projects/apple-devices-preview/images/screenshot.webp?size=medium 1.5x, https://lruihao.cn/projects/apple-devices-preview/images/screenshot.webp?size=large 2x" data-title="screenshot" style="--width: 2878px;--aspect-ratio: 2878 / 1506;background: url(/images/loading.min.svg) no-repeat center;" onload="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}this.dataset.lazyloaded='';" onerror="this.title=this.dataset.title;for(const i of ['style', 'data-title','onerror','onload']){this.removeAttribute(i);}"/></a></p>