<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Sun with no word</title>
  
  <subtitle>something about what I have learned and some interesting things</subtitle>
  <link href="https://sunjinkang.github.io/atom.xml" rel="self"/>
  
  <link href="https://sunjinkang.github.io/"/>
  <updated>2025-05-16T03:09:04.986Z</updated>
  <id>https://sunjinkang.github.io/</id>
  
  <author>
    <name>Sun Jinkang</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title></title>
    <link href="https://sunjinkang.github.io/2025/05/16/75-about-indexedDB/"/>
    <id>https://sunjinkang.github.io/2025/05/16/75-about-indexedDB/</id>
    <published>2025-05-16T01:31:31.873Z</published>
    <updated>2025-05-16T03:09:04.986Z</updated>
    
    <content type="html"><![CDATA[<h1 id="关于IndexedDB"><a href="#关于IndexedDB" class="headerlink" title="关于IndexedDB"></a>关于IndexedDB</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>大家应该都遇到过需要在浏览器中存储数据的情况吧，我们常用的就是cookie、localStorage、sessionStorage，这些方法一般来说能够满足我们的日常开发使用。但是，不知道大家有没有遇到过需要存储大量的数据的情况，这时候，前面提到的这些方法可能就满足不了我们的要求，这时候IndexedDB或许可以成为一个不错的选择。</p><h2 id="什么是IndexedDB？"><a href="#什么是IndexedDB？" class="headerlink" title="什么是IndexedDB？"></a>什么是IndexedDB？</h2><blockquote><p>IndexedDB 是一种底层 API，用于在客户端存储大量的结构化数据（也包括文件/二进制大型对象（blobs））。该 API 使用索引实现对数据的高性能搜索。</p></blockquote><p>主要特点：</p><ul><li>异步API（不会阻塞UI）</li><li>支持事务</li><li>支持索引查询</li><li>同源策略限制</li><li>存储空间较大（通常50MB以上，取决于浏览器）</li></ul><h2 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h2><ul><li>需要离线工作的Web应用</li><li>需要存储大量结构化数据的应用</li><li>需要复杂查询的本地数据存储</li><li>需要高性能本地数据访问的应用</li></ul><p>IndexedDB为Web应用提供了强大的本地存储能力，虽然API相对复杂，但能够满足高级的客户端数据存储需求。</p><p><strong>场景举例</strong></p><ul><li>Web 游戏<ul><li>场景： 保存游戏进度、配置、用户数据等。</li><li>示例：本地保存 RPG 游戏的存档数据、存储离线资源（角色模型、场景数据）、用户自定义设置存储（分辨率、按键配置）</li></ul></li><li>渐进式 Web 应用（PWA）<ul><li>场景： 提升 Web 应用的离线体验和性能。</li><li>示例：离线播放音乐或视频（配合 service worker 缓存媒体文件）、离线阅读应用（小说、电子书等）、PWA 的聊天应用暂存聊天记录</li></ul></li></ul><h2 id="基本使用方法介绍"><a href="#基本使用方法介绍" class="headerlink" title="基本使用方法介绍"></a>基本使用方法介绍</h2><p>下面我们介绍一下IndexedDB的一些方法及使用举例。</p><ol><li><code>indexedDB.open(name, version)</code><br>用于打开或创建数据库，name是数据库名称，version可选，数据库版本号。<em>升级数据库结构时需要修改版本。</em></li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> request = indexedDB.<span class="title function_">open</span>(<span class="string">&#x27;MyDatabase&#x27;</span>, <span class="number">1</span>);</span><br></pre></td></tr></table></figure><ol start="2"><li><code>request.onupgradeneeded</code><br>当数据库第一次创建或版本升级时触发，用于创建对象仓库（Object Store）和索引等结构。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">request.<span class="property">onupgradeneeded</span> = <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="comment">// xxxxxx</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ol start="3"><li><code>request.onsuccess</code> / <code>request.onerror</code><br><code>onsuccess</code>：打开数据库成功后的回调，<code>onerror</code>：打开数据库失败的回调。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">request.<span class="property">onsuccess</span> = <span class="function">(<span class="params">event</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> db = event.<span class="property">target</span>.<span class="property">result</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;数据库打开成功&#x27;</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">request.<span class="property">onerror</span> = <span class="function">(<span class="params">event</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;数据库打开失败&#x27;</span>, event.<span class="property">target</span>.<span class="property">error</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ol start="4"><li><code>db.createObjectStore(name, options)</code><br>在 <code>onupgradeneeded</code> 中调用，用于创建对象仓库。<code>keyPath</code>：指定主键字段名称（也可使用自动递增：<code>&#123; autoIncrement: true &#125;</code>）。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">request.<span class="property">onupgradeneeded</span> = <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> db = event.<span class="property">target</span>.<span class="property">result</span>;</span><br><span class="line">  <span class="keyword">const</span> store = db.<span class="title function_">createObjectStore</span>(<span class="string">&#x27;users&#x27;</span>, &#123; <span class="attr">keyPath</span>: <span class="string">&#x27;id&#x27;</span> &#125;);</span><br><span class="line">  store.<span class="title function_">createIndex</span>(<span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;name&#x27;</span>, &#123; <span class="attr">unique</span>: <span class="literal">false</span> &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><img src="create-indexeddb.png" class="lazyload" data-srcset="create-indexeddb.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="create"></p><ol start="5"><li><code>db.transaction(storeNames, mode)</code><br>创建一个事务对象，用于读取或写入数据。<code>storeNames</code>：对象仓库名称或数组，<code>mode</code>：操作模式，<code>readonly</code> 或 <code>readwrite</code>。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> tx = db.<span class="title function_">transaction</span>([<span class="string">&#x27;users&#x27;</span>], <span class="string">&#x27;readwrite&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> store = tx.<span class="title function_">objectStore</span>(<span class="string">&#x27;users&#x27;</span>);</span><br></pre></td></tr></table></figure><ol start="6"><li><code>store.add(value)</code> / <code>store.put(value)</code><br>用于添加或更新数据。<code>add()</code>：如果主键已存在会报错，<code>put()</code>：存在则更新，不存在则新增。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">store.<span class="title function_">add</span>(&#123; <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">&#x27;Alice&#x27;</span> &#125;);</span><br><span class="line">store.<span class="title function_">put</span>(&#123; <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">&#x27;Alice Smith&#x27;</span> &#125;);</span><br></pre></td></tr></table></figure><ol start="7"><li><code>store.get(key)</code><br>用于根据主键获取数据。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">store.<span class="title function_">get</span>(<span class="number">1</span>).<span class="property">onsuccess</span> = <span class="function">(<span class="params">event</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;获取结果：&#x27;</span>, event.<span class="property">target</span>.<span class="property">result</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ol start="8"><li><code>store.delete(key)</code><br>删除某条数据。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">store.<span class="title function_">delete</span>(<span class="number">1</span>);</span><br></pre></td></tr></table></figure><ol start="9"><li><code>store.clear()</code><br>清空对象仓库中所有记录。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">store.<span class="title function_">clear</span>();</span><br></pre></td></tr></table></figure><ol start="10"><li><code>store.openCursor()</code><br>用于遍历所有数据（类似迭代器）。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cursorRequest = store.<span class="title function_">openCursor</span>();</span><br><span class="line"></span><br><span class="line">cursorRequest.<span class="property">onsuccess</span> = <span class="function">(<span class="params">event</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> cursor = event.<span class="property">target</span>.<span class="property">result</span>;</span><br><span class="line">  <span class="keyword">if</span> (cursor) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(cursor.<span class="property">key</span>, cursor.<span class="property">value</span>);</span><br><span class="line">    cursor.<span class="title function_">continue</span>();  <span class="comment">// 移动到下一条</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;遍历完成&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ol start="11"><li><code>store.createIndex(name, keyPath, options)</code><br>创建索引，便于通过其他字段查询。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">store.<span class="title function_">createIndex</span>(<span class="string">&#x27;nameIndex&#x27;</span>, <span class="string">&#x27;name&#x27;</span>, &#123; <span class="attr">unique</span>: <span class="literal">false</span> &#125;);</span><br></pre></td></tr></table></figure><p>之后可以通过索引查询：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> index = store.<span class="title function_">index</span>(<span class="string">&#x27;nameIndex&#x27;</span>);</span><br><span class="line">index.<span class="title function_">get</span>(<span class="string">&#x27;Alice&#x27;</span>).<span class="property">onsuccess</span> = <span class="function">(<span class="params">e</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(e.<span class="property">target</span>.<span class="property">result</span>);</span><br></pre></td></tr></table></figure><p>之后可以通过索引查询：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> index = store.<span class="title function_">index</span>(<span class="string">&#x27;nameIndex&#x27;</span>);</span><br><span class="line">index.<span class="title function_">get</span>(<span class="string">&#x27;Alice&#x27;</span>).<span class="property">onsuccess</span> = <span class="function">(<span class="params">e</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(e.<span class="property">target</span>.<span class="property">result</span>);</span><br></pre></td></tr></table></figure><ol start="12"><li>  <code>db.deleteDatabase(name)</code><br>用于删除IndexedDB，name为创建的IndexedDB名称，注意name需要匹配，删除后数据无法恢复，慎用！数据库被打开无法被删除。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> deleteRequest = indexedDB.<span class="title function_">deleteDatabase</span>(<span class="string">&#x27;myDatabase&#x27;</span>);</span><br><span class="line">deleteRequest.<span class="property">onsuccess</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;数据库删除成功&#x27;</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">deleteRequest.<span class="property">onerror</span> = <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;数据库删除失败&#x27;</span>, event.<span class="property">target</span>.<span class="property">error</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">deleteRequest.<span class="property">onblocked</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&#x27;删除被阻止，可能数据库正被打开中&#x27;</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol><li><strong>异步操作</strong>：所有IndexedDB操作都是异步的，需要通过事件处理结果</li><li><strong>事务</strong>：任何数据操作都需要在事务中进行</li><li><strong>版本控制</strong>：数据库结构变更需要通过版本升级实现</li><li><strong>错误处理</strong>：务必处理onerror事件，避免静默失败</li><li><strong>浏览器兼容性</strong>：现代浏览器基本都支持，但旧版IE需要特定版本</li></ol><h2 id="常用的库idb"><a href="#常用的库idb" class="headerlink" title="常用的库idb"></a>常用的库idb</h2><p>由于IndexedDB的原生API不友好，idb成为了热门的IndexedDB包装库，方便我们我们操作IndexedDB，大家感兴趣的可以自己去了解一下。<br><a href="https://github.com/jakearchibald/idb">idb的github地址</a></p><p>资料：<br><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API">IndexedDB</a></p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;关于IndexedDB&quot;&gt;&lt;a href=&quot;#关于IndexedDB&quot; class=&quot;headerlink&quot; title=&quot;关于IndexedDB&quot;&gt;&lt;/a&gt;关于IndexedDB&lt;/h1&gt;&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;大家应该都遇到过需要在浏览器中存储数据的情况吧，我们常用的就是cookie、localStorage、sessionStorage，这些方法一般来说能够满足我们的日常开发使用。但是，不知道大家有没有遇到过需要存储大量的数据的情况，这时候，前面提到的这些方法可能就满足不了我们的要求，这时候IndexedDB或许可以成为一个不错的选择。&lt;/p&gt;
&lt;h2 id=&quot;什么是IndexedDB？&quot;&gt;&lt;a href=&quot;#什么是IndexedDB？&quot; class=&quot;headerlink&quot; title=&quot;什么是IndexedDB？&quot;&gt;&lt;/a&gt;什么是IndexedDB？&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;IndexedDB 是一种底层 API，用于在客户端存储大量的结构化数据（也包括文件/二进制大型对象（blobs））。该 API 使用索引实现对数据的高性能搜索。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>CSS 方法与属性介绍</title>
    <link href="https://sunjinkang.github.io/2025/04/27/74-about-css-new/"/>
    <id>https://sunjinkang.github.io/2025/04/27/74-about-css-new/</id>
    <published>2025-04-27T07:51:10.000Z</published>
    <updated>2025-04-27T07:51:37.461Z</updated>
    
    <content type="html"><![CDATA[<h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1. 前言"></a>1. 前言</h2><p>CSS（层叠样式表）是用于描述 HTML 或 XML 文档外观的样式表语言。随着 Web 技术的不断发展，CSS 也引入了许多新的特性和方法，使得开发者能够更灵活地控制页面的样式和布局。本文将介绍一些现代 CSS 中常用的方法和属性，帮助开发者更好地理解和应用这些特性。</p><h2 id="2-CSS-一些特性介绍"><a href="#2-CSS-一些特性介绍" class="headerlink" title="2. CSS 一些特性介绍"></a>2. CSS 一些特性介绍</h2><h3 id="steps"><a href="#steps" class="headerlink" title="steps()"></a>steps()</h3><p><strong>说明</strong>:<br><code>steps()</code> 是一个用于 <code>animation-timing-function</code> 属性的函数，它允许你将动画分成多个步骤，而不是平滑过渡。这对于创建逐帧动画非常有用。</p><p><strong>使用注意事项</strong>:  </p><ul><li><code>steps()</code> 接受两个参数：第一个参数是步骤的数量，第二个参数是可选的，用于指定步骤的变化点（<code>start</code> 或 <code>end</code>）。</li><li>如果你想要动画在每一步开始时变化，使用 <code>start</code>；如果你想要动画在每一步结束时变化，使用 <code>end</code>。</li></ul><p><strong>常见使用方式举例</strong>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@keyframes</span> move &#123;</span><br><span class="line">  <span class="selector-tag">from</span> &#123; <span class="attribute">transform</span>: <span class="built_in">translateX</span>(<span class="number">0</span>); &#125;</span><br><span class="line">  <span class="selector-tag">to</span> &#123; <span class="attribute">transform</span>: <span class="built_in">translateX</span>(<span class="number">100px</span>); &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.element</span> &#123;</span><br><span class="line">  <span class="attribute">animation</span>: move <span class="number">2s</span> <span class="built_in">steps</span>(<span class="number">5</span>, end) infinite;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="CSS-变量"><a href="#CSS-变量" class="headerlink" title="CSS 变量"></a>CSS 变量</h3><p><strong>说明</strong>:<br>CSS 变量（也称为自定义属性）允许你在 CSS 中定义可重用的值，并在整个样式表中引用它们。CSS 变量以 <code>--</code> 开头。</p><p><strong>使用注意事项</strong>:  </p><ul><li>CSS 变量是大小写敏感的。</li><li>你可以在 <code>:root</code> 伪类中定义全局变量，也可以在特定选择器中定义局部变量。</li><li>使用 <code>var()</code> 函数来引用变量。</li></ul><p><strong>常见使用方式举例</strong>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-pseudo">:root</span> &#123;</span><br><span class="line">  <span class="attr">--primary-color</span>: <span class="number">#3498db</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.element</span> &#123;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="built_in">var</span>(--primary-color);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="min-、max-和-clamp"><a href="#min-、max-和-clamp" class="headerlink" title="min()、max() 和 clamp()"></a>min()、max() 和 clamp()</h3><p><strong>说明</strong>:<br><code>min()</code>、<code>max()</code> 和 <code>clamp()</code> 是 CSS 中的数学函数，用于在样式表中进行动态计算。</p><ul><li><code>min()</code>：返回一组值中的最小值。</li><li><code>max()</code>：返回一组值中的最大值。</li><li><code>clamp()</code>：接受三个参数（最小值、首选值、最大值），并返回一个介于最小值和最大值之间的值。</li></ul><p><strong>使用注意事项</strong>:  </p><ul><li>这些函数可以用于任何接受长度、百分比、角度等值的属性。</li><li><code>clamp()</code> 特别适用于响应式设计，因为它可以根据视口大小动态调整值。</li></ul><p><strong>常见使用方式举例</strong>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.element</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="built_in">min</span>(<span class="number">100%</span>, <span class="number">500px</span>);</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="built_in">clamp</span>(<span class="number">1rem</span>, <span class="number">2.5vw</span>, <span class="number">2rem</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="supports"><a href="#supports" class="headerlink" title="@supports"></a>@supports</h3><p><strong>说明</strong>:<br><code>@supports</code> 是一个 CSS 规则，用于检测浏览器是否支持某个 CSS 特性。如果支持，则应用其中的样式。</p><p><strong>使用注意事项</strong>:  </p><ul><li><code>@supports</code> 可以用于渐进增强，确保在不支持某些特性的浏览器中提供备用样式。</li><li>你可以使用逻辑运算符（如 <code>and</code>、<code>or</code>、<code>not</code>）来组合多个条件。</li></ul><p><strong>常见使用方式举例</strong>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@supports</span> (<span class="attribute">display</span>: <span class="attribute">grid</span>) &#123;</span><br><span class="line">  <span class="selector-class">.container</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: grid;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="accent-color"><a href="#accent-color" class="headerlink" title="accent-color"></a>accent-color</h3><p><strong>说明</strong>:<br><code>accent-color</code> 是一个用于设置表单控件（如复选框、单选按钮、进度条等）的强调色的属性。</p><p><strong>使用注意事项</strong>:  </p><ul><li><code>accent-color</code> 目前仅支持部分表单控件。</li><li>该属性可以快速统一表单元素的样式，减少自定义样式的复杂性。</li></ul><p><strong>常见使用方式举例</strong>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">input</span><span class="selector-attr">[type=<span class="string">&quot;checkbox&quot;</span>]</span> &#123;</span><br><span class="line">  accent-<span class="attribute">color</span>: <span class="number">#3498db</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="container"><a href="#container" class="headerlink" title="@container"></a>@container</h3><p><strong>说明</strong>:<br><code>@container</code> 是一个用于容器查询的 CSS 规则，允许你根据容器的大小而不是视口大小来应用样式。</p><p><strong>使用注意事项</strong>:  </p><ul><li>容器查询目前仍处于实验阶段，浏览器支持有限。</li><li>你需要先定义一个容器，然后使用 <code>@container</code> 来查询该容器的大小。</li></ul><p><strong>常见使用方式举例</strong>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.container</span> &#123;</span><br><span class="line">  container-type: inline-size;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@container</span> (<span class="attribute">min-width</span>: <span class="number">300px</span>) &#123;</span><br><span class="line">  <span class="selector-class">.element</span> &#123;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">1.5rem</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="color-mix"><a href="#color-mix" class="headerlink" title="color-mix()"></a>color-mix()</h3><p><strong>说明</strong>:<br><code>color-mix()</code> 是一个用于混合两种颜色的 CSS 函数。你可以指定混合的比例。</p><p><strong>使用注意事项</strong>:  </p><ul><li><code>color-mix()</code> 目前仍处于实验阶段，浏览器支持有限。</li><li>你可以使用 <code>in</code> 关键字来指定颜色空间（如 <code>srgb</code>、<code>lab</code> 等）。</li></ul><p><strong>常见使用方式举例</strong>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.element</span> &#123;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="built_in">color-mix</span>(in srgb, <span class="number">#3498db</span> <span class="number">50%</span>, <span class="number">#e74c3c</span> <span class="number">50%</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="has"><a href="#has" class="headerlink" title=":has()"></a>:has()</h3><p><strong>说明</strong>:<br><code>:has()</code> 是一个 CSS 伪类，允许你选择包含特定子元素的父元素。</p><p><strong>使用注意事项</strong>:  </p><ul><li><code>:has()</code> 目前仍处于实验阶段，浏览器支持有限。</li><li>该伪类可以用于复杂的选择器，帮助你更精确地选择元素。</li></ul><p><strong>常见使用方式举例</strong>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">div</span><span class="selector-pseudo">:has</span>(&gt; <span class="selector-tag">p</span>) &#123;</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#000</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="clip-path-裁剪路径"><a href="#clip-path-裁剪路径" class="headerlink" title="clip-path 裁剪路径"></a>clip-path 裁剪路径</h3><p><strong>说明</strong>:<br><code>clip-path</code> 是一个用于裁剪元素的属性，允许你使用各种形状（如圆形、多边形等）来裁剪元素的可视区域。</p><p><strong>使用注意事项</strong>:  </p><ul><li><code>clip-path</code> 可以使用基本形状（如 <code>circle()</code>、<code>polygon()</code>）或 SVG 路径来定义裁剪区域。</li><li>该属性可以用于创建复杂的视觉效果，如不规则形状的图片裁剪。</li></ul><p><strong>常见使用方式举例</strong>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.element</span> &#123;</span><br><span class="line">  <span class="attribute">clip-path</span>: <span class="built_in">polygon</span>(<span class="number">50%</span> <span class="number">0%</span>, <span class="number">100%</span> <span class="number">50%</span>, <span class="number">50%</span> <span class="number">100%</span>, <span class="number">0%</span> <span class="number">50%</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="filter-滤镜效果"><a href="#filter-滤镜效果" class="headerlink" title="filter 滤镜效果"></a>filter 滤镜效果</h3><p><strong>说明</strong>:<br><code>filter</code> 是一个用于应用图形效果（如模糊、亮度调整、对比度调整等）的属性。</p><p><strong>使用注意事项</strong>:  </p><ul><li><code>filter</code> 可以接受多个滤镜函数，如 <code>blur()</code>、<code>brightness()</code>、<code>contrast()</code> 等。</li><li>该属性可以用于创建各种视觉效果，如毛玻璃效果、黑白照片效果等。</li></ul><p><strong>常见使用方式举例</strong>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.element</span> &#123;</span><br><span class="line">  <span class="attribute">filter</span>: <span class="built_in">blur</span>(<span class="number">5px</span>) <span class="built_in">grayscale</span>(<span class="number">50%</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-总结"><a href="#3-总结" class="headerlink" title="3. 总结"></a>3. 总结</h2><p>本文介绍了一些现代 CSS 中常用的方法和属性，包括 <code>steps()</code>、CSS 变量、<code>min()</code>、<code>max()</code>、<code>clamp()</code>、<code>@supports</code>、<code>accent-color</code>、<code>@container</code>、<code>color-mix()</code>、<code>:has()</code>、<code>clip-path</code> 和 <code>filter</code>。这些特性和方法为开发者提供了更强大的工具来创建复杂、响应式和视觉效果丰富的网页。随着浏览器对这些特性的支持不断完善，开发者可以更加灵活地应用这些技术，提升用户体验。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;1-前言&quot;&gt;&lt;a href=&quot;#1-前言&quot; class=&quot;headerlink&quot; title=&quot;1. 前言&quot;&gt;&lt;/a&gt;1. 前言&lt;/h2&gt;&lt;p&gt;CSS（层叠样式表）是用于描述 HTML 或 XML 文档外观的样式表语言。随着 Web 技术的不断发展，CSS 也引入了许多新的特性和方法，使得开发者能够更灵活地控制页面的样式和布局。本文将介绍一些现代 CSS 中常用的方法和属性，帮助开发者更好地理解和应用这些特性。&lt;/p&gt;
&lt;h2 id=&quot;2-CSS-一些特性介绍&quot;&gt;&lt;a href=&quot;#2-CSS-一些特性介绍&quot; class=&quot;headerlink&quot; title=&quot;2. CSS 一些特性介绍&quot;&gt;&lt;/a&gt;2. CSS 一些特性介绍&lt;/h2&gt;&lt;h3 id=&quot;steps&quot;&gt;&lt;a href=&quot;#steps&quot; class=&quot;headerlink&quot; title=&quot;steps()&quot;&gt;&lt;/a&gt;steps()&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;:&lt;br&gt;&lt;code&gt;steps()&lt;/code&gt; 是一个用于 &lt;code&gt;animation-timing-function&lt;/code&gt; 属性的函数，它允许你将动画分成多个步骤，而不是平滑过渡。这对于创建逐帧动画非常有用。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title></title>
    <link href="https://sunjinkang.github.io/2025/04/27/73-about-picture-validate/%E6%8B%96%E5%8A%A8%E9%AA%8C%E8%AF%81/js/index/"/>
    <id>https://sunjinkang.github.io/2025/04/27/73-about-picture-validate/%E6%8B%96%E5%8A%A8%E9%AA%8C%E8%AF%81/js/index/</id>
    <published>2025-04-27T07:15:48.187Z</published>
    <updated>2024-08-15T23:51:00.000Z</updated>
    
    <content type="html"><![CDATA[// 封装一个 DOM 查询方法function $(selector) {  return document.querySelector(selector);}// 接下来我们来获取一下 DOM 节点let imgBox = $('.imgBox'); // 图片容器let imgBlock = $('.imgBlock'); // 可以拖动的图片let imgGap = $('.imgGap'); // 图片缺口let title = $('.imgContainer h3'); // 获取标题let slider = $('.slider'); // 滑块条let btn = $('#btn'); // 拖动的滑块let span = $('.slider span'); // 滑块条的文字// 初始化函数function init() {  // 1. 随机生成背景图片  let imgArr = ['t1.png', 't2.png', 't3.png', 't4.png', 't5.png']; // 存储所有图片的数组  // 每次初始化的时候，从里面随机挑选一张图片  let randomImg = Math.floor(Math.random() * imgArr.length);  // 下面使用到了 ES6 的字符串模板  // 在字符串模板中，可以解析变量  // let name = ‘xiejie’;  // console.log('Hello,' + name);  // console.log(`Hello,${name}`);  // 图片容器以及拖动的图片块设置对应的背景图  imgBox.style.backgroundImage = `url('../img/${imgArr[randomImg]}')`;  imgBlock.style.backgroundImage = `url('../img/${imgArr[randomImg]}')`;  // 2. 计算拖动图块和空缺图块的位置  // 空缺图块可以设置的最大 top 值 = 盒子容器的高度 - 空缺图块的高度  let heightRange = imgBox.offsetHeight - imgBlock.offsetHeight;  // 空缺图块可以设置的最大 left 值 = 盒子容器的宽度的一半 - 空缺图块的宽度  let widthRange = imgBox.offsetWidth / 2 - imgBlock.offsetWidth;  // 生成随机的 left 和 top 值  let top = Math.random() * heightRange;  let left = Math.random() * widthRange + imgBox.offsetWidth / 2;  // 设置图片缺口的 left 和 top 值  imgGap.style.left = left + 'px';  imgGap.style.top = top + 'px';  // 设置拖动图片的 left 和 top 值  imgBlock.style.top = top + 'px';  imgBlock.style.left = '0px';  imgBlock.style.backgroundPositionY = -top + 'px';  imgBlock.style.backgroundPositionX = -left + 'px';  // 初始化拖动图片块、空缺图块以及下面滑块的一些设置  imgGap.style.opacity = 1; // 空缺图块可见  imgBlock.style.opacity = 0; // 拖动图片块刚开始时不可见  btn.style.left = '-2px'; // 滑块位置初始化  span.style.opacity = 1; // 显示滑块区域的文字  // 初始化 title  title.style.color = 'black';  title.innerHTML = '请完成图片验证';  // 3. 绑定对应的事件  btn.onmousedown = function (e) {    // 设置拖动图块    imgBlock.style.opacity = 1; // 让拖动图块可见    imgBlock.style.transition = 'none'; // 关闭拖动图片块的过渡效果，让整个拖动更加的丝滑    // 设置标题    title.innerHTML = '拖动图片完成验证';    title.style.color = 'black';    // 设置滑动条的文字不可见    span.style.opacity = 0;    // 将按钮的过渡也关闭掉    btn.style.transition = 'none'; // 关闭按钮的过渡效果，让整个拖动更加的丝滑    // 为整个滑动条添加鼠标移动效果    slider.onmousemove = function (ev) {      // 接下来就会有一个很关键的点，我们需要得到用户移动鼠标时的最新的 left 值      let newLeft = ev.clientX - slider.offsetLeft - e.offsetX;      // 我们还需要进行一个边界判断      if (newLeft < -2) {        newLeft = -2;      }      if (newLeft > imgBox.offsetWidth - imgBlock.offsetWidth) {        newLeft = imgBox.offsetWidth - imgBlock.offsetWidth;      }      imgBlock.style.left = newLeft + 'px';      btn.style.left = newLeft + 'px';    };    // 设置鼠标抬起事件    document.onmouseup = function () {      // 当我们的鼠标抬起的时候，进行验证      let diffLeft = imgGap.offsetLeft - imgBlock.offsetLeft; // 获取两个图块的 left 差值      if (diffLeft < 5 && diffLeft > -5) {        // 验证成功        // 设置两个图块隐藏        imgBlock.style.opacity = 0;        imgGap.style.opacity = 0;        // 设置 title        title.innerHTML = '验证成功';        title.style.color = 'red';        // 删除所有的事件        slider.onmousemove = btn.onmousedown = document.onmouseup = null;      } else {        // 验证失败        // 设置拖动图块和按钮的 left 值        imgBlock.style.left = '0px';        btn.style.left = '-2px';        // 还需要添加上过渡        imgBlock.style.transition = 'all .5s';        btn.style.transition = 'all .5s';        slider.onmousemove = document.onmouseup = null;        // 设置 title        title.innerHTML = '验证失败';        title.style.color = 'green';        // 显示滑块区域的文字        span.style.opacity = 1;      }    };  };}init();$('.changeImg').onclick = init;]]></content>
    
    
      
      
    <summary type="html">// 封装一个 DOM 查询方法

function $(selector) {
  return document.querySelector(selector);
}

// 接下来我们来获取一下 DOM 节点
let imgBox = $(&#39;.imgBox&#39;); // 图片</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title></title>
    <link href="https://sunjinkang.github.io/2025/04/27/73-about-picture-validate/%E6%8B%96%E5%8A%A8%E9%AA%8C%E8%AF%81/html/index/"/>
    <id>https://sunjinkang.github.io/2025/04/27/73-about-picture-validate/%E6%8B%96%E5%8A%A8%E9%AA%8C%E8%AF%81/html/index/</id>
    <published>2025-04-27T07:15:48.178Z</published>
    <updated>2024-08-16T00:18:40.000Z</updated>
    
    <content type="html"><![CDATA[<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>图片拖动验证</title>    <style>      /* 整体容器的样式 */      .container {        width: 280px;        height: 400px;        margin: 100px auto;      }      /* 上方的图片不好看切换一张的样式 */      .container .changeImg {        text-align: center;        position: relative;        font-size: 16px;        color: rgb(126, 57, 218);        font-weight: bolder;        cursor: pointer;        user-select: none;      }      /* 前面的图标 */      .changeImg::before {        content: '';        display: block;        position: absolute;        left: 10%;        top: calc(50% - 13px);        width: 26px;        height: 26px;        background: url('../img/sx.png') no-repeat;        background-size: cover;        background-position: center;      }      /* img 容器整体样式，包含标题 + 图片 + 滑条 */      .imgContainer {        height: 320px;        box-sizing: border-box;        padding: 15px;        border: 1px solid #adadad;        box-shadow: 0px 0px 2px #adadad;        /* 设置盒子阴影 */        border-radius: 15px;        /* 设置圆角 */      }      /* 上方标题 */      .imgContainer h3 {        text-align: center;        margin: 0;        margin-bottom: 10px;      }      /* 中间的图片 */      .imgContainer .imgBox {        width: 100%;        height: 200px;        background-repeat: no-repeat;        position: relative;      }      /* 可以拖动的图片 */      .imgBox .imgBlock {        width: 50px;        height: 50px;        position: absolute;        z-index: 10;        opacity: 0;      }      /* 图片缺口 */      .imgBox .imgGap {        width: 50px;        height: 50px;        position: absolute;        background-color: white;        box-shadow: 0px 0px 3px #adadad;        /* 设置盒子阴影 */      }      /* 滑动条 */      .slider {        width: 100%;        height: 30px;        margin: auto;        margin-top: 15px;        background-color: #ddd;        border-radius: 10px;        position: relative;        text-align: center;        line-height: 30px;        font-size: 14px;        font-weight: 200;      }      /* 滑动条的按钮 */      .slider button {        position: absolute;        top: -5px;        left: -2px;        background: white url('../img/yz.png') no-repeat;        background-size: 100%;        border-radius: 50%;        border: 0;        width: 40px;        height: 40px;        cursor: pointer;        z-index: 20;      }      /* 滑动条文字的样式 */      .slider span {        transition: all 0.5s;      }    </style>  </head>  <body>    <div class="container">      <div class="imgContainer">        <h3>请完成图片验证</h3>        <!-- 图片区域 -->        <div class="imgBox">          <!-- 可以拖动的图片块 -->          <div class="imgBlock"></div>          <!-- 需要填补的图片缺口 -->          <div class="imgGap"></div>        </div>        <!-- 滑动块 -->        <div class="slider">          <span>拖动滑块完成拼图</span>          <button type="button" id="btn"></button>        </div>      </div>    </div>    <script>      let imgBox = document.querySelector('.imgBox');      let imgBlock = document.querySelector('.imgBlock');      let imgGap = document.querySelector('.imgGap');      let title = document.querySelector('.imgContainer h3');      let slider = document.querySelector('.slider');      let btn = document.querySelector('#btn');      let span = document.querySelector('.slider span');      function init() {        imgBox.style.backgroundImage = `url('../img/bg.jpg')`;        imgBlock.style.backgroundImage = `url('../img/bg.jpg')`;        // 计算拖动图块和空缺图块的位置        // 空缺图块可以设置的最大 top 值 = 盒子容器的高度 - 空缺图块的高度        let heightRange = imgBox.offsetHeight - imgBlock.offsetHeight;        // 空缺图块可以设置的最大 left 值 = 盒子容器的宽度的一半 - 空缺图块的宽度        let widthRange = imgBox.offsetWidth / 2 - imgBlock.offsetWidth;        // 生成随机的 left 和 top 值        let top = Math.random() * heightRange;        let left = Math.random() * widthRange + imgBox.offsetWidth / 2;        // 设置图片缺口的 left 和 top 值        imgGap.style.left = left + 'px';        imgGap.style.top = top + 'px';        // 设置拖动图片的 left 和 top 值        imgBlock.style.top = top + 'px';        imgBlock.style.left = '0px';        imgBlock.style.backgroundPositionY = -top + 'px';        imgBlock.style.backgroundPositionX = -left + 'px';        // 初始化拖动图片块、空缺图块以及下面滑块的一些设置        imgGap.style.opacity = 1;        imgBlock.style.opacity = 1;        btn.style.left = '-2px';        span.style.opacity = 1;        // 初始化 title        title.style.color = 'black';        title.innerHTML = '请完成图片验证';        // 3. 绑定对应的事件        btn.onmousedown = function (e) {          // 设置拖动图块          imgBlock.style.transition = 'none';          // 设置标题          title.innerHTML = '拖动图片完成验证';          title.style.color = 'black';          // 设置滑动条的文字不可见          span.style.opacity = 0;          // 将按钮的过渡也关闭掉          btn.style.transition = 'none';          // 为整个滑动条添加鼠标移动效果          slider.onmousemove = function (ev) {            // 接下来就会有一个很关键的点，我们需要得到用户移动鼠标时的最新的 left 值            let newLeft = ev.clientX - slider.offsetLeft - e.offsetX;            // 我们还需要进行一个边界判断，保证图片和按钮回到初始位置            if (newLeft < -2) {              newLeft = -2;            }            if (newLeft > imgBox.offsetWidth - imgBlock.offsetWidth) {              newLeft = imgBox.offsetWidth - imgBlock.offsetWidth;            }            imgBlock.style.left = newLeft + 'px';            btn.style.left = newLeft + 'px';          };          // 设置鼠标抬起事件          document.onmouseup = function () {            // 当我们的鼠标抬起的时候，进行验证            let diffLeft = imgGap.offsetLeft - imgBlock.offsetLeft; // 获取两个图块的 left 差值            if (diffLeft < 5 && diffLeft > -5) {              // 差值在5以内，验证成功，隐藏两个图块              imgBlock.style.opacity = 0;              imgGap.style.opacity = 0;              // 设置 title              title.innerHTML = '验证成功';              title.style.color = 'red';              // 删除所有的事件              slider.onmousemove = btn.onmousedown = document.onmouseup = null;            } else {              // 验证失败              // 设置拖动图块和按钮的 left 值              imgBlock.style.left = '0px';              btn.style.left = '-2px';              // 还需要添加上过渡              imgBlock.style.transition = 'all .5s';              btn.style.transition = 'all .5s';              slider.onmousemove = document.onmouseup = null;              // 设置 title              title.innerHTML = '验证失败';              title.style.color = 'green';              // 显示滑块区域的文字              span.style.opacity = 1;            }          };        };      }      init();    </script>  </body></html>]]></content>
    
    
      
      
    <summary type="html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot; /&gt;
    &lt;meta</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title></title>
    <link href="https://sunjinkang.github.io/2025/04/27/73-about-picture-validate/%E6%8B%96%E5%8A%A8%E9%AA%8C%E8%AF%81/css/index/"/>
    <id>https://sunjinkang.github.io/2025/04/27/73-about-picture-validate/%E6%8B%96%E5%8A%A8%E9%AA%8C%E8%AF%81/css/index/</id>
    <published>2025-04-27T07:15:48.175Z</published>
    <updated>2023-08-27T05:53:04.000Z</updated>
    
    <content type="html"><![CDATA[/* 整体容器的样式 */.container {    width: 280px;    height: 400px;    margin: 100px auto;}/* 上方的图片不好看切换一张的样式 */.container .changeImg {    text-align: center;    position: relative;    font-size: 16px;    color: rgb(126, 57, 218);    font-weight: bolder;    cursor: pointer;    user-select: none;}/* 前面的图标 */.changeImg::before {    content: '';    display: block;    position: absolute;    left: 10%;    top: calc(50% - 13px);    width: 26px;    height: 26px;    background: url('../img/sx.png') no-repeat;    background-size: cover;    background-position: center;}/* img 容器整体样式，包含标题 + 图片 + 滑条 */.imgContainer {    height: 320px;    box-sizing: border-box;    padding: 15px;    border: 1px solid #adadad;    box-shadow: 0px 0px 2px #adadad;    /* 设置盒子阴影 */    border-radius: 15px;    /* 设置圆角 */}/* 上方标题 */.imgContainer h3 {    text-align: center;    margin: 0;    margin-bottom: 10px;}/* 中间的图片 */.imgContainer .imgBox {    width: 100%;    height: 200px;    background-repeat: no-repeat;    position: relative;}/* 可以拖动的图片 */.imgBox .imgBlock {    width: 50px;    height: 50px;    position: absolute;    z-index: 10;    opacity: 0;}/* 图片缺口 */.imgBox .imgGap {    width: 50px;    height: 50px;    position: absolute;    background-color: white;    box-shadow: 0px 0px 3px #adadad;    /* 设置盒子阴影 */}/* 滑动条 */.slider {    width: 100%;    height: 30px;    margin: auto;    margin-top: 15px;    background-color: #ddd;    border-radius: 10px;    position: relative;    text-align: center;    line-height: 30px;    font-size: 14px;    font-weight: 200;}/* 滑动条的按钮 */.slider button {    position: absolute;    top: -5px;    left: -2px;    background: white url('../img/yz.png') no-repeat;    background-size: 100%;    border-radius: 50%;    border: 0;    width: 40px;    height: 40px;    cursor: pointer;    z-index: 20;}/* 滑动条文字的样式 */.slider span {    transition: all .5s;}]]></content>
    
    
      
      
    <summary type="html">/* 整体容器的样式 */

.container {
    width: 280px;
    height: 400px;
    margin: 100px auto;
}

/* 上方的图片不好看切换一张的样式 */

.container .changeImg {
 </summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>如何实现一个图片验证？</title>
    <link href="https://sunjinkang.github.io/2025/04/27/73-about-picture-validate/"/>
    <id>https://sunjinkang.github.io/2025/04/27/73-about-picture-validate/</id>
    <published>2025-04-27T07:05:58.000Z</published>
    <updated>2025-04-27T07:15:18.743Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>相信大家都遇到过拖动某块拼图到对应的位置，拖到大致和缺口重合的位置之后，图片验证通过，可以进行后续的操作，那么这种效果是怎么实现的？下面我们来看一下具体的实现。</p><p><img src="demo.png" class="lazyload" data-srcset="demo.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="demo"></p><h2 id="相关知识"><a href="#相关知识" class="headerlink" title="相关知识"></a>相关知识</h2><p>CSS Sprite</p><blockquote><p>CSS Sprite，我们一般叫他雪碧图或精灵图，它是一种图像拼合技术。该方法是将小图标和背景图像合并到一张图片上，然后利用 css 的背景定位来显示需要显示的图片部分。</p></blockquote><p>精灵图的优点：</p><ul><li>多张图片合成一张，减少图片的字节 </li><li>减少网页的http请求，从而大大的提高页面的性能。<br>background-position<br>background-position有两个值，第一个值是水平位置，第二个值是垂直位置。即需要将背景图向右调的时候用正值，向左则为负值 同理将背景图上下调动的时候上是用负值，下是正值。<br>offsetLeft：该元素左外边框至窗口左边界的距离。<br>clientX：当事件被触发时鼠标指针相对于窗口左边界的水平坐标。<br>offsetX：当事件被触发时鼠标指针相对于所触发的标签元素的左内边框的水平坐标。<br><img src="client.png" class="lazyload" data-srcset="client.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="client"><br><img src="client-1.png" class="lazyload" data-srcset="client-1.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="client-1"></li></ul><h2 id="图片验证的原理"><a href="#图片验证的原理" class="headerlink" title="图片验证的原理"></a>图片验证的原理</h2><p>图片验证核心其实就是按钮的拖动，也就是位置的计算： </p><ul><li>设置两层图片，在下层图片中设置一个缺口，用于匹配验证 </li><li>将上层的图片通过background-position以及其他一些css样式设置，保证只露出和下层缺口一样大小和对应区域的显示 </li><li>通过js控制上层图片的移动，并对比上层图片和下层缺口的位置数据，当两者位置相同或在允许的误差范围内时，即判定验证成功。<br><img src="offset.png" class="lazyload" data-srcset="offset.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="offset"></li></ul><h2 id="图片验证的实现"><a href="#图片验证的实现" class="headerlink" title="图片验证的实现"></a>图片验证的实现</h2><ul><li>html代码<ul><li><img src="html.png" class="lazyload" data-srcset="html.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="html"></li></ul></li><li>css代码<ul><li><img src="css.png" class="lazyload" data-srcset="css.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="css"></li></ul></li><li>js代码<ul><li><img src="js-1.png" class="lazyload" data-srcset="js-1.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="js-1"></li><li><img src="js-2.png" class="lazyload" data-srcset="js-2.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="js-2"></li></ul></li></ul><p><em>用户还可以根据自身需要进行进一步的扩展，比如设置多张验证图片，每次随机显示不同的图片等等。</em></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;相信大家都遇到过拖动某块拼图到对应的位置，拖到大致和缺口重合的位置之后，图片验证通过，可以进行后续的操作，那么这种效果是怎么实现的？下面我们来看一下具体的实现。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;demo.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;demo.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;demo&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;相关知识&quot;&gt;&lt;a href=&quot;#相关知识&quot; class=&quot;headerlink&quot; title=&quot;相关知识&quot;&gt;&lt;/a&gt;相关知识&lt;/h2&gt;&lt;p&gt;CSS Sprite&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>前端常见的安全攻击</title>
    <link href="https://sunjinkang.github.io/2025/04/27/71-about-front-attack/"/>
    <id>https://sunjinkang.github.io/2025/04/27/71-about-front-attack/</id>
    <published>2025-04-27T05:20:18.000Z</published>
    <updated>2025-04-27T05:27:41.231Z</updated>
    
    <content type="html"><![CDATA[<h4 id="XSS攻击"><a href="#XSS攻击" class="headerlink" title="XSS攻击"></a>XSS攻击</h4><ul><li>定义：跨站脚本攻击（Cross Site Scripting），一种经常出现在web应用中的计算机安全漏洞，允许恶意web用户将代码植入到给其他用户使用的页面中</li><li>危害：<ul><li>盗取各类用户账号，如网银、网站管理员账号</li><li>读取、串改、添加、删除企业敏感数据</li><li>非法转账</li><li>控制受害者机器向其他网站发起攻击</li></ul></li><li>攻击方式：<ul><li>存储型攻击：XSS代码被插入到类似个人信息、网站留言、评论、脚本等存储在服务器上的信息中</li><li>反射型攻击：脚本隐藏在URL中，欺骗用户自己去点击链接，从而触发隐藏的XSS代码，一般出现在搜索页面、用户登陆口，用以窃取cookie或进行钓鱼欺骗</li><li>DOM型攻击：客户端脚本通过DOM的冬天检查修改页面内容，不依赖服务器端数据，仅在客户端执行</li></ul></li><li>防范手段<ul><li>陌生的链接尽量不要点，使用正规网站</li><li>对内容进行转义或过滤，使恶意脚本失效</li><li>设置HttpOnly属性，限制恶意脚本的访问</li></ul></li></ul><h4 id="CSRF攻击"><a href="#CSRF攻击" class="headerlink" title="CSRF攻击"></a>CSRF攻击</h4><ul><li>定义：跨站请求伪造（Cross-site request forgery）,黑客引诱用户打开黑客的网站，在黑客的网站中，利用用户登录状态发起跨站请求</li><li>危害：<ul><li>获取用户的隐私数据</li><li>盗用用户身份，发送恶意请求，如转账、发消息、发邮件等</li><li>配合其他漏洞发起攻击</li></ul></li><li>攻击方式<ul><li>登录网站后，在未登出的情况下，打开危险网站，导致信息被窃取，危险网站依据获取的信息发起请求</li></ul></li><li>防范手段<ul><li>检查请求头中origin属性和referer属性，明确请求是否从本站发出</li><li>加强Token验证</li></ul></li></ul><h4 id="点击劫持"><a href="#点击劫持" class="headerlink" title="点击劫持"></a>点击劫持</h4><ul><li>定义：一种视觉上的欺骗手段。攻击者使用透明、不可见的iframe覆盖在网页上，诱导用户进行操作</li><li>危害<ul><li>泄露信息</li><li>在不知情的情况下，进行了一些未知操作</li></ul></li><li>攻击方式<ul><li>Flash点击劫持，网页上的一些需要用户点击的小游戏</li><li>覆盖攻击，比如将页面中的图片、信息覆盖为攻击者展示的信息</li><li>拖拽劫持，用户使用拖拽功能时，将敏感信息拖拽到攻击者想要的地方</li><li>手机触屏劫持</li></ul></li><li>防范手段<ul><li>网页禁止iframe嵌套</li><li>请求头设置X-FRAME-OPTIONS，防御利用iframe嵌套的攻击<ul><li>DENY:拒绝任何域加载</li><li>SAMEORIGIN允许同源域加载</li><li>ALLOW-FROM：定义允许frame加载的页面地址</li></ul></li></ul></li></ul><h4 id="SQL注入"><a href="#SQL注入" class="headerlink" title="SQL注入"></a>SQL注入</h4><ul><li>定义：在数据交互中，后台服务器接收前端传入的数据时，未做严格判断，导致数据在拼接到SQL语句中后，被当做语句的一部分执行，导致数据库受损</li><li>危害<ul><li>数据库被恶意操作、远程控制、安装后门、信息泄露</li><li>篡改网页，传播恶意信息、软件</li><li>破坏硬盘数据，瘫痪系统</li></ul></li><li>攻击方式<ul><li>变量类型：数字型、字符型</li><li>HTTP提交方式：GET请求query string、POST请求body、cookie</li><li>报错注入：<ul><li>报错注入：制造错误条件，使得查询结果出现在错误信息中</li><li>盲注：前端无法看到，需要利用一些辅助方法进行判断，比如返回booean值的布尔盲注，返回延时sleep()的时间盲注</li><li>堆叠注入：多条SQL语句一起执行</li></ul></li></ul></li><li>防范手段<ul><li>使用预编译语句集（内置了处理SQL注入的能力）</li><li>使用正则表达式过滤传入的参数</li><li>字符串过滤</li></ul></li></ul><h4 id="OS命令注入攻击"><a href="#OS命令注入攻击" class="headerlink" title="OS命令注入攻击"></a>OS命令注入攻击</h4><ul><li>定义：通过web应用，执行非法的操作系统命令，以达到攻击目的</li><li>危害<ul><li>执行未授权的代码或命令，读取或修改攻击者无权访问的数据</li></ul></li><li>攻击方式<ul><li>应用程序通过用户输入的参数构造OS命令</li></ul></li><li>防范手段<ul><li>以最小权限运行程序</li><li>考虑使用把名单过滤OS命令</li><li>尽可能使用库或框架构建程序</li></ul></li></ul>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;XSS攻击&quot;&gt;&lt;a href=&quot;#XSS攻击&quot; class=&quot;headerlink&quot; title=&quot;XSS攻击&quot;&gt;&lt;/a&gt;XSS攻击&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;定义：跨站脚本攻击（Cross Site Scripting），一种经常出现在web应用中的计算机安全漏洞，允许恶意web用户将代码植入到给其他用户使用的页面中&lt;/li&gt;
&lt;li&gt;危害：&lt;ul&gt;
&lt;li&gt;盗取各类用户账号，如网银、网站管理员账号&lt;/li&gt;
&lt;li&gt;读取、串改、添加、删除企业敏感数据&lt;/li&gt;
&lt;li&gt;非法转账&lt;/li&gt;
&lt;li&gt;控制受害者机器向其他网站发起攻击&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;攻击方式：&lt;ul&gt;
&lt;li&gt;存储型攻击：XSS代码被插入到类似个人信息、网站留言、评论、脚本等存储在服务器上的信息中&lt;/li&gt;
&lt;li&gt;反射型攻击：脚本隐藏在URL中，欺骗用户自己去点击链接，从而触发隐藏的XSS代码，一般出现在搜索页面、用户登陆口，用以窃取cookie或进行钓鱼欺骗&lt;/li&gt;
&lt;li&gt;DOM型攻击：客户端脚本通过DOM的冬天检查修改页面内容，不依赖服务器端数据，仅在客户端执行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;防范手段&lt;ul&gt;
&lt;li&gt;陌生的链接尽量不要点，使用正规网站&lt;/li&gt;
&lt;li&gt;对内容进行转义或过滤，使恶意脚本失效&lt;/li&gt;
&lt;li&gt;设置HttpOnly属性，限制恶意脚本的访问&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;CSRF攻击&quot;&gt;&lt;a href=&quot;#CSRF攻击&quot; class=&quot;headerlink&quot; title=&quot;CSRF攻击&quot;&gt;&lt;/a&gt;CSRF攻击&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;定义：跨站请求伪造（Cross-site request forgery）,黑客引诱用户打开黑客的网站，在黑客的网站中，利用用户登录状态发起跨站请求&lt;/li&gt;
&lt;li&gt;危害：&lt;ul&gt;
&lt;li&gt;获取用户的隐私数据&lt;/li&gt;
&lt;li&gt;盗用用户身份，发送恶意请求，如转账、发消息、发邮件等&lt;/li&gt;
&lt;li&gt;配合其他漏洞发起攻击&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;攻击方式&lt;ul&gt;
&lt;li&gt;登录网站后，在未登出的情况下，打开危险网站，导致信息被窃取，危险网站依据获取的信息发起请求&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;防范手段&lt;ul&gt;
&lt;li&gt;检查请求头中origin属性和referer属性，明确请求是否从本站发出&lt;/li&gt;
&lt;li&gt;加强Token验证&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;点击劫持&quot;&gt;&lt;a href=&quot;#点击劫持&quot; class=&quot;headerlink&quot; title=&quot;点击劫持&quot;&gt;&lt;/a&gt;点击劫持&lt;/h4&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>http协议请求类型</title>
    <link href="https://sunjinkang.github.io/2025/04/27/70-about-http-type/"/>
    <id>https://sunjinkang.github.io/2025/04/27/70-about-http-type/</id>
    <published>2025-04-27T05:14:58.000Z</published>
    <updated>2025-04-27T05:19:42.113Z</updated>
    
    <content type="html"><![CDATA[<p>HTTP1.0定义了三种请求方法： GET, POST 和 HEAD方法</p><p>HTTP1.1新增了五种请求方法：OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法</p><p>幂等：如果使用同一种请求方法的多个等效请求对服务器的预期影响，与一个等效请求对服务器的预期影响一样，那么这种请求方法被认为是幂等的。</p><h4 id="GET（幂等）"><a href="#GET（幂等）" class="headerlink" title="GET（幂等）"></a>GET（幂等）</h4><ul><li>一般用于获取/查询资源信息，比如搜索排序、筛选，只能提交字符型数据</li><li>提交的数据会在地址栏显示出来</li><li>由于浏览器对地址栏长度的限制而导致传输的数据有限制（所谓的请求长度限制是由浏览器和 web 服务器决定和设置的，各种浏览器和 web 服务器的设定均不⼀样，这依赖于各个浏览器⼚家的规定或者可以根据 web 服务器的处理能⼒来设定）<ul><li>IE：2083个字符</li><li>Firefox：65536个字符</li><li>Safari：80000个字符</li><li>Opera：190000个字符</li><li>google：8182个字符</li></ul></li><li>请求之后，结果会被浏览器缓存收纳，后续请求相同内容，从缓存中获取</li></ul><h4 id="POST（非幂等）"><a href="#POST（非幂等）" class="headerlink" title="POST（非幂等）"></a>POST（非幂等）</h4><ul><li>一般用于更新资源信息，可以提交任何类型的数据，如文件、视频、音频等</li><li>请求数据放在http包的包体中不会在地址栏显示出来</li><li>无传输数据限制</li><li>每次都需要从服务器获取相关资源</li></ul><h4 id="PUT（幂等）"><a href="#PUT（幂等）" class="headerlink" title="PUT（幂等）"></a>PUT（幂等）</h4><ul><li>从客户端向服务器数据取代指定的文档内容</li><li>通常指定了资源的存放位置</li><li>修改数据内容，但是不增加数据种类，无论进行多少次PUT操作，资源不会增加</li><li>浏览器在发PUT请求前会先发OPTIONS请求进行预检，看服务端是否可以接受PUT请求，若可以就在响应头添加字段告诉浏览器可以继续发送PUT请求：response[“Access-Control-Allow-Methods”] = “PUT” </li><li>POST主要作用在一个集合资源上，而PUT主要作用在一个具体资源上</li></ul><h4 id="DELETE（幂等）"><a href="#DELETE（幂等）" class="headerlink" title="DELETE（幂等）"></a>DELETE（幂等）</h4><ul><li>删除服务器中URL标识的资源，DELETE是幂等的</li><li>多次操作不会产生多余的副作用</li><li>（返回什么状态码由什么决定？？）</li></ul><h4 id="OPTIONS（幂等）"><a href="#OPTIONS（幂等）" class="headerlink" title="OPTIONS（幂等）"></a>OPTIONS（幂等）</h4><ul><li>主要用途有两个：允许客户端查看服务器性能；获取服务器支持的HTTP请求方法（黑客经常使用的方法）</li><li>跨域之前发送嗅探请求，判断是否有对指定资源的访问权限</li></ul><h4 id="HEAD（幂等）"><a href="#HEAD（幂等）" class="headerlink" title="HEAD（幂等）"></a>HEAD（幂等）</h4><ul><li>类似于get请求，不过返回的响应中没有具体内容，用于获取报头，HEAD请求响应可以被缓存</li><li>可以用来获取请求中隐含的元信息，而无需传输实体本身</li><li>检查资源、超链接的有效性；检查网页是否被串改；用于自动搜索机器人获取网页的标志信息、RSS种子信息，或者传递安全认证信息</li></ul><h4 id="TRACE"><a href="#TRACE" class="headerlink" title="TRACE"></a>TRACE</h4><ul><li>回显服务器收到的请求，主要用于测试或诊断</li></ul><h4 id="CONNECT"><a href="#CONNECT" class="headerlink" title="CONNECT"></a>CONNECT</h4><ul><li>HTTP/1.1协议中预留给可以将链接改成管道方式的代理服务器</li><li>通常用于SSL加密服务器的链接与非加密的HTTP代理服务器的通信</li></ul><h4 id="PATCH-MOVE-COPY-LINK-UNLINK"><a href="#PATCH-MOVE-COPY-LINK-UNLINK" class="headerlink" title="PATCH/MOVE/COPY/LINK/UNLINK"></a>PATCH/MOVE/COPY/LINK/UNLINK</h4>]]></content>
    
    
    <summary type="html">&lt;p&gt;HTTP1.0定义了三种请求方法： GET, POST 和 HEAD方法&lt;/p&gt;
&lt;p&gt;HTTP1.1新增了五种请求方法：OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法&lt;/p&gt;
&lt;p&gt;幂等：如果使用同一种请求方法的多个等效请求对服务器的预期影响，与一个等效请求对服务器的预期影响一样，那么这种请求方法被认为是幂等的。&lt;/p&gt;
&lt;h4 id=&quot;GET（幂等）&quot;&gt;&lt;a href=&quot;#GET（幂等）&quot; class=&quot;headerlink&quot; title=&quot;GET（幂等）&quot;&gt;&lt;/a&gt;GET（幂等）&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;一般用于获取/查询资源信息，比如搜索排序、筛选，只能提交字符型数据&lt;/li&gt;
&lt;li&gt;提交的数据会在地址栏显示出来&lt;/li&gt;
&lt;li&gt;由于浏览器对地址栏长度的限制而导致传输的数据有限制（所谓的请求长度限制是由浏览器和 web 服务器决定和设置的，各种浏览器和 web 服务器的设定均不⼀样，这依赖于各个浏览器⼚家的规定或者可以根据 web 服务器的处理能⼒来设定）&lt;ul&gt;
&lt;li&gt;IE：2083个字符&lt;/li&gt;
&lt;li&gt;Firefox：65536个字符&lt;/li&gt;
&lt;li&gt;Safari：80000个字符&lt;/li&gt;
&lt;li&gt;Opera：190000个字符&lt;/li&gt;
&lt;li&gt;google：8182个字符&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;请求之后，结果会被浏览器缓存收纳，后续请求相同内容，从缓存中获取&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>前端鉴权的几种方式</title>
    <link href="https://sunjinkang.github.io/2025/04/27/69-about-front-token/"/>
    <id>https://sunjinkang.github.io/2025/04/27/69-about-front-token/</id>
    <published>2025-04-27T03:28:12.000Z</published>
    <updated>2025-04-27T05:13:20.234Z</updated>
    
    <content type="html"><![CDATA[<h4 id="HTTP-Basic-Authentication"><a href="#HTTP-Basic-Authentication" class="headerlink" title="HTTP Basic Authentication"></a>HTTP Basic Authentication</h4><ul><li>客户端向服务器请求数据，请求的内容可能是一个网页或者是一个ajax异步请求</li><li>服务器向客户端发送验证请求代码401</li><li>符合http1.0或1.1规范的客户端收到401返回值时，将自动弹出一个登录窗口，要求用户输入信息，一般为用户名和密码（注意：此时第一步的请求状态为peding）</li><li>用户输入信息后，将信息以BASE64加密方式加密，并将密文放入前一条请求信息中，新增：Authorization: Basic XXXXX（浏览器默认加密）</li><li>服务器收到上述请求信息后，将Authorization字段后的用户信息取出、解密，将解密后的信息与数据库信息进行比较验证，如果正确，根据请求，返回对应的请求资源</li><li>弊端：加密方式简单，加密可逆，请求头中附带加密信息，安全性较差，一般用于内网</li></ul><h4 id="session-cookie"><a href="#session-cookie" class="headerlink" title="session-cookie"></a>session-cookie</h4><ul><li>服务器在接受客户端首次访问时在服务器端创建seesion，然后保存seesion，为session生成一个唯一的标识字符串,然后在响应头中种下这个唯一标识字符串</li><li>签名。这一步只是对sid进行加密处理，服务端会根据这个密钥进行解密</li><li>浏览器中收到请求响应的时候会解析响应头，然后将sid保存在本地cookie中，浏览器在下次http请求的请求头中会带上该域名下的cookie信息</li><li>弊端：服务器在接受客户端请求时会去解析请求头cookie中的sid，然后根据这个sid去找服务器端保存的该客户端的session，然后判断该请求是否合法；不同客户端需要创建不同的seesion，session保存在服务器内存中，服务器内存消耗大；且基于cookie识别用户，易受到CSRF(跨站伪造攻击：攻击者通过伪造用户的浏览器的请求，向访问一个用户自己曾经认证访问过的网站发送出去，使目标网站接收并误以为是用户的真实操作而去执行命令。常用于盗取账号、转账、发送虚假消息等)攻击；不同服务器之间存在session共享问题</li></ul><h4 id="Token"><a href="#Token" class="headerlink" title="Token"></a>Token</h4><ul><li>令牌，用户首次登陆后，服务器生成一个token返给客户端，客户端在之后的请求中带上token请求数据即可</li><li>流程：<ul><li>客户端使用用户名密码登录</li><li>服务器收到请求，验证用户名与密码</li><li>验证成功，服务器签发一个token给客户端</li><li>客户端收到token后，存储起来，一般放在cookie或localstorage里</li><li>客户端之后的请求需要携带签发的token</li><li>服务器收到请求，验证token，验证成功，返回对应的请求数据，验证失败，返回401鉴权失败</li></ul></li><li>JWT（JSON Web Tokens）：<ul><li>服务器认证以后，生成一个 JSON 对象，发回给用户.之后用户与服务器通信的时候.服务器完全只靠这个对象认定用户身份</li><li>为了防止用户篡改数据，服务器在生成这个对象的时候，会加上签名。</li><li>jwt最大的特点就是: 服务器不保存任何 session 数据，从而比较容易实现扩展</li><li>数据结构：一个很长的字符串，中间用点（.）分隔成三个部分，即Header（头部）.Payload（负载）.Signature（签名）<ul><li>Header: 一个 JSON 对象，描述 JWT 的元数据，例如:{ “alg”: “HS256”,”typ”: “JWT”}.alg属性表示签名的算法.默认是 HMAC SHA256（写成 HS256）；typ属性表示这个令牌（token）的类型（type），JWT 令牌统一写为JWT。</li><li>头部的 JSON 对象使用 Base64URL 算法转成字符串</li><li>Payload: 部分也是一个 JSON 对象，用来存放实际需要传递的数据。这个 JSON 对象也要使用 Base64URL 算法转成字符串。（注意: JWT 默认是不加密的，任何人都可以读到，所以不要把秘密信息放在这个部分。）</li></ul></li><li>Signature: 部分是对前两部分的签名，防止数据篡改。 需要指定一个只有服务器知道的密钥（secret），再使用 Header 里面指定的签名算法（默认是 HMAC SHA256）进行处理</li><li>JWT一旦签发了,不再受服务端控制，在服务端没有记录，是无状态的，是它最大的优点也是最大的缺点</li></ul></li><li>单点登录SSO：<ul><li>用户只需要登录一次就可以访问所有相互信任的应用系统</li><li>SSO一般都需要一个独立的认证中心（passport），子系统的登录均得通过passport，子系统本身将不参与登录操作，当一个系统成功登录以后，passport将会颁发一个令牌给各个子系统，子系统可以拿着令牌会获取各自的受保护资源</li></ul></li><li>不需要建立session会话，解决了session扩展性问题</li><li>弊端：token比session的sid大，消耗更多流量，相对需要挤占更多带宽；相对而言，token验证需要花费更多时间和性能，Token相比于session-cookie来说就是一个”时间换空间”的方案</li></ul><h4 id="OAuth（开放授权）"><a href="#OAuth（开放授权）" class="headerlink" title="OAuth（开放授权）"></a>OAuth（开放授权）</h4><ul><li>一种授权机制，开放标准：数据的所有者告诉系统，同意授权第三方应用进入系统，获取这些数据。系统从而产生一个短期的进入令牌（token），用来代替密码，供第三方应用使用。</li><li>流程：<ul><li>第三方项用户请求授权</li><li>返回用户凭证</li><li>请求授权服务器授权</li><li>授权服务器同意授权后，返回一个资源访问的凭证</li><li>三方应用通过第四步的凭证向资源服务器请求相关资源</li><li>资源服务器验证凭证通过后，将第三方应用请求的资源返回</li></ul></li><li>1.0与2.0的区别<ul><li>授权方式不同，互不兼容<ul><li>1.0：<ul><li>客户端到授权服务器请求一个授权令牌</li><li>引导用户到授权服务器请求授权</li><li>用访问令牌到授权服务器换取访问令牌</li><li>用访问令牌去访问得到授权的资源</li></ul></li><li>2.0<ul><li>用户到授权服务器，请求授权，然后返回授权码</li><li>客户端由授权码到授权服务器换取访问令牌</li><li>用访问令牌去访问得到授权的资源</li></ul></li></ul></li><li>1.0协议每个token都有一个加密，2.0则不需要，但2.0要求使用https协议</li><li>2.0充分考虑了客户端的各种子态，因而提供了多种途径获取访问令牌<ul><li>授权码：微信OAuth2.0</li><li>简化模式：QQ第三方登录</li><li>密码模式</li><li>客户端模式</li></ul></li></ul></li></ul>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;HTTP-Basic-Authentication&quot;&gt;&lt;a href=&quot;#HTTP-Basic-Authentication&quot; class=&quot;headerlink&quot; title=&quot;HTTP Basic Authentication&quot;&gt;&lt;/a&gt;HTTP Basic Authentication&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;客户端向服务器请求数据，请求的内容可能是一个网页或者是一个ajax异步请求&lt;/li&gt;
&lt;li&gt;服务器向客户端发送验证请求代码401&lt;/li&gt;
&lt;li&gt;符合http1.0或1.1规范的客户端收到401返回值时，将自动弹出一个登录窗口，要求用户输入信息，一般为用户名和密码（注意：此时第一步的请求状态为peding）&lt;/li&gt;
&lt;li&gt;用户输入信息后，将信息以BASE64加密方式加密，并将密文放入前一条请求信息中，新增：Authorization: Basic XXXXX（浏览器默认加密）&lt;/li&gt;
&lt;li&gt;服务器收到上述请求信息后，将Authorization字段后的用户信息取出、解密，将解密后的信息与数据库信息进行比较验证，如果正确，根据请求，返回对应的请求资源&lt;/li&gt;
&lt;li&gt;弊端：加密方式简单，加密可逆，请求头中附带加密信息，安全性较差，一般用于内网&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;session-cookie&quot;&gt;&lt;a href=&quot;#session-cookie&quot; class=&quot;headerlink&quot; title=&quot;session-cookie&quot;&gt;&lt;/a&gt;session-cookie&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;服务器在接受客户端首次访问时在服务器端创建seesion，然后保存seesion，为session生成一个唯一的标识字符串,然后在响应头中种下这个唯一标识字符串&lt;/li&gt;
&lt;li&gt;签名。这一步只是对sid进行加密处理，服务端会根据这个密钥进行解密&lt;/li&gt;
&lt;li&gt;浏览器中收到请求响应的时候会解析响应头，然后将sid保存在本地cookie中，浏览器在下次http请求的请求头中会带上该域名下的cookie信息&lt;/li&gt;
&lt;li&gt;弊端：服务器在接受客户端请求时会去解析请求头cookie中的sid，然后根据这个sid去找服务器端保存的该客户端的session，然后判断该请求是否合法；不同客户端需要创建不同的seesion，session保存在服务器内存中，服务器内存消耗大；且基于cookie识别用户，易受到CSRF(跨站伪造攻击：攻击者通过伪造用户的浏览器的请求，向访问一个用户自己曾经认证访问过的网站发送出去，使目标网站接收并误以为是用户的真实操作而去执行命令。常用于盗取账号、转账、发送虚假消息等)攻击；不同服务器之间存在session共享问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;Token&quot;&gt;&lt;a href=&quot;#Token&quot; class=&quot;headerlink&quot; title=&quot;Token&quot;&gt;&lt;/a&gt;Token&lt;/h4&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title></title>
    <link href="https://sunjinkang.github.io/2025/04/27/68-about-use-console-test/about_console/"/>
    <id>https://sunjinkang.github.io/2025/04/27/68-about-use-console-test/about_console/</id>
    <published>2025-04-27T03:25:41.184Z</published>
    <updated>2025-04-27T03:25:41.185Z</updated>
    
    <content type="html"><![CDATA[<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>Document</title>  </head>  <body></body>  <script>    // 1.console.log    let name = 'Tom';    let age = 18;    console.log(name); // Tom    console.log(age); // 18    console.log('Name: %s, Age: %d', name, age); // Name: Tom, Age: 18    console.log('My Name is %cTom', 'color: skyblue; font-size: 30px;');    console.log(      '%c ',      'background-image:url("http://iyeslogo.orbrand.com/150902Google/005.gif");background-size:120% 120%;background-repeat:no-repeat;background-position:center center;line-height:60px;padding:30px 120px;'    );    console.log('%o', document.body);    console.log('%O', document.body);    console.log(      '--------------------------------------------------------------------------------'    );    // 2.console.warn    let name1 = 'Tom';    let age1 = 18;    console.warn(name1); // Tom    console.warn(age1); // 18    console.warn('Name: %s, Age: %d', name1, age1); // Name: Tom, Age: 18    console.warn('My Name is %cTom', 'color: skyblue; font-size: 30px;');    console.warn(      '%c ',      'background-image:url("http://iyeslogo.orbrand.com/150902Google/005.gif");background-size:120% 120%;background-repeat:no-repeat;background-position:center center;line-height:60px;padding:30px 120px;'    );    console.warn('%o', document.body);    console.warn('%O', document.body);    console.warn(      '--------------------------------------------------------------------------------'    );    // 3.console.error    let name2 = 'Tom';    let age2 = 18;    console.error(name2); // Tom    console.error(age2); // 18    console.error('Name: %s, Age: %d', name2, age2); // Name: Tom, Age: 18    console.error('My Name is %cTom', 'color: skyblue; font-size: 30px;');    console.error(      '%c ',      'background-image:url("http://iyeslogo.orbrand.com/150902Google/005.gif");background-size:120% 120%;background-repeat:no-repeat;background-position:center center;line-height:60px;padding:30px 120px;'    );    console.error('%o', document.body);    console.error('%O', document.body);    function a() {      b();    }    function b() {      console.error('error');    }    function c() {      a();    }    c();    console.error(      '--------------------------------------------------------------------------------'    );    // 4. console.info    let name3 = 'Tom';    let age3 = 18;    console.info(name3); // Tom    console.info(age3); // 18    console.info('Name: %s, Age: %d', name3, age3); // Name: Tom, Age: 18    console.info('My Name is %cTom', 'color: skyblue; font-size: 30px;');    console.info(      '%c ',      'background-image:url("http://iyeslogo.orbrand.com/150902Google/005.gif");background-size:120% 120%;background-repeat:no-repeat;background-position:center center;line-height:60px;padding:30px 120px;'    );    console.info('%o', document.body);    console.info('%O', document.body);    console.info(      '--------------------------------------------------------------------------------'    );    // 5. console.time和console.timeEnd    console.time('timer1');    console.time('timer2');    setTimeout(() => {      console.timeEnd('timer1');    }, 1000);    setTimeout(() => {      console.timeEnd('timer2');    }, 2000);    // 6. console.timeLog    console.time('timer');    setTimeout(() => {      console.timeLog('timer');      setTimeout(() => {        console.timeLog('timer');        console.timeEnd('timer');      }, 2000);    }, 1000);    // 7. console.group & console.groupEnd    console.group('1');    console.log('1-1');    console.log('1-2');    console.log('1-3');    console.group('2');    console.log('2-1');    console.log('2-2');    console.groupEnd('2');    console.groupEnd('1');    // 8. console.groupCollapsed    console.groupCollapsed('1');    console.log('1-1');    console.log('1-2');    console.log('1-3');    console.groupCollapsed('2');    console.log('2-1');    console.log('2-2');    console.groupEnd('2');    console.groupEnd('1');    // 9. console.count    for (i = 0; i < 5; i++) {      console.count();    }    // 10. console.resetCount    console.count();    console.count('a');    console.count('b');    console.count('a');    console.count('a');    console.count();    console.count();    console.countReset();    console.countReset('a');    console.countReset('b');    console.count();    console.count('a');    console.count('b');    // 11. console.table    let table = [      {        index: '1',        first: '2',        second: '3',      },      {        index: '6',        first: '7',        second: '8',      },      {        index: '11',        first: '12',        second: '13',      },    ];    console.table(table, ['index', 'first', 'second']);    // 12. console.assert    console.assert([1, 2, 3, 4, 5, 5].length < 3, 'Node count is > 100');    // 13. console.trace    function a() {      b();    }    function b() {      console.trace();    }    function c() {      a();    }    c();    // 14. console.dir    console.log(document.body);    console.dir(document.body);    // 15. console.dirxml    console.log(document.body);    console.dirxml(document.body);  </script></html>]]></content>
    
    
      
      
    <summary type="html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot; /&gt;
    &lt;meta</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>console调试技巧</title>
    <link href="https://sunjinkang.github.io/2025/04/27/68-about-use-console-test/"/>
    <id>https://sunjinkang.github.io/2025/04/27/68-about-use-console-test/</id>
    <published>2025-04-27T03:09:18.000Z</published>
    <updated>2025-04-27T03:25:23.068Z</updated>
    
    <content type="html"><![CDATA[<p><img src="console_api.png" class="lazyload" data-srcset="console_api.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="console-api"></p><h4 id="console-log"><a href="#console-log" class="headerlink" title="console.log"></a>console.log</h4><ul><li>最基本最常用，可以用在javascript代码的任何地方，在浏览器的控制台查看打印信息</li><li>console.log支持占位符形式的语法，在复杂输出时，保证模板和数据分离<ul><li>| 字符串 | %s |<br>| 整数    | %d或%i |<br>| 浮点数    | %f |<br>| 对象 |    %o或%O |<br>| CSS样式 |    %c |</li><li>打印样式可以用于显示图片，通过background-image方式引入</li><li>console.log还可以用于打印字符画,eg：<a href="https://www.zhihu.com/signin?next=/%EF%BC%8C%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%E5%AD%97%E7%AC%A6%E7%94%BB%E5%9C%A8%E7%BA%BF%E7%94%9F%E6%88%90%E5%B7%A5%E5%85%B7%EF%BC%8C%E5%B0%86%E7%94%9F%E6%88%90%E7%9A%84%E5%AD%97%E7%AC%A6%E7%B2%98%E8%B4%B4%E5%88%B0console.log()%E5%8D%B3%E5%8F%AF%E3%80%82%E5%9C%A8%E7%BA%BF%E5%B7%A5%E5%85%B7%EF%BC%9Amg2txt">https://www.zhihu.com/signin?next=%2F，可以使用字符画在线生成工具，将生成的字符粘贴到console.log()即可。在线工具：mg2txt</a> （<a href="http://www.degraeve.com/img2txt.php%EF%BC%89">http://www.degraeve.com/img2txt.php）</a></li><li>console.log打印对象时，若为普通object对象，%o与%O无区别。如果为DOM节点，使用 %o 打印的是DOM节点的内容，包含其子节点。而%O打印的是该DOM节点的对象属性</li><li><img src="consolelog.png" class="lazyload" data-srcset="consolelog.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="console-log"></li></ul></li></ul><h4 id="console-warn"><a href="#console-warn" class="headerlink" title="console.warn"></a>console.warn</h4><ul><li>用于在控制台输出警告信息。它的用法和console.log是完全一样的，只是显示的样式不太一样，信息最前面加一个黄色三角，表示警告</li><li><img src="consolewarn.png" class="lazyload" data-srcset="consolewarn.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="console-warn"></li></ul><h4 id="console-error"><a href="#console-error" class="headerlink" title="console.error"></a>console.error</h4><ul><li>用于在控制台输出错误信息,它的用法和console.log/console.warn是一样的，只是显示的样式不一样</li><li><img src="consoleerror.png" class="lazyload" data-srcset="consoleerror.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="console-error"></li><li>console.exception() 是 console.error() 的别名，功能相同。console.exception在大部分浏览器中不支持，目前Firefox支持</li><li>console.error和console.warn还可以用于打印函数调用栈，console对象还提供了专门的方法来打印函数的调用栈（console.trace()）</li></ul><h4 id="console-info-amp-console-debug"><a href="#console-info-amp-console-debug" class="headerlink" title="console.info &amp; console.debug"></a>console.info &amp; console.debug</h4><ul><li>console.info用来打印资讯类说明信息，和console.log()的用法一致</li><li>console.debug用于打印调试信息</li></ul><h4 id="console-time和console-timeEnd"><a href="#console-time和console-timeEnd" class="headerlink" title="console.time和console.timeEnd"></a>console.time和console.timeEnd</h4><ul><li>可以用于获取一段代码的执行时间</li><li>传递一个字符串参数，用于标记唯一的计时器，如果页面只有一个计时器，可不传。</li><li>没有timeEnd，计时器会一直存在</li></ul><h4 id="console-timeLog"><a href="#console-timeLog" class="headerlink" title="console.timeLog"></a>console.timeLog</h4><ul><li>与console.timeEnd类似，需要使用console.time启动计时器，传递一个字符串参数</li><li>不同点：console.timeLog打印计时器的当前时间，不清除计时器；console.timeEnd打印计时器当前时间，同时清除计时器</li></ul><h4 id="console-group-amp-console-groupEnd"><a href="#console-group-amp-console-groupEnd" class="headerlink" title="console.group &amp; console.groupEnd"></a>console.group &amp; console.groupEnd</h4><ul><li>打印分组信息，以console.group开始，console.groupEnd结束</li></ul><h4 id="console-groupCollapsed"><a href="#console-groupCollapsed" class="headerlink" title="console.groupCollapsed"></a>console.groupCollapsed</h4><ul><li>类似console.group，同样以console.groupEnd结束</li><li>默认打印的信息是折叠的</li></ul><h4 id="console-count"><a href="#console-count" class="headerlink" title="console.count"></a>console.count</h4><ul><li>获取当前代码执行的次数</li><li>传递一个参数标记次数，非必传，默认为default</li></ul><h4 id="console-countReset"><a href="#console-countReset" class="headerlink" title="console.countReset"></a>console.countReset</h4><ul><li>重置计数器，配合console.count使用，传递一个可选参数，参数与count的参数对应，若省略该参数，将重置默认default计数器为0</li></ul><h4 id="console-table"><a href="#console-table" class="headerlink" title="console.table"></a>console.table</h4><ul><li>将数组数据以表格形式进行展示</li><li>传递两个参数，第一个为打印的对象，第二个为需要打印的表格标题，注意该方法本身第一列会打印序号</li><li><img src="consoletable.png" class="lazyload" data-srcset="consoletable.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="console-table"></li><li>注意：console.table只能处理最多1000行</li></ul><h4 id="console-clear"><a href="#console-clear" class="headerlink" title="console.clear"></a>console.clear</h4><ul><li>清除控制台信息</li></ul><h4 id="console-assert"><a href="#console-assert" class="headerlink" title="console.assert"></a>console.assert</h4><ul><li>用于语句断言，当断言为 false时，则在控制台输出错误信息</li><li>传递两个参数：第一个为条件语句，结果为Boolean值，为false时触发错误信息；第二个参数为触发的错误信息，为任意类型</li></ul><h4 id="console-trace"><a href="#console-trace" class="headerlink" title="console.trace"></a>console.trace</h4><ul><li>打印当前执行代码在堆栈中的调用路径</li></ul><h4 id="console-dir"><a href="#console-dir" class="headerlink" title="console.dir"></a>console.dir</h4><ul><li>以类似文件树的形式显示指定JavaScript对象的属性</li><li>传递一个对象参数，打印该对象的所有属性和属性值，不传时无打印值</li><li>大多数情况下与console.log的打印结果一样，打印元素结构时差异较大，console.log打印的是元素的DOM结构，console.dir打印元素属性</li><li><img src="consoledir.png" class="lazyload" data-srcset="consoledir.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="console-dir"></li></ul><h4 id="console-dirxml"><a href="#console-dirxml" class="headerlink" title="console.dirxml"></a>console.dirxml</h4><ul><li>用于显示一个明确的XML/HTML元素的包括所有后代元素的交互树</li><li>如果无法作为一个element被显示，那么会以JavaScript对象的形式作为替代</li><li>对于XML和HTML元素调用console.log()和console.dirxml()是等价的</li><li><img src="consoledirxml.png" class="lazyload" data-srcset="consoledirxml.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="console-dirxml"></li></ul><h4 id="console-memory"><a href="#console-memory" class="headerlink" title="console.memory"></a>console.memory</h4><ul><li>console对象的一个属性，用来查看当前内存的使用情况</li><li>jsHeapSizeLimit：JS堆栈内存大小限制</li><li>totalJSHeapSize：可使用的内存</li><li>usedJSHeapSize：JS对象（包括V8引擎内部对象）占用的内存，不得大于totalJSHeapSize，若大于，则可能内存泄漏</li></ul><h4 id="console-timeStamp-amp-console-profile-amp-console-profileEnd"><a href="#console-timeStamp-amp-console-profile-amp-console-profileEnd" class="headerlink" title="console.timeStamp&amp;console.profile&amp;console.profileEnd"></a>console.timeStamp&amp;console.profile&amp;console.profileEnd</h4><ul><li>非标准特性，尽量不要在生产环境使用</li><li>console.timeStamp：向浏览器的 <a href="https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference">Performance</a> 或者 <a href="https://profiler.firefox.com/docs/#/">Waterfall</a> 工具添加一个标记。这样可以将代码中的一个点和其他在时间轴上已记录的事件相关联，例如布局事件和绘制事件等</li><li>console.profile&amp;console.profileEnd：开始记录性能描述信息，传递一个参数，用于记录对应的描述信息，并通过profileEnd清除对应信息</li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;img src=&quot;console_api.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;console_api.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;console-api&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;console-log&quot;&gt;&lt;a href=&quot;#console-log&quot; class=&quot;headerlink&quot; title=&quot;console.log&quot;&gt;&lt;/a&gt;console.log&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;最基本最常用，可以用在javascript代码的任何地方，在浏览器的控制台查看打印信息&lt;/li&gt;
&lt;li&gt;console.log支持占位符形式的语法，在复杂输出时，保证模板和数据分离&lt;ul&gt;
&lt;li&gt;| 字符串 | %s |&lt;br&gt;| 整数    | %d或%i |&lt;br&gt;| 浮点数    | %f |&lt;br&gt;| 对象 |    %o或%O |&lt;br&gt;| CSS样式 |    %c |&lt;/li&gt;
&lt;li&gt;打印样式可以用于显示图片，通过background-image方式引入&lt;/li&gt;
&lt;li&gt;console.log还可以用于打印字符画,eg：&lt;a href=&quot;https://www.zhihu.com/signin?next=/%EF%BC%8C%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%E5%AD%97%E7%AC%A6%E7%94%BB%E5%9C%A8%E7%BA%BF%E7%94%9F%E6%88%90%E5%B7%A5%E5%85%B7%EF%BC%8C%E5%B0%86%E7%94%9F%E6%88%90%E7%9A%84%E5%AD%97%E7%AC%A6%E7%B2%98%E8%B4%B4%E5%88%B0console.log()%E5%8D%B3%E5%8F%AF%E3%80%82%E5%9C%A8%E7%BA%BF%E5%B7%A5%E5%85%B7%EF%BC%9Amg2txt&quot;&gt;https://www.zhihu.com/signin?next=%2F，可以使用字符画在线生成工具，将生成的字符粘贴到console.log()即可。在线工具：mg2txt&lt;/a&gt; （&lt;a href=&quot;http://www.degraeve.com/img2txt.php%EF%BC%89&quot;&gt;http://www.degraeve.com/img2txt.php）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;console.log打印对象时，若为普通object对象，%o与%O无区别。如果为DOM节点，使用 %o 打印的是DOM节点的内容，包含其子节点。而%O打印的是该DOM节点的对象属性&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;consolelog.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;consolelog.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;console-log&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;console-warn&quot;&gt;&lt;a href=&quot;#console-warn&quot; class=&quot;headerlink&quot; title=&quot;console.warn&quot;&gt;&lt;/a&gt;console.warn&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;用于在控制台输出警告信息。它的用法和console.log是完全一样的，只是显示的样式不太一样，信息最前面加一个黄色三角，表示警告&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;consolewarn.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;consolewarn.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;console-warn&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title></title>
    <link href="https://sunjinkang.github.io/2025/04/27/67-about-front-end-layout/index/"/>
    <id>https://sunjinkang.github.io/2025/04/27/67-about-front-end-layout/index/</id>
    <published>2025-04-27T03:07:35.586Z</published>
    <updated>2025-04-27T03:07:35.587Z</updated>
    
    <content type="html"><![CDATA[<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta http-equiv="X-UA-Compatible" content="IE=edge">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title></head><!-- 圣杯布局 --><!-- <style>  body {    min-width: 550px;    font-weight: bold;    font-size: 20px;  }  #header, #footer {    background: rgba(29, 27, 27, 0.726);    text-align: center;    height: 60px;    line-height: 60px;    clear: both;  }  #container{      padding: 0 200px;      overflow: hidden;  }  .column{      height: 200px;      float: left;      position: relative;  }  #left{      width: 200px;      margin-left: -100%;      left: -200px;      background-color: aqua;  }  #right{      width: 200px;      margin-left: -200px;      right: -200px;      background-color: wheat;  }  #center{      width: 100%;      background-color: tomato;  }</style> --><!-- 双飞翼 --><!-- <style type="text/css">  .main>div{    float: left;    height: 500px;  }  .left {    width: 200px;    background: red;    margin-left: -100%;  }  .right{    width: 200px;    background: blue;    margin-left: -200px;  }  .middle{    width: 100%;    background: yellow;    }  .content{    margin-left: 200px;    margin-right: 200px;  }</style> --><body>  <!-- 弹性布局 -->  <!-- http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html -->  <!-- 响应式布局 -->  <!-- http://segmentfault.com/ -->  <!-- 三列布局 -- 圣杯布局 -->  <!-- <div id="header">#header</div>  <div id="container">    <div id="center" class="column">#center</div>    <div id="left" class="column">#left</div>    <div id="right" class="column">#right</div>  </div>  <div id="footer">#footer</div> -->  <!-- 三列布局 -- 双飞翼布局 --><!-- <div class="main"><div class="middle">      <div class="content">middle</div></div><div class="left">left</div><div class="right">right</div></div> --></body></html>]]></content>
    
    
      
      
    <summary type="html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot;&gt;
  &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot;&gt;
  &lt;meta name=&quot;viewp</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>前端常见的布局方式</title>
    <link href="https://sunjinkang.github.io/2025/04/27/67-about-front-end-layout/"/>
    <id>https://sunjinkang.github.io/2025/04/27/67-about-front-end-layout/</id>
    <published>2025-04-27T02:59:54.000Z</published>
    <updated>2025-04-27T03:07:04.588Z</updated>
    
    <content type="html"><![CDATA[<h4 id="静态布局"><a href="#静态布局" class="headerlink" title="静态布局"></a>静态布局</h4><p>最传统、原始的Web布局设计。网页最外层容器(outer)有固定的大小,所有的内容以该容器为标准,超出宽高的部分用滚动条(overflow:scroll)来实现滚动查阅</p><ul><li>优点：不存在浏览器兼容性。布局简单</li><li>缺点：不会随着屏幕大小而变化</li></ul><h4 id="流式布局（百分比布局）"><a href="#流式布局（百分比布局）" class="headerlink" title="流式布局（百分比布局）"></a>流式布局（百分比布局）</h4><p>随着屏幕的改变，页面的布局没有发生大的变化，可以进行适配调整<br>常见的类型：</p><ul><li><p>左侧固定+右侧自适应</p></li><li><p>左右固定宽度+中间自适应</p></li><li><p>优点：元素宽高按屏幕分辨率调整，布局不发生变化</p></li><li><p>缺点：屏幕尺度跨度过大的情况下，页面不能正常显示</p></li></ul><h4 id="弹性布局"><a href="#弹性布局" class="headerlink" title="弹性布局"></a>弹性布局</h4><p>使用css3新特性flex进行的布局</p><ul><li>优点：简单、方便、快速</li><li>缺点：CSS3新特性,存在浏览器兼容性问题</li><li><img src="flex_issue.png" class="lazyload" data-srcset="flex_issue.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="flex-issue"></li></ul><h4 id="自适应布局"><a href="#自适应布局" class="headerlink" title="自适应布局"></a>自适应布局</h4><p>分别为不同的屏幕分辨率定义布局。布局切换时页面元素发生改变，但在每个布局中，页面元素不随窗口大小的调整发生变化。 你可以把自适应布局看作是静态布局的一个系列。 就是说你看到的页面，里面元素的位置会变化而大小不会变化</p><ul><li>优点：对网站的复杂程度兼容性更大；对开发工程师来说制作的成本代价更低；代码执行效果更高效；测试时更加容易，运营相对更加精准</li><li>缺点：同一个网站往往需要为不同的设备制作不同的页面，不但会增加开发成本，还会因为客户的需求改变时，可能会改动多套代码、流程相比较来说较繁琐</li></ul><h4 id="响应式布局"><a href="#响应式布局" class="headerlink" title="响应式布局"></a>响应式布局</h4><p>不同的屏幕设置布局格式，当屏幕大小改变时，会出现不同的布局，意思就是在这个屏幕下这个元素块在这个地方，但是在那个屏幕下，这个元素块又会出现在那个地方。只是布局改变，元素不变。可以看成是不同屏幕下由多个静态布局组成的，自适应布局和流式布局的综合方式，为不同屏幕分辨率范围创建流式布局；利用媒体查询可以检测到屏幕的尺寸(主要检测宽度)，并设置不同的CSS样式，就可以实现响应式的布局</p><ul><li>优点：适应pc端和移动端，如果有足够的耐心，页面效果会很完美</li><li>缺点：只能适应主流的宽高；如果匹配足够多的设备屏幕的大小，对于工程师来说工作量不小，设计更需要多个版本，工作量增大</li><li><img src="adaption_react.png" class="lazyload" data-srcset="adaption_react.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="adaption-react"></li></ul><h4 id="三列布局"><a href="#三列布局" class="headerlink" title="三列布局"></a>三列布局</h4><ul><li>左右两边宽度固定，中间宽度自适应；中间列的内容可以完整显示<ul><li>定位实现<ul><li>左右两列绝对定位并且固定宽度，中间元素自适应，且左右margin设置为左右元素的宽度</li><li>缺点：当出现滚动条时，内容区在滚动条后边显示，而且内容区仍旧被压缩(不推荐使用)</li></ul></li><li>浮动实现<ul><li>左右进行浮动展示</li><li>缺点：如果有文字出现，布局就会错乱，导致扩展性不好</li></ul></li></ul></li><li>圣杯布局&amp;双飞翼布局<ul><li>随着页面宽度的变化，三栏布局中的中间盒子优先自适应渲染，两边盒子宽度固定不变</li><li>原理区别：<ul><li>圣杯布局，为了中间div内容不被遮挡，将中间div设置了左右padding-left和padding-right后，将左右两个div用相对布局position: relative并分别配合right和left属性，以便左右两栏div移动后不遮挡中间div</li><li>双飞翼布局，为了中间div内容不被遮挡，直接在中间div内部创建子div用于放置内容，在该子div里用margin-left和margin-right为左右两栏div留出位置</li></ul></li></ul></li></ul>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;静态布局&quot;&gt;&lt;a href=&quot;#静态布局&quot; class=&quot;headerlink&quot; title=&quot;静态布局&quot;&gt;&lt;/a&gt;静态布局&lt;/h4&gt;&lt;p&gt;最传统、原始的Web布局设计。网页最外层容器(outer)有固定的大小,所有的内容以该容器为标准,超出宽高的部分用滚动条(overflow:scroll)来实现滚动查阅&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点：不存在浏览器兼容性。布局简单&lt;/li&gt;
&lt;li&gt;缺点：不会随着屏幕大小而变化&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;流式布局（百分比布局）&quot;&gt;&lt;a href=&quot;#流式布局（百分比布局）&quot; class=&quot;headerlink&quot; title=&quot;流式布局（百分比布局）&quot;&gt;&lt;/a&gt;流式布局（百分比布局）&lt;/h4&gt;&lt;p&gt;随着屏幕的改变，页面的布局没有发生大的变化，可以进行适配调整&lt;br&gt;常见的类型：&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title></title>
    <link href="https://sunjinkang.github.io/2025/04/27/66-about-array-operation/filter_sort/"/>
    <id>https://sunjinkang.github.io/2025/04/27/66-about-array-operation/filter_sort/</id>
    <published>2025-04-27T02:28:54.268Z</published>
    <updated>2025-04-27T02:28:54.269Z</updated>
    
    <content type="html"><![CDATA[// 去重var array = [];for (var i = 0; i < 10000; i++) {  array.push(Math.floor(Math.random() * (100 - 1)) + 1);}console.log(array);// // 1// function uniq1(array){//   var start = new Date().getTime();//   var temp = [];//   for(var i = 0; i < array.length; i++){//       if(temp.indexOf(array[i]) == -1){//           temp.push(array[i]);//       }//   }//   var end = new Date().getTime();//   console.log(`1: ${temp}`);//   console.log(end - start);// };// uniq1(JSON.parse(JSON.stringify(array)));// // 2// function uniq2(array){//   var start = new Date().getTime();//   var temp = {}, result = [], len = array.length, val, type;//   for (var i = 0; i < len; i++) {//       val = array[i];//       type = typeof val;//       if (!temp[val]) {//           temp[val] = [type];//           result.push(val);//       } else if (temp[val].indexOf(type) < 0) {//           temp[val].push(type);//           result.push(val);//       }//   }//   var end = new Date().getTime();//   console.log(`2: ${result}`);//   console.log(end - start);// };// uniq2(JSON.parse(JSON.stringify(array)));// // 3// function uniq3(array){//   var start = new Date().getTime();//   array.sort();//   var temp=[array[0]];//   for(var i = 1; i < array.length; i++){//       if( array[i] !== temp[temp.length-1]){//           temp.push(array[i]);//       }//   }//   var end = new Date().getTime();//   console.log(`3: ${temp}`);//   console.log(end - start);// };// uniq3(JSON.parse(JSON.stringify(array)));// // 4// function uniq4(array){//   var start = new Date().getTime();//   var temp = [];//   for(var i = 0; i < array.length; i++) {//       if(array.indexOf(array[i]) == i){//           temp.push(array[i])//       }//   }//   var end = new Date().getTime();//   console.log(`4: ${temp}`);//   console.log(end - start);// };// uniq4(JSON.parse(JSON.stringify(array)));// // 5// function uniq5(array){//   var start = new Date().getTime();//   var temp = [];//   var index = [];//   var l = array.length;//   for(var i = 0; i < l; i++) {//       for(var j = i + 1; j < l; j++){//           if (array[i] === array[j]){//               i++;//               j = i;//           }//       }//       temp.push(array[i]);//       index.push(i);//   }//   var end = new Date().getTime();//   console.log(`5: ${temp}`);//   console.log(end - start);// };// uniq5(JSON.parse(JSON.stringify(array)));// // 6// function uniq6(array){//   var start = new Date().getTime();//   var result = [...new Set(array)];//   var end = new Date().getTime();//   console.log(`6: ${result}`);//   console.log(end - start);// }// uniq6(JSON.parse(JSON.stringify(array)));// // 7// function uniq7(arr){//   var start = new Date().getTime();//   var i, j, len = arr.length;//   for(i = 0; i < len; i++){//     for(j = i + 1; j < len; j++){//       if(arr[i] == arr[j]){//         arr.splice(j,1);//         len--;//         j--;//       }//     }//   }//   var end = new Date().getTime();//   console.log(`7: ${arr}`);//   console.log(end - start);// };// uniq7(JSON.parse(JSON.stringify(array)));// 排序// 1.sort// function sort1(array) {//   var start = new Date().getTime();//   array.sort(function(a,b) {//     return a - b;//升序//     // return b - a;//降序//   })//   var end = new Date().getTime();//   console.log(array);//   console.log(end - start);// };// sort1(JSON.parse(JSON.stringify(array)));// // 2.冒泡排序// function sort2(array) {//   var start = new Date().getTime();//   var b=0//设置用来调换位置的值//   for(var i=0;i<array.length;i++){//       for(var j=0;j<array.length;j++){//           if(array[j]>array[j+1]){//               b=array[j]//               array[j]=array[j+1]//               array[j+1]=b//           }//       }//   }//   var end = new Date().getTime();//   console.log(array);//   console.log(end - start);// };// sort2(JSON.parse(JSON.stringify(array)));// // 3.选择排序// function sort3(array) {//   var start = new Date().getTime();//   for(var i = 0; i < array.length - 1; i++){//   　　for(var j = i + 1; j < array.length; j++){//   　　　　if(array[i] > array[j]){//   　　　　　　var tmp = array[i];//   　　　　　　array[i] = array[j];//   　　　　　　array[j] = tmp;//   　　　　}//   　　}//   }//   var end = new Date().getTime();//   console.log(array);//   console.log(end - start);// };// sort3(JSON.parse(JSON.stringify(array)));// // 4.快速排序// function quick(array) {//   if (array.length<=1){return array}//   var middleIndex = Math.floor(array.length / 2);//   var middle = array.splice(middleIndex,1)[0];//   var leftArr = [];//   var rightArr = [];//   for (var i = 0; i < array.length; i++) {//       if (array[i] <= middle) {//           leftArr.push(array[i])//       } else if (array[i] > middle) {//           rightArr.push(array[i])//       }//   }//   return quick(leftArr).concat([middle],quick(rightArr))// };// function sort4(array) {//   var start = new Date().getTime();//   var result = quick(array);//   var end = new Date().getTime();//   console.log(result);//   console.log(end - start);// };// sort4(JSON.parse(JSON.stringify(array)));// 5.归并排序// function merge(left,right){//   let result = [];//   while(left.length >0 && right.length > 0){//       if(left[0]<right[0]){//           result.push(left.shift());//       }else{//           result.push(right.shift());//       }//   }//   return result.concat(left).concat(right);// }// function mergeSort(arr){//   if(arr.length == 1){//     return arr;//   }//   let mid = Math.floor(arr.length/2);//   let left_arr = arr.slice(0,mid);//   let right_arr = arr.slice(mid);//   return merge(mergeSort(left_arr),mergeSort(right_arr));// }// function sort5(array) {//   var start = new Date().getTime();//   var result = mergeSort(array);//   var end = new Date().getTime();//   console.log(result);//   console.log(end - start);// };// sort5(JSON.parse(JSON.stringify(array)));// 6.插入排序function sort6(array) {  var start = new Date().getTime();  let len = array.length;  let preIndex, current;  for (let i = 1; i < len; i++) {    preIndex = i - 1;    current = array[i];    while (preIndex >= 0 && current < array[preIndex]) {      array[preIndex + 1] = array[preIndex];      preIndex--;    }    array[preIndex + 1] = current;  }  var end = new Date().getTime();  console.log(array);  console.log(end - start);};sort6(JSON.parse(JSON.stringify(array)));// 7.桶排序function bucketSort(arr,bucketCount){  result = []  minValue = arr[0]  maxValue = arr[0]  for(let i=0;i<arr.length;i++){      if(arr[i]<minValue){          minValue = arr[i]      }      if(arr[i]>maxValue){          maxValue = arr[i]      }  }  bucketSize = Math.floor((maxValue-minValue)/bucketCount)+1  bucket = new Array(bucketSize)  for(let i=0;i<bucket.length;i++){      bucket[i] = []  }  for(let i=0;i<arr.length;i++){      bucket[Math.floor((arr[i]-minValue)/bucketCount)].push(arr[i])  }  for(let i=0;i<bucket.length;i++){      bucket[i].sort()      for(let j=0;j<bucket[i].length;j++){          result.push(bucket[i][j])      }  }  return result;}function sort7(array) {  var start = new Date().getTime();  var result = bucketSort(array, 100);  var end = new Date().getTime();  console.log(result);  console.log(end - start);};sort7(JSON.parse(JSON.stringify(array)));]]></content>
    
    
      
      
    <summary type="html">// 去重
var array = [];
for (var i = 0; i &lt; 10000; i++) {
  array.push(Math.floor(Math.random() * (100 - 1)) + 1);
}
console.log(array);

// /</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>数组的操作之去重、排序（去重和排序的方法）</title>
    <link href="https://sunjinkang.github.io/2025/04/27/66-about-array-operation/"/>
    <id>https://sunjinkang.github.io/2025/04/27/66-about-array-operation/</id>
    <published>2025-04-27T02:23:32.000Z</published>
    <updated>2025-04-27T02:46:18.888Z</updated>
    
    <content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>数组是大家常用的数据结果，数组中我们常常需要对数组的元素进行去重和排序，以下是关于数组的去重排序的一些介绍。</p><h2 id="去重"><a href="#去重" class="headerlink" title="去重"></a>去重</h2><h4 id="简单去重"><a href="#简单去重" class="headerlink" title="简单去重"></a>简单去重</h4><ul><li>新建一新数组，遍历传入数组，值不在新数组就push进该新数组中。</li><li><img src="1.png" class="lazyload" data-srcset="1.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="simple"></li></ul><h4 id="对象键值对去重"><a href="#对象键值对去重" class="headerlink" title="对象键值对去重"></a>对象键值对去重</h4><ul><li>新建一js对象以及新数组，遍历传入数组时，判断值是否为js对象的键，不是的话给对象新增该键并放入新数组。</li><li>注意点：判断是否为js对象键时，会自动对传入的键执行“toString()”，不同的键可能会被误认为一样，例如n[1]、n[“1”]。解决上述问题还是得调用“indexOf”等方法进行判断。</li><li><img src="2.png" class="lazyload" data-srcset="2.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="key-value"></li></ul><h4 id="排序相邻去重"><a href="#排序相邻去重" class="headerlink" title="排序相邻去重"></a>排序相邻去重</h4><ul><li>给传入数组排序，排序后相同值相邻，然后遍历时,新数组只加入不与前一值重复的值。</li><li>会打乱原来数组的顺序</li><li><img src="3.png" class="lazyload" data-srcset="3.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="sort1"></li></ul><h4 id="数组下标去重"><a href="#数组下标去重" class="headerlink" title="数组下标去重"></a>数组下标去重</h4><ul><li>如果当前数组的第i项在当前数组中第一次出现的位置不是i，那么表示第i项是重复的，忽略掉。否则存入结果数组。</li><li><img src="4.png" class="lazyload" data-srcset="4.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array-index"></li></ul><h4 id="优化便利数组去重"><a href="#优化便利数组去重" class="headerlink" title="优化便利数组去重"></a>优化便利数组去重</h4><ul><li>获取没重复的最右一值放入新数组，检测到有重复值时终止当前循环同时进入顶层循环的下一轮判断</li><li><img src="5.png" class="lazyload" data-srcset="5.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array5"></li></ul><h4 id="Set去重"><a href="#Set去重" class="headerlink" title="Set去重"></a>Set去重</h4><ul><li>Set数据结构，它类似于数组，其成员的值都是唯一的。利用Array.from将Set结构转换成数组。</li><li><img src="6.png" class="lazyload" data-srcset="6.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array-set"></li></ul><h4 id="splice操作原数组去重"><a href="#splice操作原数组去重" class="headerlink" title="splice操作原数组去重"></a>splice操作原数组去重</h4><ul><li>双层循环，外层循环元素，内层循环时比较值，值相同时，则删去这个值。注意点:删除元素之后，需要将数组的长度也减1。</li><li><img src="7.png" class="lazyload" data-srcset="7.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array-splice"></li></ul><p><em>注意：以上方法在比较是否存在相同值的时候，一般使用indexOf，可以实现类似逻辑的判断方法还有includes、hasOwnProperty。</em></p><h2 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h2><h4 id="sort方法"><a href="#sort方法" class="headerlink" title="sort方法"></a>sort方法</h4><ul><li>默认排序顺序是根据字符串UniCode码，这个方法值只能排序第一位数 也可以字符串进行排序，传入比较函数进行比较</li><li><img src="2-1.png" class="lazyload" data-srcset="2-1.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array-sort-1"></li></ul><h4 id="reverse倒序方法"><a href="#reverse倒序方法" class="headerlink" title="reverse倒序方法"></a>reverse倒序方法</h4><h4 id="冒泡排序"><a href="#冒泡排序" class="headerlink" title="冒泡排序"></a>冒泡排序</h4><ul><li>每轮依次比较相邻两个数的大小，后面比前面小则交换。</li><li><img src="2-2.png" class="lazyload" data-srcset="2-2.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array-sort-2"></li></ul><h4 id="选择排序"><a href="#选择排序" class="headerlink" title="选择排序"></a>选择排序</h4><ul><li>通过比较首先选出最小的数放在第一个位置上，然后在其余的数中选出次小数放在第二个位置上,依此类推,直到所有的数成为有序序列。</li><li><img src="2-3.png" class="lazyload" data-srcset="2-3.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array-sort-3"></li></ul><h4 id="快速排序"><a href="#快速排序" class="headerlink" title="快速排序"></a>快速排序</h4><ul><li>先从数列中取出一个数作为基准，分区过程，将比这个数大的数全放到它的右边，小于或等于它的数全放到它的左边，再对左右区间重复第二步，直到各区间只有一个数。</li><li><img src="2-4.png" class="lazyload" data-srcset="2-4.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array-sort-4"></li></ul><h4 id="归并排序"><a href="#归并排序" class="headerlink" title="归并排序"></a>归并排序</h4><ul><li>将两个或两个以上的有序表组合成一个新的有序表。假如初始序列含有n个记录，则可看成是n个有序的子序列，每个子序列的长度为1，然后两两归并，得到[n/2]（向上取整）个长度为2或1的有序子序列；再两两归并，……，如此重复，直到得到一个长度为n的有序序列为止</li><li>速度仅次于快速排序，为稳定排序算法，一般用于总体无序，但是各子项相对有序的数列</li><li><img src="2-5.png" class="lazyload" data-srcset="2-5.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array-sort-5"></li></ul><h4 id="插入排序"><a href="#插入排序" class="headerlink" title="插入排序"></a>插入排序</h4><ul><li>构建有序序列，对于未排序数据，在已排序序列中从后向前扫描，找到相应位置并插入。</li><li><img src="2-6.png" class="lazyload" data-srcset="2-6.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array-sort-6"></li></ul><h4 id="桶排序"><a href="#桶排序" class="headerlink" title="桶排序"></a>桶排序</h4><ul><li>假设输入数据服从均匀分布，将数据分到有限数量的桶里，每个桶再分别排序（有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序）</li><li><img src="2-7.png" class="lazyload" data-srcset="2-7.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="array-sort-7"></li></ul>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h4&gt;&lt;p&gt;数组是大家常用的数据结果，数组中我们常常需要对数组的元素进行去重和排序，以下是关于数组的去重排序的一些介绍。&lt;/p&gt;
&lt;h2 id=&quot;去重&quot;&gt;&lt;a href=&quot;#去重&quot; class=&quot;headerlink&quot; title=&quot;去重&quot;&gt;&lt;/a&gt;去重&lt;/h2&gt;&lt;h4 id=&quot;简单去重&quot;&gt;&lt;a href=&quot;#简单去重&quot; class=&quot;headerlink&quot; title=&quot;简单去重&quot;&gt;&lt;/a&gt;简单去重&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;新建一新数组，遍历传入数组，值不在新数组就push进该新数组中。&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;1.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;1.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;simple&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>关于xpath</title>
    <link href="https://sunjinkang.github.io/2025/04/27/65-about-xpath/"/>
    <id>https://sunjinkang.github.io/2025/04/27/65-about-xpath/</id>
    <published>2025-04-27T02:10:27.000Z</published>
    <updated>2025-04-27T02:11:19.351Z</updated>
    
    <content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>Xpath 是一种用在 XML 文档中定位元素的语言，同样也支持 HTML 元素的解析。这篇文章主要是关于HTML部分的内容，从这个名字可以看出来，Xpath是关于路径的，Xpath是通过路径查找元素。</p><h4 id="HTML中的Xpath"><a href="#HTML中的Xpath" class="headerlink" title="HTML中的Xpath"></a>HTML中的Xpath</h4><p><img src="html.png" class="lazyload" data-srcset="html.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="html"><br><img src="html-tree.png" class="lazyload" data-srcset="html-tree.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="html-tree"></p><p>HTML是一种树形结构，HTML 是根节点，所有的其他元素节点都是从根节点发出的。其他的元素都是这棵树上的节点Node，每个节点还可能有属性和文本。<br>而路径就是指某个节点到另一个节点的路线。</p><p>我们一般的路径可以分为绝对路径和相对路径。Xpath中的绝对路径从 HTML 根节点开始算，相对路径从任意节点开始。<br>在浏览器中，我们可以直接复制需要节点的Xpath数据。<br><img src="copy-path.png" class="lazyload" data-srcset="copy-path.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="copy-path"><br>chrome浏览器中复制的相对路径会从举例最近的有id的元素开始找。<br><img src="copy-path-result.png" class="lazyload" data-srcset="copy-path-result.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="copy-path-result"><br>从上面这个路径我们可以看到，虽然能根据这个路径找到对应的目标元素，但是这里面有div[1]这种结构，即父节点下面的第一个div，这种路径并不是很可靠。所以，需要我们自己根据需要自定义Xpath数据。</p><h4 id="绝对路径"><a href="#绝对路径" class="headerlink" title="绝对路径"></a>绝对路径</h4><p>绝对路径是从根节点/html开始往下，一层层的表示出来直到需要的节点为止，使用起来较为麻烦。这里就不多做介绍了，我们主要来看一下相对路径。</p><h4 id="相对路径"><a href="#相对路径" class="headerlink" title="相对路径"></a>相对路径</h4><p>Xpath 中更常用的方式是相对路径定位方法，以“//”开头。相对路径可以从任意节点开始，不过一般会选取一个可以唯一定位到的元素作为起始节点，可以增加查找的准确性。</p><h5 id="定位语法"><a href="#定位语法" class="headerlink" title="定位语法"></a>定位语法</h5><p>| 表达式 | 说明    | 举例 |<br>| / |    从根节点开始选取 |    /html/div/span |<br>| // |    从任意节点开始选取 |    //input |<br>| . |    选取当前节点 |  |<br>| .. |    选取当前节点的父节点 |    //input/.. 会选取 input 的父节点 |<br>| @ |    选取属性，或者根据属性选取 |    //input[@data] 选取具备 data 属性的 input 元素 //@data 选取所有 data 属性 |<br>| * |    通配符，表示任意节点或任意属性 |  |</p><h5 id="常见的定位方法"><a href="#常见的定位方法" class="headerlink" title="常见的定位方法"></a>常见的定位方法</h5><h6 id="元素属性定位"><a href="#元素属性定位" class="headerlink" title="元素属性定位"></a>元素属性定位</h6><p>属性定位是通过 @ 符号指定需要使用的属性。</p><p>根据元素是否具备某个属性查找元素<br><img src="at-sign.png" class="lazyload" data-srcset="at-sign.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="at-sign"></p><p>根据属性是否等于某值查找元素<br><img src="at-sign-1.png" class="lazyload" data-srcset="at-sign-1.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="at-sign-1"></p><p><img src="at-sign-2.png" class="lazyload" data-srcset="at-sign-2.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="at-sign-2"><br>svg和path标签无法进行查找</p><p>注意，属性值必须要加引号，单双引号都可以。</p><h6 id="层级属性结合定位"><a href="#层级属性结合定位" class="headerlink" title="层级属性结合定位"></a>层级属性结合定位</h6><p>遇到某些元素无法精确定位的时候，可以查找其父级及其祖先节点，找到有确定的祖先节点后通过层级依次向下定位。<br>使用星号找不特定的元素；使用..找父级节点；使用//找某一类型的节点等等。</p><h6 id="使用谓语定位"><a href="#使用谓语定位" class="headerlink" title="使用谓语定位"></a>使用谓语定位</h6><p>谓语是 Xpath 中用于描述元素位置。主要有数字下标、最后一个子元素last()、元素下标函数position()，还可以通过position()的比较大小查找某个范围的元素。<br><img src="at-sign-3.png" class="lazyload" data-srcset="at-sign-3.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="at-sign-3"><br><img src="at-sign-4.png" class="lazyload" data-srcset="at-sign-4.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="at-sign-4"><br><img src="at-sign-5.png" class="lazyload" data-srcset="at-sign-5.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="at-sign-5"></p><h6 id="使用逻辑运算符"><a href="#使用逻辑运算符" class="headerlink" title="使用逻辑运算符"></a>使用逻辑运算符</h6><p>如果元素的某个属性无法精确定位到这个元素，我们还可以用逻辑运算符 and、or、| 进行运算定位。</p><p><img src="at-sign-6.png" class="lazyload" data-srcset="at-sign-6.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="at-sign-6"></p><p>注意：|是同时查找多个路径的。</p><h6 id="使用文本定位"><a href="#使用文本定位" class="headerlink" title="使用文本定位"></a>使用文本定位</h6><p>使用文本定位，是 Xpath 中的一大特色。可以通过 text()、string()方法获取元素节点的文本内容。<br>注意：text()和string()的区别在于text()只能获取当前节点的文本内容，string()可以获取当前节点内部所有元素节点的文本内容。<br><img src="at-sign-7.png" class="lazyload" data-srcset="at-sign-7.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="at-sign-7"><br>注意：使用string()查找的时候，在满足文本要求的前提下，查找的元素会把尽可能多的满足要求的节点都列出来，所以，如果需要查找具体的节点，要保证条件限制尽可能严格一点。</p><h6 id="使用部分匹配函数"><a href="#使用部分匹配函数" class="headerlink" title="使用部分匹配函数"></a>使用部分匹配函数</h6><p>Xpath 中有提供了几个函数，用来进行部分匹配。</p><p>| 函数    | 说明    | 举例 |<br>| contains |    选取属性或者文本包含某些字符 |    //div[contains(@id, ‘data’)] 选取 id 属性包含 data 的 div 元素，//div[contains(string(), ‘支付宝’)] 选取内部文本包含“支付宝”的 div 元素 |<br>| starts-with |    选取属性或者文本以某些字符开头 |    //div[starts-with(@id, ‘data’)] 选取 id 属性以 data 开头的 div 元素，//div[starts-with(string(), ‘银联’)] 选取内部文本以“银联”开头的 div 元素 |<br>| ends-with |    选取属性或者文本以某些字符开头    | //div[ends-with(@id, ‘require’)] 选取 id 属性以 require 结尾的 div 元素，//div[ends-with(string(), ‘支付’)] 选取内部文本以“支付”结尾的 div 元素 |</p><p><img src="at-sign-8.png" class="lazyload" data-srcset="at-sign-8.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="at-sign-8"></p><p><em>注意</em><br>兼容性和替代方案<br>需要注意的是，ends-with函数是XPath 2.0的语法，而一些浏览器可能只支持XPath 1.0。如果你在使用XPath 1.0的环境中遇到问题，可以使用以下替代方案：<br>//input[substring(@id, string-length(@id) - string-length(‘123’) + 1) = ‘123’]</p><h5 id="验证-Xpath"><a href="#验证-Xpath" class="headerlink" title="验证 Xpath"></a>验证 Xpath</h5><p>验证 xpath 有两种方法：</p><ul><li>在开发者工具的 Elements 中按Ctrl + F，在搜索框中输入 Xpath</li><li>在开发者工具的 Console 中使用 $x()<br><img src="at-sign-9.png" class="lazyload" data-srcset="at-sign-9.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="at-sign-9"></li></ul><h5 id="Xpath用处"><a href="#Xpath用处" class="headerlink" title="Xpath用处"></a>Xpath用处</h5><ul><li>在网络爬虫中，XPath是一个非常强大的工具，可以帮助我们精确定位和提取需要的数据。</li><li>在Web自动化测试中，XPath用于定位页面元素，执行点击、输入等操作，常用于Selenium等自动化测试框架中‌。</li></ul>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h4&gt;&lt;p&gt;Xpath 是一种用在 XML 文档中定位元素的语言，同样也支持 HTML 元素的解析。这篇文章主要是关于HTML部分的内容，从这个名字可以看出来，Xpath是关于路径的，Xpath是通过路径查找元素。&lt;/p&gt;
&lt;h4 id=&quot;HTML中的Xpath&quot;&gt;&lt;a href=&quot;#HTML中的Xpath&quot; class=&quot;headerlink&quot; title=&quot;HTML中的Xpath&quot;&gt;&lt;/a&gt;HTML中的Xpath&lt;/h4&gt;&lt;p&gt;&lt;img src=&quot;html.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;html.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;html&quot;&gt;&lt;br&gt;&lt;img src=&quot;html-tree.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;html-tree.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;html-tree&quot;&gt;&lt;/p&gt;
&lt;p&gt;HTML是一种树形结构，HTML 是根节点，所有的其他元素节点都是从根节点发出的。其他的元素都是这棵树上的节点Node，每个节点还可能有属性和文本。&lt;br&gt;而路径就是指某个节点到另一个节点的路线。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title></title>
    <link href="https://sunjinkang.github.io/2025/04/27/64-picture-in-picture/pictureinpicture/"/>
    <id>https://sunjinkang.github.io/2025/04/27/64-picture-in-picture/pictureinpicture/</id>
    <published>2025-04-27T02:09:25.301Z</published>
    <updated>2025-02-14T05:49:58.084Z</updated>
    
    <content type="html"><![CDATA[<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>Document</title>  </head>  <body>    <video width="200px" height="160px" controls src="weed-dream.mp4"></video>    <button id="pip-button">开启画中画</button>  </body>  <script type="text/javascript">    const video = document.querySelector('video');    const pipButton = document.getElementById('pip-button');    pipButton.disabled = true;    video.addEventListener('loadedmetadata', () => {      pipButton.disabled = false;    });    video.load();    // 检测浏览器是否支持画中画    if (!document.pictureInPictureEnabled) {      pipButton.disabled = true;      console.log('当前浏览器不支持画中画功能');    }    // 测试一下一段时间之后自动切换为画中画    setTimeout(async () => {      await video.requestPictureInPicture();    }, 5000);    // 点击按钮开启画中画    pipButton.addEventListener('click', async () => {      try {        if (video !== document.pictureInPictureElement) {          await video.requestPictureInPicture();        } else {          await document.exitPictureInPicture();        }      } catch (error) {        console.error('画中画操作失败：', error);      }    });    // 监听进入画中画事件    video.addEventListener('enterpictureinpicture', () => {      pipButton.textContent = '退出画中画';    });    // 监听退出画中画事件    video.addEventListener('leavepictureinpicture', () => {      pipButton.textContent = '开启画中画';    });  </script></html>]]></content>
    
    
      
      
    <summary type="html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>关于画中画的介绍及简单实现</title>
    <link href="https://sunjinkang.github.io/2025/04/27/64-about-picture-in-picture/"/>
    <id>https://sunjinkang.github.io/2025/04/27/64-about-picture-in-picture/</id>
    <published>2025-04-27T02:07:32.000Z</published>
    <updated>2025-04-27T02:08:46.773Z</updated>
    
    <content type="html"><![CDATA[<h2 id="关于画中画（Picture-in-Picture）的介绍及简单实现"><a href="#关于画中画（Picture-in-Picture）的介绍及简单实现" class="headerlink" title="关于画中画（Picture-in-Picture）的介绍及简单实现"></a>关于画中画（Picture-in-Picture）的介绍及简单实现</h2><h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>大家应该都看过下面这种一个小窗悬浮在页面上的效果，这种效果叫做画中画。画中画（Picture-in-Picture，PiP）是一种现代浏览器支持的功能，允许用户将视频或其他媒体元素从网页中分离出来，以一个小窗口的形式浮动在屏幕的任意位置。比较常见的就是视频网站，一个小窗口悬浮在页面最上层，用户可以在浏览其他内容的同时继续观看视频，极大地提升了多任务处理的便利性。本文将重点介绍视频画中画的实现方法。</p><h4 id="画中画原理"><a href="#画中画原理" class="headerlink" title="画中画原理"></a>画中画原理</h4><p>画中画的实现原理基于浏览器的 <strong>Picture-in-Picture API</strong>，主要包括以下几个步骤：</p><p><em>媒体元素分离</em>：</p><ul><li>浏览器将指定的 <code>&lt;video&gt;</code> 元素从 DOM 中分离出来，渲染到一个独立的浮动窗口中。</li><li>这个浮动窗口可以放置在屏幕的任意位置，且始终在最上层显示。</li></ul><p><em>独立控制</em>：</p><ul><li>画中画窗口中的视频可以独立控制（播放、暂停、音量调节等）。</li><li>即使原网页被最小化或切换到其他标签页，画中画窗口仍然可以继续播放。</li></ul><p><em>事件监听</em>：</p><ul><li>通过 JavaScript 监听画中画的状态变化（如进入画中画、退出画中画等），以便进行相应的逻辑处理。</li></ul><p>画中画有两种方法：</p><ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/requestPictureInPicture">video使用的requestPictureInPicture</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/DocumentPictureInPicture">document的documentPictureInPicture</a><br>本文主要是介绍video使用的requestPictureInPicture。</li></ul><h4 id="视频画中画的核心介绍"><a href="#视频画中画的核心介绍" class="headerlink" title="视频画中画的核心介绍"></a>视频画中画的核心介绍</h4><p>视频画中画是画中画功能的主要应用场景，其核心依赖于以下 API：</p><p><em>requestPictureInPicture()</em></p><ul><li>用于将 <code>&lt;video&gt;</code> 元素切换到画中画模式。</li><li>示例：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> video = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;video&#x27;</span>);</span><br><span class="line">video.<span class="title function_">requestPictureInPicture</span>();</span><br></pre></td></tr></table></figure></li></ul><p><em>exitPictureInPicture()</em></p><ul><li>用于退出画中画模式。</li><li>示例：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">document</span>.<span class="title function_">exitPictureInPicture</span>();</span><br></pre></td></tr></table></figure></li></ul><p><em>pictureInPictureEnabled</em></p><ul><li>用于检测当前浏览器是否支持画中画功能。</li><li>示例：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">pictureInPictureEnabled</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;画中画功能支持&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><p><em>pictureInPictureElement</em></p><ul><li>用于获取当前处于画中画模式的元素。</li><li>示例：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> pipElement = <span class="variable language_">document</span>.<span class="property">pictureInPictureElement</span>;</span><br><span class="line"><span class="keyword">if</span> (pipElement) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;当前画中画元素：&#x27;</span>, pipElement);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><p><em>enterpictureinpicture、leavepictureinpicture</em></p><ul><li>用于监听进入和退出画中画模式的事件。</li><li>示例：<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">video.<span class="title function_">addEventListener</span>(<span class="string">&#x27;enterpictureinpicture&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;进入画中画模式&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">video.<span class="title function_">addEventListener</span>(<span class="string">&#x27;leavepictureinpicture&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;退出画中画模式&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></li></ul><h4 id="视频画中画的简单实现"><a href="#视频画中画的简单实现" class="headerlink" title="视频画中画的简单实现"></a>视频画中画的简单实现</h4><p>以下是一个简单的视频画中画实现示例：</p><p><em>HTML</em></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">video</span> <span class="attr">controls</span> <span class="attr">src</span>=<span class="string">&quot;your-video-url.mp4&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">video</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">&quot;pip-button&quot;</span>&gt;</span>开启画中画<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p><em>JavaScript</em></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> video = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;video&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> pipButton = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;pip-button&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检测浏览器是否支持画中画</span></span><br><span class="line"><span class="keyword">if</span> (!<span class="variable language_">document</span>.<span class="property">pictureInPictureEnabled</span>) &#123;</span><br><span class="line">  pipButton.<span class="property">disabled</span> = <span class="literal">true</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;当前浏览器不支持画中画功能&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 点击按钮开启画中画</span></span><br><span class="line">pipButton.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (video !== <span class="variable language_">document</span>.<span class="property">pictureInPictureElement</span>) &#123;</span><br><span class="line">      <span class="keyword">await</span> video.<span class="title function_">requestPictureInPicture</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="variable language_">document</span>.<span class="title function_">exitPictureInPicture</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;画中画操作失败：&#x27;</span>, error);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听进入画中画事件</span></span><br><span class="line">video.<span class="title function_">addEventListener</span>(<span class="string">&#x27;enterpictureinpicture&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  pipButton.<span class="property">textContent</span> = <span class="string">&#x27;退出画中画&#x27;</span>;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听退出画中画事件</span></span><br><span class="line">video.<span class="title function_">addEventListener</span>(<span class="string">&#x27;leavepictureinpicture&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  pipButton.<span class="property">textContent</span> = <span class="string">&#x27;开启画中画&#x27;</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="画中画的适用场景介绍"><a href="#画中画的适用场景介绍" class="headerlink" title="画中画的适用场景介绍"></a>画中画的适用场景介绍</h4><p>画中画功能在以下场景中非常实用：</p><p><em>视频播放器</em></p><ul><li>用户可以在浏览其他内容的同时继续观看视频。</li></ul><p><em>在线会议</em></p><ul><li>将会议视频以小窗口形式浮动，方便用户多任务操作。</li></ul><p><em>教育平台</em></p><ul><li>学生可以在观看课程视频的同时做笔记或查阅资料。</li></ul><p><em>直播平台</em></p><ul><li>用户可以在观看直播的同时浏览其他网页。</li></ul><h4 id="画中画使用的注意事项"><a href="#画中画使用的注意事项" class="headerlink" title="画中画使用的注意事项"></a>画中画使用的注意事项</h4><p><em>浏览器兼容性</em></p><ul><li>画中画功能在现代浏览器中支持较好，但在某些浏览器（如 Safari）中可能存在限制。</li><li>可以通过 <code>document.pictureInPictureEnabled</code> 检测浏览器是否支持。</li></ul><p><em>用户交互</em></p><ul><li>画中画功能必须由用户主动触发（如点击按钮），不能通过脚本自动开启。</li></ul><p><em>安全性</em></p><ul><li>画中画窗口不会显示原网页的其他内容，确保用户隐私安全。</li></ul><p><em>性能优化</em></p><ul><li>如果视频分辨率较高，画中画窗口可能会占用较多系统资源，需注意性能优化。</li></ul><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>画中画功能为用户提供了极大的便利，尤其是在多任务处理场景中。通过浏览器的 <strong>Picture-in-Picture API</strong>，开发者可以轻松实现视频画中画效果。本文详细介绍了视频画中画的原理、核心 API、实现方法、适用场景及注意事项，希望能为开发者提供实用的参考。</p><p>此外，<code>DocumentPictureInPicture</code> 是画中画功能的扩展，允许将整个文档或部分 DOM 元素放入画中画窗口，但目前仍处于实验阶段，兼容性和实用性有限，建议在实际项目中谨慎使用。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;关于画中画（Picture-in-Picture）的介绍及简单实现&quot;&gt;&lt;a href=&quot;#关于画中画（Picture-in-Picture）的介绍及简单实现&quot; class=&quot;headerlink&quot; title=&quot;关于画中画（Picture-in-Picture）的介绍及简单实现&quot;&gt;&lt;/a&gt;关于画中画（Picture-in-Picture）的介绍及简单实现&lt;/h2&gt;&lt;h4 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h4&gt;&lt;p&gt;大家应该都看过下面这种一个小窗悬浮在页面上的效果，这种效果叫做画中画。画中画（Picture-in-Picture，PiP）是一种现代浏览器支持的功能，允许用户将视频或其他媒体元素从网页中分离出来，以一个小窗口的形式浮动在屏幕的任意位置。比较常见的就是视频网站，一个小窗口悬浮在页面最上层，用户可以在浏览其他内容的同时继续观看视频，极大地提升了多任务处理的便利性。本文将重点介绍视频画中画的实现方法。&lt;/p&gt;
&lt;h4 id=&quot;画中画原理&quot;&gt;&lt;a href=&quot;#画中画原理&quot; class=&quot;headerlink&quot; title=&quot;画中画原理&quot;&gt;&lt;/a&gt;画中画原理&lt;/h4&gt;&lt;p&gt;画中画的实现原理基于浏览器的 &lt;strong&gt;Picture-in-Picture API&lt;/strong&gt;，主要包括以下几个步骤：&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>76-关于弹幕</title>
    <link href="https://sunjinkang.github.io/2025/03/15/76-about-danmu/"/>
    <id>https://sunjinkang.github.io/2025/03/15/76-about-danmu/</id>
    <published>2025-03-15T12:57:18.000Z</published>
    <updated>2025-05-22T08:11:54.719Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>弹幕，作为一种极具互动性的视觉表达方式，已经成为视频平台（如 Bilibili、爱奇艺、腾讯视频等）的标配。它不仅提升了用户参与感，也在一定程度上丰富了视频内容的表达维度。<br>下面我们将介绍弹幕的基本实现原理并结合 Bilibili 的弹幕思路，构建一个可用的弹幕系统。</p><h2 id="弹幕的本质与核心原理"><a href="#弹幕的本质与核心原理" class="headerlink" title="弹幕的本质与核心原理"></a>弹幕的本质与核心原理</h2><p>弹幕的本质，是在播放视频过程中，将用户生成的文本内容（弹幕）实时渲染在视频层上方，进一步包括<strong>横向滚动、同步展示、支持样式自定义与交互控制等</strong>。</p><p>核心要素包括：</p><ol><li><strong>轨道控制</strong>：防止多条弹幕重叠</li><li><strong>动画渲染</strong>：实现从右至左（或其他方向）的移动</li><li><strong>消息管理</strong>：弹幕的接收、发送、缓存和删除</li><li><strong>播放同步</strong>：弹幕与视频时间对齐</li><li><strong>交互功能</strong>：弹幕开关、颜色、过滤、密度等设置</li></ol><p>下面我们先从一个简单的实现入手</p><h2 id="纯前端的基础弹幕实现"><a href="#纯前端的基础弹幕实现" class="headerlink" title="纯前端的基础弹幕实现"></a>纯前端的基础弹幕实现</h2><h3 id="基本功能"><a href="#基本功能" class="headerlink" title="基本功能"></a>基本功能</h3><ul><li>文字从右侧飘入左侧</li><li>随机轨道显示</li><li>动画结束后自动销毁</li></ul><h3 id="HTML-结构"><a href="#HTML-结构" class="headerlink" title="HTML 结构"></a>HTML 结构</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;video-container&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">video</span> <span class="attr">id</span>=<span class="string">&quot;video&quot;</span> <span class="attr">src</span>=<span class="string">&quot;sample.mp4&quot;</span> <span class="attr">controls</span>&gt;</span><span class="tag">&lt;/<span class="name">video</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;danmu-layer&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="样式"><a href="#样式" class="headerlink" title="样式"></a>样式</h3><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.video-container</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">640px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">360px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.danmu-layer</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">0</span>; <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">pointer-events</span>: none;</span><br><span class="line">  <span class="attribute">overflow</span>: hidden;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.danmu-item</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">white-space</span>: nowrap;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">16px</span>;</span><br><span class="line">  <span class="attribute">color</span>: white;</span><br><span class="line">  <span class="attribute">animation</span>: move linear;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@keyframes</span> move &#123;</span><br><span class="line">  <span class="selector-tag">from</span> &#123; <span class="attribute">transform</span>: <span class="built_in">translateX</span>(<span class="number">100%</span>); &#125;</span><br><span class="line">  <span class="selector-tag">to</span> &#123; <span class="attribute">transform</span>: <span class="built_in">translateX</span>(-<span class="number">100%</span>); &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="JavaScript-实现"><a href="#JavaScript-实现" class="headerlink" title="JavaScript 实现"></a>JavaScript 实现</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">createDanmaku</span>(<span class="params">text</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> layer = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;.danmaku-layer&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> el = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>);</span><br><span class="line">  el.<span class="property">className</span> = <span class="string">&#x27;danmaku-item&#x27;</span>;</span><br><span class="line">  el.<span class="property">textContent</span> = text;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 轨道计算</span></span><br><span class="line">  <span class="keyword">const</span> trackHeight = <span class="number">30</span>;</span><br><span class="line">  <span class="keyword">const</span> trackCount = <span class="title class_">Math</span>.<span class="title function_">floor</span>(layer.<span class="property">clientHeight</span> / trackHeight);</span><br><span class="line">  <span class="keyword">const</span> track = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * trackCount);</span><br><span class="line">  el.<span class="property">style</span>.<span class="property">top</span> = <span class="string">`<span class="subst">$&#123;track * trackHeight&#125;</span>px`</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 动画持续时间</span></span><br><span class="line">  <span class="keyword">const</span> duration = <span class="number">8</span> + <span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">2</span>;</span><br><span class="line">  el.<span class="property">style</span>.<span class="property">animationDuration</span> = <span class="string">`<span class="subst">$&#123;duration&#125;</span>s`</span>;</span><br><span class="line"></span><br><span class="line">  layer.<span class="title function_">appendChild</span>(el);</span><br><span class="line"></span><br><span class="line">  el.<span class="title function_">addEventListener</span>(<span class="string">&#x27;animationend&#x27;</span>, <span class="function">() =&gt;</span> el.<span class="title function_">remove</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 示例：每秒生成一条弹幕</span></span><br><span class="line"><span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">createDanmaku</span>(<span class="string">&#x27;Hello 弹幕~&#x27;</span>);</span><br><span class="line">&#125;, <span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="渲染方式选择：DOM-vs-Canvas-vs-WebGL"><a href="#渲染方式选择：DOM-vs-Canvas-vs-WebGL" class="headerlink" title="渲染方式选择：DOM vs Canvas vs WebGL"></a>渲染方式选择：DOM vs Canvas vs WebGL</h2><table><thead><tr><th>渲染方式</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td><strong>DOM（absolute + animation）</strong></td><td>易实现、支持样式多</td><td>性能差，弹幕多时掉帧</td></tr><tr><td><strong>Canvas</strong></td><td>高性能、能支持 1000+ 条弹幕</td><td>开发复杂、不易调试</td></tr><tr><td><strong>WebGL</strong></td><td>超高性能</td><td>最复杂，需 GPU 编程知识</td></tr><tr><td>建议实现：小规模用 DOM 实现，大规模用 Canvas，长期项目考虑 WebGL。</td><td></td><td></td></tr></tbody></table><h2 id="构建一个高级弹幕系统（模拟-Bilibili-实现）"><a href="#构建一个高级弹幕系统（模拟-Bilibili-实现）" class="headerlink" title="构建一个高级弹幕系统（模拟 Bilibili 实现）"></a>构建一个高级弹幕系统（模拟 Bilibili 实现）</h2><h3 id="目标增强功能"><a href="#目标增强功能" class="headerlink" title="目标增强功能"></a>目标增强功能</h3><ul><li>与视频时间同步（非实时发送也能同步）</li><li>使用 Canvas 提升性能</li><li>支持发送自定义颜色弹幕</li><li>弹幕开关</li><li>WebSocket 实时接收弹幕</li><li>使用本地视频进行绑定展示</li></ul><h3 id="HTML-结构（支持输入和控制）"><a href="#HTML-结构（支持输入和控制）" class="headerlink" title="HTML 结构（支持输入和控制）"></a>HTML 结构（支持输入和控制）</h3><ul><li>视频与弹幕分层渲染，避免 DOM 操作影响性能</li><li>Canvas 尺寸需与视频保持比例协调</li><li>控制面板集成色彩选择等实用功能</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">video</span> <span class="attr">id</span>=<span class="string">&quot;video&quot;</span> <span class="attr">src</span>=<span class="string">&quot;./video.mp4&quot;</span> <span class="attr">width</span>=<span class="string">&quot;800&quot;</span> <span class="attr">controls</span>&gt;</span><span class="tag">&lt;/<span class="name">video</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">canvas</span> <span class="attr">id</span>=<span class="string">&quot;danmaku-canvas&quot;</span> <span class="attr">width</span>=<span class="string">&quot;800&quot;</span> <span class="attr">height</span>=<span class="string">&quot;400&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;controls&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">id</span>=<span class="string">&quot;danmakuText&quot;</span> <span class="attr">placeholder</span>=<span class="string">&quot;输入弹幕&quot;</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;color&quot;</span> <span class="attr">id</span>=<span class="string">&quot;colorPicker&quot;</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">&quot;sendUserDanmaku()&quot;</span>&gt;</span>发送<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">&quot;toggleDanmaku()&quot;</span>&gt;</span>开/关弹幕<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="Canvas-渲染弹幕核心逻辑"><a href="#Canvas-渲染弹幕核心逻辑" class="headerlink" title="Canvas 渲染弹幕核心逻辑"></a>Canvas 渲染弹幕核心逻辑</h3><ul><li>基于 RAF 的动画循环保证流畅性</li><li>速度随机化（2-3.5px/帧）实现自然运动差异</li><li>measureText 精确计算文本宽度实现内存回收<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> canvas = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;danmaku-canvas&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> ctx = canvas.<span class="title function_">getContext</span>(<span class="string">&#x27;2d&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> video = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;video&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> danmakus = []; <span class="comment">// 所有弹幕</span></span><br><span class="line"><span class="keyword">let</span> showDanmaku = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 渲染帧</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!showDanmaku) <span class="keyword">return</span> <span class="title function_">requestAnimationFrame</span>(render);</span><br><span class="line"></span><br><span class="line">  ctx.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, canvas.<span class="property">width</span>, canvas.<span class="property">height</span>);</span><br><span class="line">  <span class="keyword">const</span> currentTime = video.<span class="property">currentTime</span>;</span><br><span class="line"></span><br><span class="line">  danmakus.<span class="title function_">forEach</span>(<span class="function"><span class="params">dm</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (dm.<span class="property">time</span> &lt;= currentTime) &#123;</span><br><span class="line">      dm.<span class="property">x</span> -= dm.<span class="property">speed</span>;</span><br><span class="line">      ctx.<span class="property">font</span> = <span class="string">`<span class="subst">$&#123;dm.fontSize&#125;</span>px sans-serif`</span>;</span><br><span class="line">      ctx.<span class="property">fillStyle</span> = dm.<span class="property">color</span>;</span><br><span class="line">      ctx.<span class="title function_">fillText</span>(dm.<span class="property">text</span>, dm.<span class="property">x</span>, dm.<span class="property">y</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  danmakus = danmakus.<span class="title function_">filter</span>(<span class="function"><span class="params">dm</span> =&gt;</span> dm.<span class="property">x</span> &gt; -ctx.<span class="title function_">measureText</span>(dm.<span class="property">text</span>).<span class="property">width</span>);</span><br><span class="line">  <span class="title function_">requestAnimationFrame</span>(render);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">requestAnimationFrame</span>(render);</span><br></pre></td></tr></table></figure></li></ul><h3 id="弹幕数据结构和生成方法"><a href="#弹幕数据结构和生成方法" class="headerlink" title="弹幕数据结构和生成方法"></a>弹幕数据结构和生成方法</h3><ul><li>时间戳绑定实现「时移播放」场景支持</li><li>Y 轴随机分布简化轨道管理逻辑</li><li>速度随机化提升视觉效果<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">createDanmaku</span>(<span class="params">text, color, time</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    text,</span><br><span class="line">    color,</span><br><span class="line">    time,</span><br><span class="line">    <span class="attr">x</span>: canvas.<span class="property">width</span>,</span><br><span class="line">    <span class="attr">y</span>: <span class="title class_">Math</span>.<span class="title function_">random</span>() * canvas.<span class="property">height</span>,</span><br><span class="line">    <span class="attr">speed</span>: <span class="number">2</span> + <span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">1.5</span>,</span><br><span class="line">    <span class="attr">fontSize</span>: <span class="number">20</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h3 id="用户发送弹幕"><a href="#用户发送弹幕" class="headerlink" title="用户发送弹幕"></a>用户发送弹幕</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">sendUserDanmaku</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> text = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;danmakuText&#x27;</span>).<span class="property">value</span>;</span><br><span class="line">  <span class="keyword">const</span> color = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;colorPicker&#x27;</span>).<span class="property">value</span>;</span><br><span class="line">  <span class="keyword">const</span> time = video.<span class="property">currentTime</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> dm = <span class="title function_">createDanmaku</span>(text, color, time);</span><br><span class="line">  danmakus.<span class="title function_">push</span>(dm);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 同时发送到服务器（WebSocket）</span></span><br><span class="line">  ws.<span class="title function_">send</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(dm));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="WebSocket-实时收发"><a href="#WebSocket-实时收发" class="headerlink" title="WebSocket 实时收发"></a>WebSocket 实时收发</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> ws = <span class="keyword">new</span> <span class="title class_">WebSocket</span>(<span class="string">&#x27;ws://localhost:8080&#x27;</span>);</span><br><span class="line">ws.<span class="property">onopen</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;连接服务器&#x27;</span>);</span><br><span class="line">  ws.<span class="title function_">send</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;getHistory&#x27;</span> &#125;));</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">ws.<span class="property">onmessage</span> = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(e.<span class="property">data</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (data.<span class="property">type</span> === <span class="string">&#x27;history&#x27;</span>) &#123;</span><br><span class="line">    danmakus.<span class="title function_">push</span>(...data.<span class="property">danmakus</span>); <span class="comment">// 加载历史</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    danmakus.<span class="title function_">push</span>(<span class="title function_">createDanmaku</span>(data.<span class="property">text</span>, data.<span class="property">color</span>, data.<span class="property">time</span>));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="开关弹幕"><a href="#开关弹幕" class="headerlink" title="开关弹幕"></a>开关弹幕</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">toggleDanmaku</span>(<span class="params"></span>) &#123;</span><br><span class="line">  showDanmaku = !showDanmaku;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="服务器建议（Node-js-示例）"><a href="#服务器建议（Node-js-示例）" class="headerlink" title="服务器建议（Node.js 示例）"></a>服务器建议（Node.js 示例）</h3><p>使用简单的 WebSocket server 可实现广播：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">WebSocket</span> = <span class="built_in">require</span>(<span class="string">&#x27;ws&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> wss = <span class="keyword">new</span> <span class="title class_">WebSocket</span>.<span class="title class_">Server</span>(&#123; <span class="attr">port</span>: <span class="number">8080</span> &#125;);</span><br><span class="line"><span class="keyword">const</span> clients = <span class="keyword">new</span> <span class="title class_">Set</span>();</span><br><span class="line"><span class="keyword">const</span> danmakuHistory = []; <span class="comment">// 所有弹幕存储</span></span><br><span class="line">wss.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="function">(<span class="params">ws</span>) =&gt;</span> &#123;</span><br><span class="line">  clients.<span class="title function_">add</span>(ws);</span><br><span class="line"></span><br><span class="line">  ws.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function">(<span class="params">msg</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> data = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(msg);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (data.<span class="property">type</span> === <span class="string">&#x27;getHistory&#x27;</span>) &#123;</span><br><span class="line">      <span class="comment">// 将所有历史弹幕发送给客户端</span></span><br><span class="line">      ws.<span class="title function_">send</span>(</span><br><span class="line">        <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">          <span class="attr">type</span>: <span class="string">&#x27;history&#x27;</span>,</span><br><span class="line">          <span class="attr">danmakus</span>: danmakuHistory,</span><br><span class="line">        &#125;)</span><br><span class="line">      );</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 正常弹幕处理</span></span><br><span class="line">    danmakuHistory.<span class="title function_">push</span>(data);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> client <span class="keyword">of</span> clients) &#123;</span><br><span class="line">      <span class="keyword">if</span> (client !== ws &amp;&amp; client.<span class="property">readyState</span> === <span class="title class_">WebSocket</span>.<span class="property">OPEN</span>) &#123;</span><br><span class="line">        client.<span class="title function_">send</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(data));</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  ws.<span class="title function_">on</span>(<span class="string">&#x27;close&#x27;</span>, <span class="function">() =&gt;</span> clients.<span class="title function_">delete</span>(ws));</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>以上只是简单实现了弹幕的效果，还有一些优化点没有处理，比如：</p><ul><li>添加防碰撞算法（轨道优先级管理）</li><li>实现弹幕类型多样化<ul><li>不同位置的滚动</li><li>不同方向的滚动等等</li></ul></li><li>增加弹幕密度控制</li><li>添加历史弹幕加载功能</li><li>实现字体描边等不同的样式特效或动画特效</li></ul><h2 id="结尾"><a href="#结尾" class="headerlink" title="结尾"></a>结尾</h2><p>弹幕系统的实现，不仅仅是文字飘动那么简单。它融合了动画、轨道管理、实时通信、数据同步、性能优化等多个技术点，是前端开发中非常有趣的一项综合性挑战。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;弹幕，作为一种极具互动性的视觉表达方式，已经成为视频平台（如 Bilibili、爱奇艺、腾讯视频等）的标配。它不仅提升了用户参与感，也在一定程度上丰富了视频内容的表达维度。&lt;br&gt;下面我们将介绍弹幕的基本实现原理并结合 Bilibili 的弹幕思路，构建一个可用的弹幕系统。&lt;/p&gt;
&lt;h2 id=&quot;弹幕的本质与核心原理&quot;&gt;&lt;a href=&quot;#弹幕的本质与核心原理&quot; class=&quot;headerlink&quot; title=&quot;弹幕的本质与核心原理&quot;&gt;&lt;/a&gt;弹幕的本质与核心原理&lt;/h2&gt;&lt;p&gt;弹幕的本质，是在播放视频过程中，将用户生成的文本内容（弹幕）实时渲染在视频层上方，进一步包括&lt;strong&gt;横向滚动、同步展示、支持样式自定义与交互控制等&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;核心要素包括：&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>如何有效利用浏览器的Devtools？</title>
    <link href="https://sunjinkang.github.io/2024/08/09/72-about-how-to-use-devtools/"/>
    <id>https://sunjinkang.github.io/2024/08/09/72-about-how-to-use-devtools/</id>
    <published>2024-08-08T22:48:08.000Z</published>
    <updated>2025-04-27T07:48:37.972Z</updated>
    
    <content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>相信大家应该都很熟悉 Chrome DevTools了，我们平时在开发中经常会用Devtools提供的丰富的功能和工具，帮助我们诊断和调试网页应用程序。本文将分享一些 Chrome DevTools 的调试技巧，在开发中提高效率。</p><h4 id="模拟接口响应和网页内容"><a href="#模拟接口响应和网页内容" class="headerlink" title="模拟接口响应和网页内容"></a>模拟接口响应和网页内容</h4><p>通过本地覆盖可以模拟接口返回值和响应头，无需 mock 数据工具，无需等待后端支持，快速复现在一些数据下的 BUG 等。在 DevTools 可以直接修改你想要的 Fetch/XHR 接口数据，还可以修改响应头，解决跨域等问题，不仅可以覆盖 Fetch/XHR，JS、CSS 等资源也可以。</p><p>下面是在 Network 面板快速模拟远程资源的内容和响应头的步骤：<br><img src="override0.png" class="lazyload" data-srcset="override0.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="override0"><br><img src="override1.png" class="lazyload" data-srcset="override1.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="override1"><br><img src="override2.png" class="lazyload" data-srcset="override2.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="override2"><br><img src="override3.png" class="lazyload" data-srcset="override3.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="override3"></p><p><em>设置本地覆盖步骤：</em></p><ul><li>打开 DevTools，导航至 Network 网络面板，右键单击要覆盖的请求，从下拉菜单中选择 Override content 或 Open in Sources panel。</li><li>如果是首次使用，未设置过本地覆盖文件目录，DevTools 会在顶部的操作栏中提示您选择一个文件夹来存储覆盖文件，并 “允许” 授予 DevTools 对其的访问权限（在 window 下选择了系统盘的文件夹测试发现用不了，可能是权限问题，建议选个非系统盘的文件夹）。</li><li>在 Sources 面板中修改数据，完成后按 Ctrl + S 保存，刷新页面，即可看见修改后的数据（被覆盖的资源在图标右下角会有个紫色的圆点）。</li><li>若要恢复使用服务上的数据，请导航到 Sources &gt; Overrides，可以点击取消 “Enable local overrides” 复选框，或者点击旁边的 Clear 图标，或者如上图演示中的单个删除。</li></ul><p><strong>注意：在Source面板中修改的数据一定要保存</strong></p><p><em>它是怎么工作的？</em><br>当你在 DevTools 中进行更改时，DevTools 会将修改后的文件的副本保存到您指定的文件夹中。<br>当你重新加载页面时，DevTools 会提供本地修改后的文件，而不是网络资源。所以在旧版本支持 Override 的版本中，也可以手动创建一个文件来覆盖内容。</p><h4 id="快速重发请求"><a href="#快速重发请求" class="headerlink" title="快速重发请求"></a>快速重发请求</h4><p>在联调接口或者排查 BUG 的时候，经常需要重新再发一次请求，如果要重新操作一次复杂的交互、重新输入一大堆参数时，这种方式会显得比较麻烦。<br>这时候就可以通过 Replay XHR 来快速重发请求</p><p><img src="replay.png" class="lazyload" data-srcset="replay.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="replay"></p><p><em>操作步骤：</em></p><ul><li>导航至 Network 网络面板，右击一个 XHR 请求，可以点击 Fetch/XHR 过滤。</li><li>点击 Replay XHR。</li></ul><h4 id="在-Console-中发请求"><a href="#在-Console-中发请求" class="headerlink" title="在 Console 中发请求"></a>在 Console 中发请求</h4><p>针对上面同样的场景，有时候我们需要修改请求头、入参再重新发起请求，那么 Replay XHR 就不支持了。<br>可以通过 Copy as fetch ，在控制台修改请求参数，发起请求</p><p><img src="copy1.png" class="lazyload" data-srcset="copy1.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="copy1"><br><img src="copy2.png" class="lazyload" data-srcset="copy2.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="copy2"></p><p><em>操作步骤：</em></p><ul><li>导航至 Network 网络面板，右击一个 XHR 请求，可以点击 Fetch/XHR 过滤。</li><li>点击 Copy -&gt; Copy as fetch。</li><li>导航至 Console 面板，Ctrl + v 粘贴。</li><li>修改内容，如接口 url、header、body， 然后按回车键即可发起请求。</li></ul><h4 id="Console-中的-符号"><a href="#Console-中的-符号" class="headerlink" title="Console 中的 $符号"></a>Console 中的 $符号</h4><p><em>$0-$4</em><br>当要在 Console 中在调试页面元素时，比如要获取元素的信息，此时就可以使用 4。<br>$0：当前选择的元素 ，**$1：上一次的引用，$2：**上上次的引用，一直到 $4</p><p><img src="dollar1.png" class="lazyload" data-srcset="dollar1.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="dollar1"></p><p><em>操作步骤：</em></p><ul><li>点击菜单栏第一个选择图标，或者使用快捷键 Ctrl + Shift + C 选择元素。</li><li>导航至 Console 面板，现在就可以使用 4，例如选择了两次，第一次选择的元素可以使用 1 访问，第二次选择的元素使用 0 访问。</li></ul><p><em>$ 和 $$</em></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$(‘xxx’) 相当于 <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(‘xxx’)  </span><br><span class="line">$$(‘xxx’) 相当于 <span class="title class_">Array</span>.<span class="title function_">form</span>(<span class="variable language_">document</span>.<span class="title function_">querySelectorall</span>(‘xxx’))</span><br></pre></td></tr></table></figure><p><img src="dollar2.png" class="lazyload" data-srcset="dollar2.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="dollar2"></p><p><em>$_</em><br>调试的过程中，经常需要打印一些变量值，但是如果想查看上一次执行的结果，使用 $_ 是对上次执行结果的引用。</p><p><img src="dollar3.png" class="lazyload" data-srcset="dollar3.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="dollar3"></p><h4 id="Element-面板"><a href="#Element-面板" class="headerlink" title="Element 面板"></a>Element 面板</h4><p><em>隐藏 DOM Element</em><br>有时候我们想截图，但是想要隐藏图中的敏感信息，此时就可以隐藏元素或者直接删除对应的元素<br><em>一键展开所有 DOM</em><br>在调式 DOM Element 的时候，如果 DOM 层次比较深的情况下，一个个去展开就比较麻烦，我们可以使用快捷键 Alt + Click 一键展开该层下的所有 DOM</p><p><em>拖拽移动 DOM Element</em><br>当我们想看页面某一部分元素在不同的位置显示效果的时候，可以直接拖拽 DOM 元素调整位置，也可以使用键盘快捷键 Ctrl + 上下箭头。</p><h4 id="截图"><a href="#截图" class="headerlink" title="截图"></a>截图</h4><p>如果想要截取多屏很长的整个页面内容，系统自带的截图软件显然不支持，此时可以使用 command 命令截图</p><p><img src="screenshot.png" class="lazyload" data-srcset="screenshot.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="screenshot"></p><p><em>操作步骤：</em></p><ul><li>按 Ctrl + Shift + P 调出 command 命令</li><li>输入命令 capture full size screenshot 后回车。</li></ul><p><em>截图 command：</em></p><ul><li>截框选区域：capture area screenshot</li><li>截滚动全屏：capture full size screenshot</li><li>截选中的节点：capture node screenshot</li><li>截当前窗口内：capture screenshot<br>还有很多其他的命令，如切换主题 Switch to Dark theme，查看所有快捷键 Show shortcuts 等等。</li></ul><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>本文介绍了 Chrome DevTools 的调试技巧和最新版本 Chrome 117 中的新功能，包括模拟接口响应和网页内容、快速重发请求、在 Console 中发请求、Console 中的快捷命令、条件断点、Element 面板以及截图命令，这些调试技巧有助于我们在开发中提高效率。</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h4&gt;&lt;p&gt;相信大家应该都很熟悉 Chrome DevTools了，我们平时在开发中经常会用Devtools提供的丰富的功能和工具，帮助我们诊断和调试网页应用程序。本文将分享一些 Chrome DevTools 的调试技巧，在开发中提高效率。&lt;/p&gt;
&lt;h4 id=&quot;模拟接口响应和网页内容&quot;&gt;&lt;a href=&quot;#模拟接口响应和网页内容&quot; class=&quot;headerlink&quot; title=&quot;模拟接口响应和网页内容&quot;&gt;&lt;/a&gt;模拟接口响应和网页内容&lt;/h4&gt;&lt;p&gt;通过本地覆盖可以模拟接口返回值和响应头，无需 mock 数据工具，无需等待后端支持，快速复现在一些数据下的 BUG 等。在 DevTools 可以直接修改你想要的 Fetch/XHR 接口数据，还可以修改响应头，解决跨域等问题，不仅可以覆盖 Fetch/XHR，JS、CSS 等资源也可以。&lt;/p&gt;
&lt;p&gt;下面是在 Network 面板快速模拟远程资源的内容和响应头的步骤：&lt;br&gt;&lt;img src=&quot;override0.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;override0.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;override0&quot;&gt;&lt;br&gt;&lt;img src=&quot;override1.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;override1.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;override1&quot;&gt;&lt;br&gt;&lt;img src=&quot;override2.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;override2.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;override2&quot;&gt;&lt;br&gt;&lt;img src=&quot;override3.png&quot; class=&quot;lazyload&quot; data-srcset=&quot;override3.png&quot; srcset=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==&quot; alt=&quot;override3&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
</feed>
