

<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>css筆記 &#8211; Max的程式語言筆記</title>
	<atom:link href="https://stackoverflow.max-everyday.com/category/css/feed/" rel="self" type="application/rss+xml" />
	<link>https://stackoverflow.max-everyday.com</link>
	<description>我要當一個豬頭，快樂過每一天</description>
	<lastBuildDate>Mon, 26 Oct 2026 21:23:14 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.4</generator>

<image>
	<url>https://stackoverflow.max-everyday.com/wp-content/uploads/2017/02/max-stackoverflow-256.png</url>
	<title>css筆記 &#8211; Max的程式語言筆記</title>
	<link>https://stackoverflow.max-everyday.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>SVG 動畫的排版小陷阱：讓線條和箭頭同步出現</title>
		<link>https://stackoverflow.max-everyday.com/2026/10/svg-path-marker-end/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/10/svg-path-marker-end/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 26 Oct 2026 21:21:44 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[Debug]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8830</guid>

					<description><![CDATA[最近在處理 SVG 動畫時，遇到了一個有趣的畫面...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/watermarked_img_7311881495471058215_clean-1024x572.jpg?v=1793049781" alt="" class="wp-image-8833" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/watermarked_img_7311881495471058215_clean-1024x572.jpg?v=1793049781 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/watermarked_img_7311881495471058215_clean-600x335.jpg?v=1793049781 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/watermarked_img_7311881495471058215_clean-767x428.jpg?v=1793049781 767w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/watermarked_img_7311881495471058215_clean.jpg?v=1793049781 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">最近在處理 SVG 動畫時，遇到了一個有趣的畫面瑕疵。當我們使用 path 標籤搭配 marker-end 來加上箭頭時，瀏覽器展現動畫的行為跟我們預期的有點落差。</p>



<p class="wp-block-paragraph">這篇文章來記錄這個問題的根本原因，以及如何透過簡單的技巧完美解決。</p>



<h2 class="wp-block-heading">問題是甚麼</h2>



<p class="wp-block-paragraph">在網頁中，如果我們寫了 path 結合 marker-end 這樣的語法，本意是希望路徑在畫完的時候，終點才出現這個箭頭。</p>



<p class="wp-block-paragraph">但是實際執行動畫時，只要路徑開始顯現，也就是透明度變成 1 的那一個瞬間，瀏覽器就會在終點立刻把這個箭頭渲染出來。</p>



<p class="wp-block-paragraph">這會導致一個很不協調的現象：線條還在慢慢從起點畫過來，但終點已經提早立好了一個箭頭。</p>



<h2 class="wp-block-heading">為什麼會這樣</h2>



<p class="wp-block-paragraph">這是因為 SVG 的 marker-end 本質上就是一個綁定在路徑端點上的裝飾元件。</p>



<p class="wp-block-paragraph">當我們使用 stroke-dashoffset 來控制線條慢慢畫出來時，路徑只要一有顯示的跡象，瀏覽器就會認為端點已經存在，於是提前把裝飾的箭頭畫上去，沒有辦法隨著線條進度同步延遲出現。</p>



<h2 class="wp-block-heading">解決方案</h2>



<p class="wp-block-paragraph">為了讓箭頭配合線條畫到的時間點才出現，我們需要分開處理路徑和箭頭。</p>



<p class="wp-block-paragraph">第一步是移除 SVG 的 marker-end 屬性，這樣可以避免瀏覽器在路徑終點預先繪製箭頭。</p>



<p class="wp-block-paragraph">第二步是獨立一個箭頭元件出來，這裡使用 polygon 標籤來當作獨立的箭頭，並且根據路徑終點的座標以及切線斜率來旋轉對齊端點。</p>



<h2 class="wp-block-heading">程式碼實作</h2>



<p class="wp-block-paragraph">調整過後的程式碼像這樣：</p>



<p class="wp-block-paragraph">HTML</p>



<pre class="wp-block-code"><code>&lt;svg class="marathon-svg" viewBox="0 0 1400 260" xmlns="http://www.w3.org/2000/svg"&gt;
    &lt;!-- Track path --&gt;
    &lt;path id="track" d="M80,150 Q280,40 480,120 Q680,240 880,140 Q1080,50 1280,150 L1360,180" stroke="#C8922A" stroke-width="6" stroke-linecap="round" fill="none" stroke-dasharray="1360" stroke-dashoffset="1360" style="
            animation: dashDraw 3s ease forwards;
            --dash-len: 1360;
            animation-delay: calc(var(--scene-t,0s) * -1 + 9s);
            animation-play-state: paused;
            "&gt;&lt;/path&gt;
    &lt;!-- Arrowhead that appears when drawing reaches the end (at 11.5s) --&gt;
    &lt;polygon points="0 -9, 22 0, 0 9" fill="#C8922A" transform="translate(1360, 180) rotate(20.56)" style="
            opacity: 0;
            animation: fadeIn 0.3s ease forwards;
            animation-delay: calc(var(--scene-t,0s) * -1 + 11.5s);
            animation-play-state: paused;
            "&gt;&lt;/polygon&gt;
&lt;/svg&gt;
</code></pre>



<p class="wp-block-paragraph">透過把箭頭拉出來獨立控制，並且設定好對應的 animation-delay 讓它在時間點剛好對上線條畫完的一刻，就能解決箭頭提前出現的不協調感。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/10/svg-path-marker-end/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>如果 AI 生成的 Flex ：如何用 CSS Grid 打造完美的左右雙欄卡片</title>
		<link>https://stackoverflow.max-everyday.com/2026/10/css-from-flex-to-grid/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/10/css-from-flex-to-grid/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 20 Oct 2026 16:04:15 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[css]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8815</guid>

					<description><![CDATA[在網頁前端開發中，我們經常需要將區塊拆分成左右兩...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">在網頁前端開發中，我們經常需要將區塊拆分成左右兩欄，例如左邊放標籤或圖示、右邊放標題與內文。當我們把任務交給 AI 生成時，最常見的反應就是直接使用 Flexbox 排版。</p>



<p class="wp-block-paragraph">雖然 Flexbox 非常好用，但在處理多行、不規則高度或是希望呈現傳統表格對齊（例如類似 HTML 表格中的 tr 包含兩個 td ）的結構時，以下示範如何把第4列搬到第2例右側。</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="609" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/chrome_2026-10-20-23-49-28-1024x609.png?v=1792512039" alt="" class="wp-image-8816" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/chrome_2026-10-20-23-49-28-1024x609.png?v=1792512039 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/chrome_2026-10-20-23-49-28-600x357.png?v=1792512039 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/chrome_2026-10-20-23-49-28-767x456.png?v=1792512039 767w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/chrome_2026-10-20-23-49-28.png?v=1792512039 1059w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">AI 生成的盲點：為什麼 Flexbox 在這裡會翻車？</h2>



<p class="wp-block-paragraph">如果讓 AI 直接用 <code>display: flex</code> 來處理這種左側是標籤、右側是標題加內文的卡片，通常會遇到以下幾個痛點：</p>



<ol start="1" class="wp-block-list">
<li>高度與垂直對齊難以控制：當右側的文字因為換行變成兩行或三行時，左側的標籤或圖示往往會跟著亂跑，無法與右側內容保持一致的垂直置中或頂端對齊。</li>



<li>巢狀結構過於肥胖：為了模擬左右兩欄，AI 往往會生出好幾層 <code>div</code> 來互相巢狀包裹，導致 HTML 結構變得很冗長。</li>



<li>欄位寬度不穩定：如果左側的標籤文字長度不一致，會直接導致右側的文字區域跟著被擠壓變形。</li>
</ol>



<h2 class="wp-block-heading">核心原理：為什麼改用 CSS Grid？</h2>



<p class="wp-block-paragraph">為了達到類似傳統表格（ tr 搭配兩個 td ）的嚴謹對齊效果，最佳解法其實是 <strong>CSS Grid</strong>。</p>



<p class="wp-block-paragraph">我們只需要將卡片本身設為 <code>display: grid</code>，並透過 <code>grid-template-columns: 200px 1fr</code>：</p>



<ul class="wp-block-list">
<li>第一欄（左側）固定寬度或給予適當的空間放標籤與圖示。</li>



<li>第二欄（右側）設定為 <code>1fr</code>，自動佔滿剩餘的所有寬度。</li>
</ul>



<p class="wp-block-paragraph">這樣一來，不管右側的文字換幾行、內容有多長，左右兩欄的對齊關係依然穩固如山，完全不需要擔心版面變形。</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">這是一篇針對此次 CSS 版面調整所撰寫的技術部落格文章，內容包含原理簡介、AI 生成常見陷阱、實際遇到的問題以及解法，並已將程式碼對照調整為清楚的 Before 與 After 區塊。</p>



<h2 class="wp-block-heading">從 AI 生成的 Flex 陷阱談起：如何用 CSS Grid 打造完美的左右雙欄卡片</h2>



<p class="wp-block-paragraph">在網頁前端開發中，我們經常需要將區塊拆分成左右兩欄，例如左邊放標籤或圖示、右邊放標題與內文。當我們把任務交給 AI 生成時，最常見的反應就是直接使用 Flexbox 排版。</p>



<p class="wp-block-paragraph">雖然 Flexbox 非常好用，但在處理多行、不規則高度或是希望呈現傳統表格對齊（例如類似 HTML 表格中的 tr 包含兩個 td ）的結構時，使用 Flexbox 往往會衍生出許多意想不到的排版災難。</p>



<h2 class="wp-block-heading">AI 生成的盲點：為什麼 Flexbox 在這裡會翻車？</h2>



<p class="wp-block-paragraph">如果讓 AI 直接用 <code>display: flex</code> 來處理這種左側是標籤、右側是標題加內文的卡片，通常會遇到以下幾個痛點：</p>



<ol start="1" class="wp-block-list">
<li>高度與垂直對齊難以控制：當右側的文字因為換行變成兩行或三行時，左側的標籤或圖示往往會跟著亂跑，無法與右側內容保持一致的垂直置中或頂端對齊。</li>



<li>巢狀結構過於肥胖：為了模擬左右兩欄，AI 往往會生出好幾層 <code>div</code> 來互相巢狀包裹，導致 HTML 結構變得很冗長。</li>



<li>欄位寬度不穩定：如果左側的標籤文字長度不一致，會直接導致右側的文字區域跟著被擠壓變形。</li>
</ol>



<h2 class="wp-block-heading">核心原理：為什麼改用 CSS Grid？</h2>



<p class="wp-block-paragraph">為了達到類似傳統表格（ tr 搭配兩個 td ）的嚴謹對齊效果，最佳解法其實是 <strong>CSS Grid</strong>。</p>



<p class="wp-block-paragraph">我們只需要將卡片本身設為 <code>display: grid</code>，並透過 <code>grid-template-columns: 200px 1fr</code>：</p>



<ul class="wp-block-list">
<li>第一欄（左側）固定寬度或給予適當的空間放標籤與圖示。</li>



<li>第二欄（右側）設定為 <code>1fr</code>，自動佔滿剩餘的所有寬度。</li>
</ul>



<p class="wp-block-paragraph">這樣一來，不管右側的文字換幾行、內容有多長，左右兩欄的對齊關係依然穩固如山，完全不需要擔心版面變形。</p>



<h2 class="wp-block-heading">實戰修改前後對照</h2>



<h3 class="wp-block-heading">CSS 修改前後對照</h3>



<p class="wp-block-paragraph">Before（修改前）：</p>



<p class="wp-block-paragraph">CSS</p>



<pre class="wp-block-code"><code>/* Right: 3 Alignment Pillars */
.alignment-pillars {
  position: absolute;
  top: 170px;
  left: 510px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.align-card {
  padding: 18px 26px;
  opacity: 0;
}
</code></pre>



<p class="wp-block-paragraph">After（修改後）：</p>



<p class="wp-block-paragraph">CSS</p>



<pre class="wp-block-code"><code>/* Right: 3 Alignment Pillars */
.alignment-pillars {
  position: absolute;
  top: 170px;
  left: 510px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  width: 600px;
}

.align-card {
  padding: 18px 26px;
  opacity: 0;
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 20px;
  align-items: center;
}

.align-card-left {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.align-card-right {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
</code></pre>



<h3 class="wp-block-heading">HTML 修改前後對照</h3>



<p class="wp-block-paragraph">Before（修改前）：</p>



<p class="wp-block-paragraph">HTML</p>



<pre class="wp-block-code"><code><code>&lt;!-- Pillar 1 -->
  &lt;div class="align-card align-1 card-base">
    &lt;div class="pillar-tag p1">心法 1：釐清真正的需求&lt;/div>
    &lt;div class="pillar-title">拿到任務先別急著打開檔案或叫 AI 產出&lt;/div>
    &lt;div class="pillar-desc">多問幾個為什麼，找出背後真正要解決的核心問題。先問清定價需求，一頁表格就能直擊核心。&lt;/div>
  &lt;/div>
</code></code></pre>



<p class="wp-block-paragraph">After（修改後）：</p>



<p class="wp-block-paragraph">HTML</p>



<pre class="wp-block-code"><code><code>&lt;!-- Pillar 1 -->
  &lt;div class="align-card align-1 card-base">
    &lt;div class="align-card-left">
      &lt;div class="pillar-tag p1">心法 1：釐清真正的需求&lt;/div>
    &lt;/div>
    &lt;div class="align-card-right">
      &lt;div class="pillar-title">拿到任務先別急著打開檔案或叫 AI 產出&lt;/div>
      &lt;div class="pillar-desc">多問幾個為什麼，找出背後真正要解決的核心問題。先問清定價需求，一頁表格就能直擊核心。&lt;/div>
    &lt;/div>
  &lt;/div>
</code></code></pre>



<h2 class="wp-block-heading">總結</h2>



<p class="wp-block-paragraph">下次當你需要處理類似表格列、左右對齊的區塊時，如果 AI 自動幫你寫了滿滿的 Flexbox 巢狀結構，不妨主動要求它改用 <strong>CSS Grid</strong>。只要定義好欄位寬度比例，就能用最乾淨的程式碼，換取最穩定、最不容易跑版的畫面表現。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/10/css-from-flex-to-grid/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>解決 WordPress 深色模式切換時超連結「白轉黑 0.5 秒」殘影閃爍問題</title>
		<link>https://stackoverflow.max-everyday.com/2026/10/wordpress-bgcolor-from-white-to-black-blink/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/10/wordpress-bgcolor-from-white-to-black-blink/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 15 Oct 2026 19:58:10 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[WordPress筆記]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8800</guid>

					<description><![CDATA[在為 WordPress 主題自訂深色模式（Da...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/watermarked_img_14618693422224554944_clean-1024x572.jpg?v=1792154892" alt="" class="wp-image-8804" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/watermarked_img_14618693422224554944_clean-1024x572.jpg?v=1792154892 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/watermarked_img_14618693422224554944_clean-600x335.jpg?v=1792154892 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/watermarked_img_14618693422224554944_clean-767x428.jpg?v=1792154892 767w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/10/watermarked_img_14618693422224554944_clean.jpg?v=1792154892 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">在為 WordPress 主題自訂深色模式（Dark Mode / Dark Theme）切換功能時，許多開發者常會遇到一個相當破壞使用體驗的視覺 Bug：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>現象描述：</strong><br>點擊切換為深色模式（或由深色轉淺色）的瞬間，頁面上的<strong>超連結（<code>&lt;a&gt;</code>）或文字區塊背景</strong>會先短暫閃出一塊白色（<code>#ffffff</code>），接著在約 <strong>0.5 秒</strong>的時間內才緩慢漸變過渡為黑色（<code>#000000</code> / <code>#16181d</code>）。</p>
</blockquote>



<p class="wp-block-paragraph">這並非 JavaScript 執行延遲，而是 CSS 過渡動畫（Transitions）與瀏覽器補間運算相互作用產生的典型副作用。本文將完整記錄該問題的<strong>根本成因</strong>與<strong>最佳實踐解法</strong>。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">一、問題根本原因分析</h2>



<p class="wp-block-paragraph">這個問題主要由兩個 CSS 特性疊加造成：</p>



<h3 class="wp-block-heading">1. 原生主題的 <code>transition: all 0.5s ease;</code> 廣泛繼承</h3>



<p class="wp-block-paragraph">在許多 WordPress 主題（包括 Gutenshop）的樣式表（<code>style.css</code>）中，為了讓按鈕與部分互動元素具備平滑動畫，常常撰寫了如下規則：</p>



<pre class="wp-block-code"><code>transition: all 0.5s ease;</code></pre>



<p class="wp-block-paragraph">當使用 JavaScript 動態在 <code>&lt;body&gt;</code> 或 <code>&lt;html&gt;</code> 上新增/切換 <code>.theme-dark</code> 類別時，瀏覽器會監測到樣式變更。由於設定了 <code>transition: all</code>，<strong>頁面上所有繼承或套用了該規則的屬性（包含 <code>background-color</code>、<code>color</code>、<code>border-color</code>）都會進入 0.5 秒的漸變過渡狀態</strong>。</p>



<h3 class="wp-block-heading">2. 顏色插值運算（Color Interpolation）與透明背景缺失</h3>



<ul class="wp-block-list">
<li>預設情況下，常規文字超連結 <code>&lt;a&gt;</code> 的 <code>background-color</code> 通常是 <code>transparent</code>（透明）或繼承父容器。</li>



<li>當深淺模式切換時，如果某些元件或祖先元素設定了背景色變換，瀏覽器在計算 <code>transparent</code>（實質為 <code>rgba(0,0,0,0)</code> 或 <code>rgba(255,255,255,0)</code>）至目標色（如 <code>#16181d</code>）的中間過渡幀時，容易將未明定背景的元素以白色底（<code>#fff</code>）作為基底進行過渡插值。</li>



<li>結果就形成了：<strong>超連結背景突然變白，並耗時 0.5 秒慢慢過渡到黑色</strong>的突兀閃爍。</li>
</ul>



<h3 class="wp-block-heading">3. 首次造訪無 Cookie 時，未即時寫入使用者系統偏好</h3>



<ul class="wp-block-list">
<li>若使用者尚未手動點擊切換過，瀏覽器中就沒有 <code>gutenshop_theme</code> 的 Cookie。</li>



<li>每次重新整理或跳轉頁面時，若僅依賴延遲載入的腳本偵測 <code>prefers-color-scheme: dark</code>，頁面在 HTML 解析初期無法從 Cookie 取得狀態，導致從預設的淺色（白）重新被腳本動態判定改為深色（黑），觸發二次切換閃爍。</li>



<li><strong>最佳解法：</strong> 在初次載入（Page Load）偵測到無 Cookie 時，<strong>立即將使用者的 System Theme 寫入 Cookie</strong>，並在 <code>&lt;head&gt;</code> 的 Anti-flicker 行內腳本同步寫入，確保下一個畫面與未來所有子頁面在 DOM 渲染前就能以 Cookie 命中深色模式，徹底杜絕閃爍。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">二、解決方案</h2>



<p class="wp-block-paragraph">要徹底杜絕這個現象，最佳做法是「<strong>雙管齊下</strong>」：</p>



<ol class="wp-block-list">
<li><strong>切換當下全域靜音過渡（Disable Transitions during Toggle）</strong>：切換主題時暫時鎖死所有 transition，讓深淺色瞬間套用，不觸發任何補間動畫。</li>



<li><strong>約束超連結的 transition 範圍</strong>：明確鎖定 <code>&lt;a&gt;</code> 的背景為透明，並限制僅有 <code>color</code>（文字顏色）允許 hover 漸變，禁止背景參與 transition。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">步驟 1：在 CSS 中加入停用過渡與連結約束規則</h3>



<p class="wp-block-paragraph">在你的獨立深色模式樣式檔（如 <code>css/dark-theme.css</code>）加入以下規則：</p>



<pre class="wp-block-code"><code>/* ==========================================================================
   1. 暫停過渡工具類：切換主題瞬間徹底消除 0.5 秒的顏色補間閃爍
   ========================================================================== */
.disable-transitions,
.disable-transitions *,
.disable-transitions *::before,
.disable-transitions *::after {
  -webkit-transition: none !important;
  -moz-transition: none !important;
  -o-transition: none !important;
  -ms-transition: none !important;
  transition: none !important;
}

/* ==========================================================================
   2. 超連結背景與過渡約束：
      明確限制常規連結背景為透明，且過渡僅限文字顏色 (color)，防止背景補間
   ========================================================================== */
a:not(.button):not(.added_to_cart):not(.theme-switch-link) {
  background-color: transparent;
  transition-property: color;
  transition-duration: 0.2s;
}

/* 深色模式下連結文字配色 */
body.theme-dark a {
  color: #60a5fa;
}

body.theme-dark a:hover,
body.theme-dark a:focus {
  color: #93c5fd;
}</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">步驟 2：在切換 JavaScript 中包裝過渡阻斷邏輯</h3>



<p class="wp-block-paragraph">在處理主題切換的 JavaScript（如 <code>js/theme-switch.js</code>）的 <code>applyTheme</code> 函數中：</p>



<ul class="wp-block-list">
<li>切換前先在根節點 <code>&lt;html&gt;</code> 加上 <code>.disable-transitions</code>。</li>



<li>透過存取 <code>window.getComputedStyle().opacity</code> 強制觸發瀏覽器重繪（Force Reflow），保證類別立即生效且不會被瀏覽器批次合併渲染。</li>



<li>完成類別切換後，使用 <code>requestAnimationFrame</code> 在下一個畫面幀將 <code>.disable-transitions</code> 移除，確保按鈕後續正常的滑鼠 hover 動畫不受影響。</li>
</ul>



<pre class="wp-block-code"><code>/**
 * 套用深淺主題類別
 * @param {string} theme 'dark' 或 'light'
 */
function applyTheme(theme) {
  var isDark = (theme === 'dark');

  // 1. 立即停止當前所有 CSS 過渡動畫，防止 0.5s 白黑殘影閃爍
  document.documentElement.classList.add('disable-transitions');

  // 2. 切換深淺模式類別
  document.body.classList.toggle('theme-dark', isDark);
  document.body.classList.toggle('theme-light', !isDark);
  document.documentElement.classList.toggle('theme-dark', isDark);
  document.documentElement.classList.toggle('theme-light', !isDark);

  // 3. 強制觸發重繪 (Reflow)，確保樣式變更在 transition 被關閉的狀態下生效
  window.getComputedStyle(document.documentElement).opacity;

  // 4. 下一個渲染週期恢復原本的 CSS 過渡效果（按鈕 hover 等仍維持流暢動畫）
  requestAnimationFrame(function () {
    document.documentElement.classList.remove('disable-transitions');
  });

  // 5. 同步切換按鈕的 aria 屬性與提示文字
  var links = document.querySelectorAll('.theme-switch-link');
  links.forEach(function (el) {
    el.title = isDark ? '切換至淺色模式' : '切換至深色模式';
    el.setAttribute('aria-pressed', isDark ? 'true' : 'false');
  });
}

/**
 * 頁面初始化：當無 Cookie 時，立即將使用者的系統主題 (OS Theme) 寫入 Cookie
 */
function init() {
  var saved = getCookie(COOKIE_NAME);
  var currentTheme;

  if (saved === 'dark' || saved === 'light') {
    currentTheme = saved;
  } else {
    // 首次造訪無 cookie：抓取系統主題並立即寫入 cookie，避免二次載入或跳轉時重複判斷而閃爍
    currentTheme = getSystemTheme();
    setCookie(COOKIE_NAME, currentTheme, COOKIE_DAYS);
  }

  var isDark = (currentTheme === 'dark');
  applyTheme(currentTheme);
  injectDesktopToggle(isDark);
  injectMobileToggle(isDark);
  document.addEventListener('click', handleClick);
  watchOSTheme();
}</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">步驟 3：在 Head 注入極速防閃爍腳本並更新版本號</h3>



<p class="wp-block-paragraph">在 <code>functions.php</code> 的 <code>wp_enqueue_scripts</code> 中：</p>



<ol class="wp-block-list">
<li>注入極速 Inline 腳本：若初次無 Cookie，亦立即將系統主題寫入 Cookie 並先加上 <code>theme-dark</code> 類別，在 HTML 剛載入時就完成判定。</li>



<li>推進樣式表與腳本版本號（例如 <code>1.0.1</code> / <code>1.0.3</code>），避免瀏覽器快取舊檔案：</li>
</ol>



<pre class="wp-block-code"><code>// functions.php
wp_register_style( 
    'gutenshop-dark-theme', 
    get_template_directory_uri() . '/css/dark-theme.css', 
    array( 'gutenshop-style' ), 
    '1.0.1' 
);
wp_enqueue_style( 'gutenshop-dark-theme' );

// 防閃爍腳本：搶先注入 &lt;html&gt;，若無 cookie 則同步寫入系統主題 cookie
$anti_flicker_js = "(function(){try{var m=document.cookie.match(/(^|;\\s*)gutenshop_theme=(&#91;^;]+)/);var t=m?decodeURIComponent(m&#91;2]):null;var d=(window.matchMedia&amp;&amp;window.matchMedia('(prefers-color-scheme: dark)').matches)?'dark':'light';var theme=t||d;if(!t){var exp=new Date();exp.setTime(exp.getTime()+(365*24*60*60*1000));document.cookie='gutenshop_theme='+encodeURIComponent(d)+'; expires='+exp.toUTCString()+'; path=/; SameSite=Lax';}if(theme==='dark'){document.documentElement.classList.add('theme-dark');}}catch(e){}})();";
wp_add_inline_script( 'gutenshop-navigation', $anti_flicker_js, 'before' );

wp_register_script( 
    'gutenshop-theme-switch', 
    get_template_directory_uri() . '/js/theme-switch.js', 
    array( 'gutenshop-custom-js-jquery' ), 
    '1.0.3', 
    true 
);
wp_enqueue_script( 'gutenshop-theme-switch' );</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">三、成果驗證</h2>



<p class="wp-block-paragraph">套用此修改後：</p>



<ol class="wp-block-list">
<li><strong>點擊深淺模式切換按鈕</strong>：背景與所有文字立刻無縫切換，<strong>完全不再出現 0.5 秒的白色閃爍與變色殘影</strong>。</li>



<li><strong>一般互動不受影響</strong>：一般的按鈕 hover、下拉選單滑動等原有動畫依然正常流暢運作。</li>



<li><strong>效能優化</strong>：利用強制 Reflow 配合 <code>requestAnimationFrame</code>，不會造成頁面重載或多餘運算開銷。</li>
</ol>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/10/wordpress-bgcolor-from-white-to-black-blink/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CSP (Content Security Policy) 的網站要如何安全的使用 Inline script</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/csp-content-security-policy-%e7%9a%84%e7%b6%b2%e7%ab%99%e8%a6%81%e5%a6%82%e4%bd%95%e5%ae%89%e5%85%a8%e7%9a%84%e4%bd%bf%e7%94%a8-inline-script/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/csp-content-security-policy-%e7%9a%84%e7%b6%b2%e7%ab%99%e8%a6%81%e5%a6%82%e4%bd%95%e5%ae%89%e5%85%a8%e7%9a%84%e4%bd%bf%e7%94%a8-inline-script/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 09:01:22 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[css]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8583</guid>

					<description><![CDATA[當你在幫網站做健康檢查，看到弱掃報告（ 像是 Z...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">當你在幫網站做健康檢查，看到弱掃報告（ 像是 ZAP ）跳出一個橘黃色的 Medium 警告，寫著 <code>style-src 'unsafe-inline'</code> 或是發現 <code>script-src</code> 滿是漏洞時，心裡是不是頓時涼了一半？</p>



<p class="wp-block-paragraph">簡單來說，如果你的 CSP （ 網頁安全政策 ）開了 <code>unsafe-inline</code> ，就等於是在大門裝了密碼鎖，卻把密碼用便利貼貼在門口一樣。駭客只要找到機會塞一段惡意腳本（ XSS 攻擊 ），你的網站就直接變成他的遊樂場。</p>



<p class="wp-block-paragraph">但是偏偏像是 Google Tag Manager （ GTM ）這種必裝的分析工具，官方給的程式碼裡面就是有一大段 Inline Script ：</p>



<p class="wp-block-paragraph">HTML</p>



<pre class="wp-block-code"><code>&lt;script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"&gt;&lt;/script&gt;
&lt;script&gt;
  window.dataLayer = window.dataLayer || &#91;];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
&lt;/script&gt;
</code></pre>



<p class="wp-block-paragraph">既想要防禦 XSS 攻擊，又不能把 GTM <sup></sup>拔掉，這時候你有兩條路可以走，讓我們來看看這兩個相愛相殺的解決方案。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">方案一：使用 Nonce （ 一次性密碼鎖 ）</h3>



<p class="wp-block-paragraph">這個方法就像是去看演唱會，每次進場工作人員都會在你的手上蓋一個當天限定、過期失效的印章。</p>



<p class="wp-block-paragraph">在 CSP 標頭（ Header ）裡，你可以這樣設定：</p>



<p class="wp-block-paragraph">HTTP</p>



<pre class="wp-block-code"><code>Content-Security-Policy: script-src 'nonce-每天換的神秘亂數密碼'; img-src www.googletagmanager.com
</code></pre>



<p class="wp-block-paragraph">然後在你的網頁程式碼中，那段 GTM 的標籤也必須帶上相同的密碼：</p>



<p class="wp-block-paragraph">HTML</p>



<pre class="wp-block-code"><code>&lt;script nonce='每天換的神秘亂數密碼'&gt;
  // GTM 的扣（Code）放這裡
&lt;/script&gt;
</code></pre>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 工程師真心話</h4>



<ul class="wp-block-list">
<li><strong>優點</strong>：超級安全。因為駭客根本猜不到你下一個 Request 的亂數密碼是什麼，就算他成功塞了惡意指令，也會因為沒有通關密碼而被瀏覽器直接擋在門外。</li>



<li><strong>缺點（ 也就是坑 ）</strong>：這非常考驗後端的大腦。你的伺服器必須在「 每次 」收到請求時，都用高強度的加密演算法生出一組全新的隨機字串。最慘的是，你的網頁從此跟「 回應快取 」（ Response Cache ）說再見，因為一旦網頁被快取，密碼就不會變了，那這個鎖就形同虛設。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">方案二：使用 Hash （ 驗收指紋認證 ）</h3>



<p class="wp-block-paragraph">如果你的網站是 SPA （ 單頁應用程式 ），或是部署在 GitHub Pages 這種根本沒有後端可以幫忙生密碼的靜態伺服器，那 Hash 就是你的救星。</p>



<p class="wp-block-paragraph">這個概念是，瀏覽器會直接去核對這段程式碼的「 指紋 」。</p>



<p class="wp-block-paragraph">步驟非常簡單，把 <code>&lt;script&gt;</code> 到 <code>&lt;/script&gt;</code> 之間的程式碼內容，丟到 <a target="_blank" rel="noreferrer noopener" href="https://stackoverflow.max-everyday.com/wp-admin/post.php?post=8583&amp;action=edit">SHA256 線上計算工具</a> 裡面去跑一下，會得到一串像是 <code>0bbd063b7...</code> 的雜湊值（ Hash ）。</p>



<p class="wp-block-paragraph">接著把這串指紋放進你的 CSP 設定裡：</p>



<p class="wp-block-paragraph">HTTP</p>



<pre class="wp-block-code"><code>Content-Security-Policy: script-src 'sha256-計算出來的那串指紋'; img-src www.googletagmanager.com
</code></pre>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 工程師真心話</h4>



<ul class="wp-block-list">
<li><strong>優點</strong>：前端自己就能搞定，不需要後端伺服器配合，而且網頁依然可以開開心心地做快取，提升載入速度。</li>



<li><strong>缺點（ 最雷的地方 ）</strong>：
<ol start="1" class="wp-block-list">
<li><strong>字元敏感度破表</strong>：SHA256 是一個極度龜毛的演算法。只要你的程式碼裡面多了一個空格、少了一個換行，甚至不同工程師的電腦存檔時，斷行符號一個是 LF （ <code>\n</code> ）一個是 CRLF （ <code>\r\n</code> ），算出來的指紋就完全不一樣。指紋一對不上，你的 GTM 就會直接被 CSP 射下來。</li>



<li><strong>維護地獄</strong>：哪天行銷團隊突然要求修改 GTM 裡面的參數，只要動到一個字，你就必須重新計算一次 Hash 值、重新更新 CSP 標頭。如果交接沒做好，後續接手的工程師改了程式碼發現網站功能壞掉，可能要在電腦前哭著找兇手找很久。</li>
</ol>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">總結防禦指南</h3>



<p class="wp-block-paragraph">想要高枕無憂、後端架構也撐得住，請選 <strong>Nonce</strong> 。</p>



<p class="wp-block-paragraph">如果是靜態網站、追求速度與快取，請選 <strong>Hash</strong> ，但每次改動程式碼時，請記得去燒香拜拜順便更新指紋。</p>



<p class="wp-block-paragraph">總之，快把 <code>unsafe-inline</code> 從你的 CSP 裡面拿掉，別再讓你的網站裸奔了。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/csp-content-security-policy-%e7%9a%84%e7%b6%b2%e7%ab%99%e8%a6%81%e5%a6%82%e4%bd%95%e5%ae%89%e5%85%a8%e7%9a%84%e4%bd%bf%e7%94%a8-inline-script/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>網頁開發者必看！用白話秒懂 CSS Injection 與 CSP 防禦</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/css-injection-csp-url/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/css-injection-csp-url/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 06:39:22 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[React 筆記]]></category>
		<category><![CDATA[css]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8572</guid>

					<description><![CDATA[為什麼連寫個看起來無害的 CSS 都會被瀏覽器當...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-1024x572.jpg?v=1781764740" alt="" class="wp-image-8579" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-1024x572.jpg?v=1781764740 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-600x335.jpg?v=1781764740 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-768x429.jpg?v=1781764740 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean.jpg?v=1781764740 1376w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">為什麼連寫個看起來無害的 CSS 都會被瀏覽器當成恐怖份子。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">瀏覽器的大內總管：CSP 到底在嚴格什麼？</h2>



<p class="wp-block-paragraph">各位前端與後端苦力們，有沒有遇過這種慘劇？</p>



<p class="wp-block-paragraph">當你興高采烈地在網站加上這行內容安全政策（CSP）：</p>



<pre class="wp-block-code"><code><code>Content-Security-Policy: default-src 'self';</code></code></pre>



<p class="wp-block-paragraph">本以為從此高枕無憂，結果下一秒打開網頁，發現自己寫的行內腳本（inline-script）全部陣亡：</p>



<pre class="wp-block-code"><code><code>&lt;script&gt;console.log('連我都不能跑是怎樣');&lt;/script&gt;</code></code></pre>



<p class="wp-block-paragraph">好啦，大家都知道 JavaScript 權限大、壞人最愛用，所以瀏覽器預設不讓它亂跑很合理。為了安撫大內總管，我們還得乖乖去算雜湊值（Hash）或是塞個 nonce 安全憑證，像是這樣：</p>



<pre class="wp-block-code"><code><code>Content-Security-Policy: default-src 'self'; script-src 'sha256-xxx...';</code></code></pre>



<p class="wp-block-paragraph">但是，最讓人傻眼的是，連單純想幫網頁化妝的行內樣式（inline-style）也被一起關進大牢了：</p>



<pre class="wp-block-code"><code><code>&lt;style&gt;h1 { color: yellow; }&lt;/style&gt;</code></code></pre>



<p class="wp-block-paragraph">CSS 到底做錯了什麼？它只是個孩子啊！</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">警匪片現場：當 CSS 變成滲透工具</h2>



<p class="wp-block-paragraph">資安世界有三大神聖支柱：機密性、完整性、可用性。而隨便亂加的 CSS，其實正在默默破壞這一切。</p>



<h3 class="wp-block-heading">疑犯一號：DOM 元素私生子（破壞完整性）</h3>



<p class="wp-block-paragraph">你可能會想：「這網頁是我寫的，裡面的 CSS 當然也是我寫的啊！」</p>



<p class="wp-block-paragraph">事情可沒那麼簡單。在 JavaScript 的世界裡，壞人可以用各種神奇手法動態產生元素：</p>



<p class="wp-block-paragraph">JavaScript</p>



<pre class="wp-block-code"><code>let style = document.createElement('style');
style.innerText = 'h1 { color: blue; }';
document.body.appendChild(style);
</code></pre>



<p class="wp-block-paragraph">這段程式碼一旦被惡意注入，它就能神不知鬼不覺地改掉你的網頁外觀。這就是為什麼瀏覽器乾脆一不做二不休，預設把所有沒登記過的行內樣式全部封鎖。</p>



<h3 class="wp-block-heading">疑犯二號：假藉美化名義的間諜（破壞機密性）</h3>



<p class="wp-block-paragraph">CSS 裡面有兩個看起來很無辜、但實際上超常被當作間諜的語法： <code>@import</code> 和 <code>url()</code> 。</p>



<p class="wp-block-paragraph">當你用了背景圖片：</p>



<pre class="wp-block-code"><code><code>background-image: url(https://example.com/spy.jpg);</code></code></pre>



<p class="wp-block-paragraph">瀏覽器看到這行，就會自動發出一個請求去下載圖片。這時候，壞人就可以利用這個機制搞鬼：</p>



<ul class="wp-block-list">
<li><strong>偷看你的行蹤（Beacon 技術）：</strong> 只要把圖片網址加上個人專屬的編碼，當你一打開網頁，壞人的伺服器收到圖片請求，就知道「喔！某某某在幾點幾分看了這個頁面！」</li>



<li><strong>Cookie 順風車：</strong> 如果網站沒設定好，這個請求甚至會順便把你的 Cookie 跨站送出去，直接把你的登入狀態雙手奉上。</li>
</ul>



<p class="wp-block-paragraph">所以，想要在有 CSP 的防護下安全地秀出圖片，你還必須跟總管報備圖片的白色名單：</p>



<pre class="wp-block-code"><code><code>Content-Security-Policy: default-src 'self'; style-src 'sha256-...'; img-src http://信任的圖片來源;</code></code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">結論</h2>



<p class="wp-block-paragraph">在這個壞人招數層出不窮的時代，網頁開發者真的不能只活在「CSS 只是用來排版」的粉紅泡泡裡。多了解一點 CSP 的龜毛規則，雖然設定時會寫到抓狂，但至少能保住大家的飯碗，不讓網站變成駭客的免費遊樂園！</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/css-injection-csp-url/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to Use the :has() Selector in CSS</title>
		<link>https://stackoverflow.max-everyday.com/2024/12/how-to-use-the-has-selector-in-css/</link>
					<comments>https://stackoverflow.max-everyday.com/2024/12/how-to-use-the-has-selector-in-css/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 05 Dec 2024 01:41:05 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[css]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=6035</guid>

					<description><![CDATA[之前使用過 :not() 覺得超級的實用，例如：...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1024" height="1024" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2024/12/89ac7fc5-1ae6-4a94-b35c-144dd7704fdb.jpeg" alt="" class="wp-image-6037" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2024/12/89ac7fc5-1ae6-4a94-b35c-144dd7704fdb.jpeg?v=1733363050 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2024/12/89ac7fc5-1ae6-4a94-b35c-144dd7704fdb-600x600.jpeg?v=1733363050 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2024/12/89ac7fc5-1ae6-4a94-b35c-144dd7704fdb-300x300.jpeg?v=1733363050 300w, https://stackoverflow.max-everyday.com/wp-content/uploads/2024/12/89ac7fc5-1ae6-4a94-b35c-144dd7704fdb-768x768.jpeg?v=1733363050 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">之前使用過 :not() 覺得超級的實用，例如：選取所有checkbox 狀態是沒被打勾的。今天要來學習使用 :has().</p>



<h2 class="wp-block-heading">相關文章</h2>



<ul class="wp-block-list">
<li>:has()<br><a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/:has">https://developer.mozilla.org/zh-CN/docs/Web/CSS/:has</a></li>



<li>CSS :has() Pseudo-class<br><a href="https://www.w3schools.com/cssref/sel_has.php">https://www.w3schools.com/cssref/sel_has.php</a></li>



<li>:has() 個案研究<br><a href="https://developer.chrome.com/blog/css-ui-ecommerce-has?hl=zh-tw">https://developer.chrome.com/blog/css-ui-ecommerce-has?hl=zh-tw</a></li>



<li>How to Use the :has() Selector in CSS<br><a href="https://www.freecodecamp.org/news/how-to-use-the-has-selector-in-css/">https://www.freecodecamp.org/news/how-to-use-the-has-selector-in-css/</a></li>
</ul>



<p class="wp-block-paragraph">影片：Learn CSS :not() and :has() selector in 6 Minutes<br><a href="https://www.youtube.com/watch?v=-pQZLQakz-c">https://www.youtube.com/watch?v=-pQZLQakz-c</a></p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Learn CSS :not() and :has() selector in 6 Minutes" width="640" height="360" src="https://www.youtube.com/embed/-pQZLQakz-c?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p class="wp-block-paragraph">滿神奇的，居然完全沒使用 javascript 就到過 filter 功能。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2024/12/how-to-use-the-has-selector-in-css/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>左側分類欄位組合右側收合</title>
		<link>https://stackoverflow.max-everyday.com/2024/05/sidebar-with-collapse/</link>
					<comments>https://stackoverflow.max-everyday.com/2024/05/sidebar-with-collapse/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 07 May 2024 07:33:30 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[javascript筆記]]></category>
		<category><![CDATA[Bootstrap]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=5717</guid>

					<description><![CDATA[在使用 clipchamp 時看到這一個功能，覺...]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="428" height="491" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2024/05/Screenshot-2024-05-07-at-3.14.17 PM.png?v=1715066261" alt="" class="wp-image-5718"/></figure>
</div>


<p class="wp-block-paragraph">在使用 clipchamp 時看到這一個功能，覺得很有趣。</p>



<p class="wp-block-paragraph">比較簡單的解法，是使用 sidebar + collapse  就可以做到，範例程式：<br><a href="https://getbootstrap.com/docs/5.3/examples/sidebars/">https://getbootstrap.com/docs/5.3/examples/sidebars/</a></p>



<p class="wp-block-paragraph">clipchame 的這個做法的優點是讓分類可以保持在左側sidebar 裡。</p>



<p class="wp-block-paragraph">實作的方式是，sidebar 使用 tab 來實作，收合使用。 collapse, 參考看看 bootstrap 範例：<br><a href="https://getbootstrap.com/docs/5.3/components/collapse/">https://getbootstrap.com/docs/5.3/components/collapse/</a></p>



<p class="wp-block-paragraph">一般 collapse 是上下收合，這個是左右收。</p>



<p class="wp-block-paragraph">一般 tab 是上下的版面，這個是左右版面。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">實際上用的到html程式碼：</p>



<pre class="wp-block-code"><code>  &lt;div class="ifilter_chart"&gt;
      &lt;div class="ifitler_chart_left_side_bar"&gt;
          &lt;nav aria-label="左側導覽" class="ifilter_chart_left_nav normal" tabindex="0"&gt;
              &lt;div class="ifilter_chart_left_nav_grid"&gt;
                  &lt;div class="ifilter_chart_left_nav_item"&gt;
                      &lt;div role="tablist"&gt;
                          &lt;button aria-label="Filter" aria-selected="true" class="ifilter_chart_left_nav_btn active" data-bs-toggle="tab" href="#ifilter_tab_bar_filter" data-sidebar-tab="true" role="tab" tabindex="0"&gt;
                              &lt;i class="fs-3 bi bi-filter"&gt;&lt;/i&gt;
                              &lt;div class="ifilter_chart_left_nav_item_text"&gt;
                                  Filter
                              &lt;/div&gt;
                          &lt;/button&gt;
                          
                        &lt;button aria-label="Config" aria-selected="false" class="ifilter_chart_left_nav_btn" data-bs-toggle="tab" href="#ifilter_tab_bar_config" data-sidebar-tab="true" role="tab" tabindex="-1"&gt;
                            &lt;i class="fs-3 bi bi-grid-fill"&gt;&lt;/i&gt;
                            &lt;div class="ifilter_chart_left_nav_item_text"&gt;
                                Config
                            &lt;/div&gt;
                        &lt;/button&gt;                          
                      &lt;/div&gt;
                  &lt;/div&gt;
              &lt;/div&gt;
          &lt;/nav&gt;
          &lt;div style="position: relative;"&gt;
              &lt;div aria-label="左側內容" class="ifilter_chart_left_sidebar" style="height: 100%"&gt;
                  &lt;div class="ifilter_chart_left_sidebar_grid collapse show" id="collapse_bar_sidebar"&gt;
                      &lt;div class="tab-content ifilter_chart_left_sidebar_grid_container" style="position: relative; box-sizing: border-box; will-change: width; width: 280px; height: 100%; max-width: 50vw; min-width: 280px;"&gt;
                          &lt;div class="tab-pane fade active show" id="ifilter_tab_bar_filter" role="tabpanel"&gt;
&lt;!-- your filter block here --&gt;
                          &lt;/div&gt;
                          &lt;div class="tab-pane fade" id="ifilter_tab_bar_config"&gt;
&lt;!-- your config block here --&gt;
                          &lt;/div&gt;
                      &lt;/div&gt;
                  &lt;/div&gt;
              	  
              	  &lt;button aria-label="收合按鈕"
              	  	data-bs-toggle="collapse" data-bs-target="#collapse_bar_sidebar" aria-expanded="true" aria-controls="collapse_sidebar" 
              	  	class="btn btn-primary ifilter_chart_collapse_button ifilter_chart_collapse_icon ifilter_sidebar_button collapsed"&gt;
				    &lt;svg class="ifilter_chart_collapse_icon_svg" height="12" viewbox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"&gt;
				        &lt;path d="M2.22 4.47c.3-.3.77-.3 1.06 0L6 7.19l2.72-2.72a.75.75 0 0 1 1.06 1.06L6.53 8.78c-.3.3-.77.3-1.06 0L2.22 5.53a.75.75 0 0 1 0-1.06Z" fill="currentColor"&gt;
				        &lt;/path&gt;
				    &lt;/svg&gt;
				&lt;/button&gt;
              &lt;/div&gt;
          &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class="ifilter_chart_canvas"&gt;
&lt;!-- your main canvas here --&gt;
      &lt;/div&gt;
  &lt;/div&gt;
</code></pre>



<p class="wp-block-paragraph">css:</p>



<pre class="wp-block-code"><code>div.ifilter_chart {
    display: flex;
    height: 100%;	
}

.ifitler_chart_left_side_bar{
    display: flex;
    /*
    // why remark this become height: 100% 
    height: 100%; */
    position: relative;
    z-index: 5;	
}

.ifilter_chart_left_nav {
    background-color: rgb(255, 255, 255);
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 64px;
    border-inline-end: 1px solid rgb(230, 230, 230);
}

.ifilter_chart_left_nav_grid {
    flex-grow: 1;
    align-self: stretch;
    position: relative;
    overflow: hidden;
}

.ifilter_chart_left_nav_item {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-top: 0px;
    /* overflow-y: scroll; */
}

.ifilter_chart_left_nav_btn.active {
    opacity: 1;
    color: var(--primary) !important;
    background: var(--rgba-primary-1);
    font-weight: 600;
    box-shadow: none;    
}
.ifilter_chart_left_nav_btn {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background: none;
    border: none;
    color: #737B8B;
    line-height: 1.1;
    padding: 4px;
    width: 64px;
    height: 64px;
    font-weight: 400;
    cursor: pointer;
    /* transition: color 100ms ease-in-out 0s, background 100ms ease-in-out 0s; */
}

.ifilter_chart_left_nav_item_text {
    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}
    
.ifilter_chart_left_sidebar {
	position: relative;
    height: 100%;
}

.ifilter_chart_left_sidebar_grid {
    background-color: rgb(255, 255, 255);
    display: flex;
    flex-direction: column;
}

.ifilter_chart_left_sidebar_media {
    background-color: rgb(255, 255, 255);
    overflow: hidden overlay;
    flex-grow: 1;
}

.ifilter_chart_left_sidebar_section {
    position: relative;
    width: 100%;
    min-height: 100%;
    display: flex;
    flex-direction: column;
}

.ifilter_chart_left_sidebar_grid_container {
    padding: 16px;
    display: flex;
    flex-direction: column;
}

.ifilter_chart_collapse_icon {
    position: absolute;
    top: calc(50% - 38px);
    height: 76px;
    width: 16px;
    color: rgb(39, 40, 54);
    transition: color 150ms ease-out 0s;
}
.ifilter_sidebar_button {
    position: absolute;
    top: calc(50% - 38px);
    transition: color 150ms ease-out 0s;	
}

.ifilter_chart_collapse_icon {
    left: 100%;
}
.ifilter_chart_collapse_icon_svg:not(.collapsed) {
    transform: rotate(90deg);
}
.ifilter_chart_collapse_icon_svg.collapsed {
    transform: rotate(270deg);
}

.ifilter_chart_collapse_icon_svg {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -6px;
    margin-top: -6px;
}

.ifilter_chart_collapse_button {
    text-align: inherit;
    padding: 0px;
    border: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
    display: flex;
    -webkit-box-pack: center;
    justify-content: center;
    -webkit-box-align: center;
    align-items: center;
}

.ifilter_chart_canvas {
	width: 100%;
	text-align: -webkit-center;
	padding-top: 3rem;
    margin-left: 20px;
}</code></pre>



<p class="wp-block-paragraph">javascript:</p>



<pre class="wp-block-code"><code>var collapseElementList = &#91;].slice.call(document.querySelectorAll('.ifilter_chart_left_nav_btn'));
collapseElementList.map(function (collapseEl) {
	collapseEl.addEventListener("click", function(event){
        let targetElement = event.target || event.srcElement;
        //console.log(targetElement);
        if(targetElement.tagName=="I" || targetElement.tagName=="DIV") {
        	targetElement = targetElement.parentNode;
            //console.log(targetElement);
        }
    	
        let btn_href = targetElement.getAttribute("href");
    	let btn_selector = "";
    	let collapse_id = "";
    	if(btn_href=="#ifilter_tab_bar_config" || btn_href=="#ifilter_tab_bar_filter") {
    		collapse_id = "collapse_bar_sidebar";
    		btn_selector = 'button&#91;data-bs-target="#'+ collapse_id +'"]';
    	}
    	const sidebar_btn = document.querySelector(btn_selector);
    	if(sidebar_btn) {
    		//console.log(sidebar_btn.getAttribute("class"));
    		if(sidebar_btn.getAttribute("class").indexOf("collapsed") > -1) {
    			const bsCollapse = new bootstrap.Collapse(document.querySelector('#'+collapse_id));
    			bsCollapse.toggle();
    		}
    	}
    });
});


var collapseElementList = &#91;].slice.call(document.querySelectorAll('.ifilter_chart_left_sidebar_grid'));
collapseElementList.map(function (collapseEl) {
	// hide.bs.collapse
	const element_selector = "button&#91;data-bs-target=\"#"+ collapseEl.getAttribute("id") +"\"] svg.ifilter_chart_collapse_icon_svg";
	collapseEl.addEventListener("hidden.bs.collapse", function(event){
		$(element_selector).addClass("collapsed");
	});

	// show.bs.collapse
	collapseEl.addEventListener("shown.bs.collapse", function(event){
		$(element_selector).removeClass("collapsed");
	});
});
</code></pre>



<p class="wp-block-paragraph">說明：你的使用情況可能比我簡單很多，我因為有多個圖表要共用 css, 所以程式碼會複雜一點點。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2024/05/sidebar-with-collapse/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>讓三欄式網格式的圖片自動縮小</title>
		<link>https://stackoverflow.max-everyday.com/2024/02/row-cols-md-3/</link>
					<comments>https://stackoverflow.max-everyday.com/2024/02/row-cols-md-3/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 05 Feb 2024 02:24:52 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[Bootstrap]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=5418</guid>

					<description><![CDATA[在套用bootstrap 時, 不小心把 img...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">在套用bootstrap 時, 不小心把 img tag 放到 card 裡, 大多數的情況下, image 是以原圖方式顯示, 縮小的話, 需要自定寬度, 如果設定絕對的寬度, 就需要自義左右造成的空白間隙.</p>



<p class="wp-block-paragraph">比較方便的做法, 是直接填滿某一個網格, 然後再格子裡設定間距, 做出來的執行效果:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="999" height="867" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2024/02/chrome_2024-02-05_10-11_9t.png?v=1707099492" alt="" class="wp-image-5419" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2024/02/chrome_2024-02-05_10-11_9t.png?v=1707099492 999w, https://stackoverflow.max-everyday.com/wp-content/uploads/2024/02/chrome_2024-02-05_10-11_9t-600x521.png?v=1707099492 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2024/02/chrome_2024-02-05_10-11_9t-768x667.png?v=1707099492 768w" sizes="auto, (max-width: 999px) 100vw, 999px" /></figure>
</div>


<p class="wp-block-paragraph">中間神奇的關鍵是 row-cols-md-3, 滿神奇的, 加了這個class , 圖片就自動縮到格子裡, 後面加 g-3 是要讓邊邊不要直接摸到圖片的線條。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2024/02/row-cols-md-3/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>bootstrape validation 使用心得</title>
		<link>https://stackoverflow.max-everyday.com/2023/09/bootstrape-validation/</link>
					<comments>https://stackoverflow.max-everyday.com/2023/09/bootstrape-validation/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 26 Sep 2023 09:23:59 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[javascript筆記]]></category>
		<category><![CDATA[Bootstrap]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=5064</guid>

					<description><![CDATA[為什麼要使用 validation, 因為這個使...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="831" height="652" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2023/09/2023-09-26-16_54_15-Window.jpg" alt="" class="wp-image-5066" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2023/09/2023-09-26-16_54_15-Window.jpg?v=1695719684 831w, https://stackoverflow.max-everyday.com/wp-content/uploads/2023/09/2023-09-26-16_54_15-Window-600x471.jpg?v=1695719684 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2023/09/2023-09-26-16_54_15-Window-768x603.jpg?v=1695719684 768w" sizes="auto, (max-width: 831px) 100vw, 831px" /></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">為什麼要使用 validation, 因為這個使用上很直覺, 可以直接跟user講那一個欄位分別是合法,還有各自的錯誤訊息, 也不會因讓畫面有奇怪的彈跳, 使用的效果相較於彈出式的alert/dialog/modal 而言, 比較推薦使用.</p>



<p class="wp-block-paragraph">在 input tag, 並不是使用 <code>onchange </code>來觸發每一個輸入的字元, <code>onchange </code>是在離開input 輸入框時, 才會觸發, 每打一個字, 使用的是事件是 <code>oninput</code>.</p>



<p class="wp-block-paragraph">在 input 的 parent 加入 <code>.was-validated</code> 就可以觸發讓輸入框有打勾與&#8221;必填&#8221;的視覺上的效果. 如果是要類似的 ajax 的使用方法, 必需在&#8221;完成檢查&#8221; 並且沒有錯誤, 才 addClass, 反之要 removeClasss, 就可以有上面的效果.</p>



<p class="wp-block-paragraph">但是, 如果是要在輸入框有值, 且要顯示為錯誤, 請手動讓正確的 form-control 加入 class <code>.is-valid</code> 有錯的 form-control 加入  <code>.is-invalid</code> .</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">官方的說明:<br><a href="https://getbootstrap.com/docs/5.3/forms/validation/">https://getbootstrap.com/docs/5.3/forms/validation/</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2023/09/bootstrape-validation/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>tabulator 的寬度無法overflow:hidden</title>
		<link>https://stackoverflow.max-everyday.com/2023/08/tabulator-width-overflow-hidden/</link>
					<comments>https://stackoverflow.max-everyday.com/2023/08/tabulator-width-overflow-hidden/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Fri, 04 Aug 2023 10:33:26 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[css]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=4968</guid>

					<description><![CDATA[正常情況下, overflow 的部分被隱藏, ...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">正常情況下, overflow 的部分被隱藏, 並在內部元件產生橫向的scroll bar.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="535" height="578" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2023/08/2023-08-04-18_24_54-Window.jpg" alt="" class="wp-image-4970"/></figure>



<p class="wp-block-paragraph">異常情況, 是無法產生 scrollbar, 畫面變超大, </p>



<p class="wp-block-paragraph">努力地研究 tabulator 官方文件, 也看不出所以然:<br><a href="https://tabulator.info/docs/5.5/layout#fittowidth">https://tabulator.info/docs/5.5/layout#fittowidth</a></p>



<p class="wp-block-paragraph">手動調整 tabulator 的 table width:100% 也無解.</p>



<p class="wp-block-paragraph"><strong>產成的原因:</strong></p>



<p class="wp-block-paragraph">某一個爸爸 div 讓 tabulator 繼承到 display: grid;  而且上層又是用 flex, 交叉使用就掛掉.</p>



<p class="wp-block-paragraph">解法1: 避開 grid 就OK了. </p>



<p class="wp-block-paragraph">解法2: 在 tabulator 的上層設成 display: grid 居然也行, 真太神奇了, 呵呵. </p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2023/08/tabulator-width-overflow-hidden/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
