在 Eleventy(11ty)文章中做出像 WordPress 的 Feature Image 特色圖片
目錄
從 WordPress 搬家到 11ty 之後,有個想保留的一個使用體驗,就是:
每篇文章可以在文章開頭放一張「feature image」,並且順手帶出文章摘要,或一些有趣文字。
WordPress 裡這件事很自然;到了 11ty,就要自己定規格、自己做模板與樣式。
這篇文章分享我現在採用的做法:
在 frontmatter 先定義一些資料格式~
- 放 feature_image(圖片路徑)
- 放 feature_meta_text(文章摘要/或一段簡單文字)
- 放 use_feature_split(要不要啟用 Hover 特效)
你可以把它當成「像 WordPress 一樣的特色圖片機制」,只是更自由、更可控。
目標效果
一般模式(預設)
- 文章開頭顯示靜態 feature image
特效模式(可選)
- hover 時 feature image 裂成四塊飛散
- 同時顯示文章資訊文字(feature_meta_text)
步驟 1:在每篇文章 frontmatter 加上 feature image 設定
參考如下 frontmatter 範例:
---
# 文章標題
title: 從 WordPress 搬家到 Eleventy(11ty):你一定會遇到的幾個注意事項整理
# 文章描述(常用在 SEO、OpenGraph、列表摘要)
description: 從 WordPress 搬家到 Eleventy 靜態網站生成器的完整指南,整理了遷移過程中常見的注意事項、坑洞與解決方案。包含內容匯出、圖片處理、前端框架整合等實戰經驗分享。
# Feature image:文章開頭的主圖(你用相對路徑)
feature_image: "../img/feature-images/11ty-notes.png"
# 是否啟用「裂開 hover 特效」
# false = 用一般 <img> 呈現
# true = 用特效版本(四塊碎片 + 文字)
use_feature_split: false
# 特效模式時要顯示的文章資訊文字(簡短、像副標)
feature_meta_text: WordPress 到 Eleventy 的遷移之旅
# 發佈時間(用 ISO 格式很標準)
date: 2025-12-22T10:00:00.000Z
# metadata 結構(分類、文章型態、canonical url)
metadata:
categories:
- 架設網站
- 靜態網站生成器
type: wordpress-migration
url: https://kamadiam.com/wordpress-to-eleventy-migration/
# tags(文章標籤)
tags:
- wordpress
- eleventy
- 11ty
- 靜態網站
- 網站搬家
- 遷移指南
- 前端開發
- 網站優化
# 指定 Markdown 的模板引擎, 可以避免不同程式語法衝突
templateEngineOverride: md
---frontmatter 可以很簡單的自定義資料格式,可以控制文章的顯示或行為。
- feature_meta_text 建議保持「一句話」即可,越短越像 WordPress 的「精選摘要」
- use_feature_split 預設 false,要用特效的文章再開即可,這個是額外加入的小趣味。
步驟 2:在 post.njk 加入條件邏輯
修改的主要邏輯是:
- 如果 feature_image 存在 → 即顯示 feature image
- 如果 use_feature_split 且 feature_image 存在 → feature image 要加上特效
請參考範例,(PS:nunjucks 註解格式為 {# ... #}) :
{#
============================================================
Feature image 渲染入口:
- use_feature_split = true → 顯示裂開特效版本
- use_feature_split = false → 顯示一般 <img>
============================================================
#}
{# 1) 這裏是特效模式:需要 use_feature_split=true 且有 feature_image #}
{%- if use_feature_split and feature_image %}
{# 你自訂的 filter:把 frontmatter 的相對路徑,解析成可用的 URL #}
{%- set resolvedImagePath = feature_image | resolveImagePath %}
{# 確保解析成功才渲染,避免 broken image #}
{%- if resolvedImagePath %}
<div class="feature-wrap">
{# figure:使用 CSS 變數 --img 把圖片 URL 傳給 CSS #}
<figure
class="feature-split"
aria-label="Article feature image"
style="--img: url('{{ resolvedImagePath }}')"
>
{# 視覺底座:提供圓角/背景框(因為不使用 overflow hidden) #}
<div class="feature-frame" aria-hidden="true"></div>
{# 四塊碎片:四個 span 用同一張圖切成 2x2 #}
<div class="tiles" aria-hidden="true">
<span class="tile t1"></span>
<span class="tile t2"></span>
<span class="tile t3"></span>
<span class="tile t4"></span>
</div>
{# 底部文字資訊:如果有 feature_meta_text 才顯示 #}
{%- if feature_meta_text %}
<div class="feature-text">{{ feature_meta_text }}</div>
{%- endif %}
</figure>
</div>
{%- endif %}
{# 2) 這裏是一般模式:有 feature_image 就用普通 img #}
{%- elif feature_image %}
{# 這裡直接用 feature_image(相對路徑)顯示圖片 #}
<img
src="{{ feature_image }}"
alt="Feature image for {{ title }}"
class="post-feature-image"
>
{%- endif %}
步驟 3:套用 CSS,做出「裂成四塊」的核心效果
效果就是自由發揮,如果不想只是顯示一張圖,讓文章有趣一點,或是埋藏彩蛋,可以自由修改 html 搭配 css 展現特別效果。
/* ============================================================
feature-wrap:用 padding + 負 margin 擴大 hover 觸發區域
目的:讓滑鼠不要一定得壓在圖片上,稍微靠近也能觸發
============================================================ */
.feature-wrap {
position: relative;
/* 擴大 hover 區域(包含圖片周圍) */
padding: var(--space-2xl);
/* 用負 margin 把版面位置「抵回來」,避免整體被撐大 */
margin: calc(-1 * var(--space-2xl));
}
/* ============================================================
feature-split:核心容器(不裁切,碎片可以飛出去)
--img:圖片 URL(由 inline style 傳進來)
--radius:圓角
--spread:裂開距離
--lift:hover 上浮高度
============================================================ */
.feature-split {
--img: url("");
--radius: var(--space-3xs);
--spread: var(--space-l);
--lift: var(--space-s);
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
/* ✅ 不裁切:碎片可以飛出原本框 */
overflow: visible;
border-radius: var(--radius);
/* 初始位置明確指定 translateY(0),避免 hover 回彈視覺跳一下 */
transform: translateY(0) translateZ(0);
/* 只讓 transform 動畫(效能最好) */
transition: transform 620ms cubic-bezier(.2, .85, .2, 1);
margin: var(--space-m) 0;
}
/* ============================================================
feature-frame:視覺底座
因為 overflow 沒有 hidden,不能靠裁切做圓角
所以用這層提供背景/邊框/圓角,讓「合起來」時像一張完整圖
============================================================ */
.feature-frame {
position: absolute;
inset: 0;
background: var(--neutral-color);
border-radius: var(--radius);
pointer-events: none;
}
/* ============================================================
tiles:四塊碎片的容器
============================================================ */
.tiles {
position: absolute;
inset: 0;
border-radius: var(--radius);
}
/* ============================================================
tile:單一碎片
技術重點:
- 每塊 50% x 50%
- 背景圖放大成 200% 200%(因為只顯示四分之一)
- 用 background-position 決定顯示哪一個象限
============================================================ */
.tile {
position: absolute;
width: 50%;
height: 50%;
/* ✅ 同一張圖,放大兩倍再切成四塊 */
background: var(--img) center / 200% 200% no-repeat;
border-radius: calc(var(--radius) - var(--space-3xs));
/* 合起來時不顯示邊框,裂開時才顯示(避免抖動就用 transition) */
border: 1px solid transparent;
box-shadow: 0 var(--space-s) var(--space-l) rgba(0, 0, 0, .22);
/* ✅ 動畫只做 transform/filter/opacity/border-color(效能好) */
transition:
transform 620ms cubic-bezier(.2, .85, .2, 1),
filter 620ms cubic-bezier(.2, .85, .2, 1),
opacity 620ms cubic-bezier(.2, .85, .2, 1),
border-color 620ms cubic-bezier(.2, .85, .2, 1);
will-change: transform;
backface-visibility: hidden;
}
/* ============================================================
四塊的位置與對應的背景象限
============================================================ */
.t1 { left: 0; top: 0; background-position: 0% 0%; }
.t2 { left: 50%; top: 0; background-position: 100% 0%; }
.t3 { left: 0; top: 50%; background-position: 0% 100%; }
.t4 { left: 50%; top: 50%; background-position: 100% 100%; }
/* ============================================================
feature-text:hover 時顯示的文字資訊
============================================================ */
.feature-text {
position: absolute;
left: 50%;
top: 50%;
/* 初始略縮小 + 隱藏 */
transform: translate(-50%, -50%) scale(0.9);
opacity: 0;
transition: transform 520ms cubic-bezier(.2, .85, .2, 1), opacity 520ms ease;
pointer-events: none;
color: var(--text-color);
font-size: var(--step--1);
font-weight: 500;
letter-spacing: .2px;
line-height: 1.6;
text-align: center;
white-space: nowrap;
background: rgba(255, 255, 255, .92);
backdrop-filter: blur(12px);
border-radius: var(--space-l);
border: 1px solid var(--color-gray-20);
box-shadow: 0 var(--space-l) var(--space-2xl) rgba(0, 0, 0, .15);
padding: var(--space-2xs) var(--space-2xs);
max-width: 80%;
}
/* ============================================================
Hover 動畫(只在支援 hover 的裝置上)
============================================================ */
@media (hover: hover) and (pointer: fine) {
/* 容器上浮(你同時支援 feature-wrap:hover 與 figure:hover) */
.feature-wrap:hover .feature-split,
.feature-split:hover {
transform: translateY(calc(-1 * var(--lift))) translateZ(0);
}
/* 四塊向四個方向飛散 + 旋轉 */
.feature-wrap:hover .feature-split .t1,
.feature-split:hover .t1 {
transform: translate(calc(-1 * var(--spread)), calc(-1 * var(--spread))) rotate(-4deg);
}
.feature-wrap:hover .feature-split .t2,
.feature-split:hover .t2 {
transform: translate(var(--spread), calc(-1 * var(--spread))) rotate(4deg);
}
.feature-wrap:hover .feature-split .t3,
.feature-split:hover .t3 {
transform: translate(calc(-1 * var(--spread)), var(--spread)) rotate(3deg);
}
.feature-wrap:hover .feature-split .t4,
.feature-split:hover .t4 {
transform: translate(var(--spread), var(--spread)) rotate(-3deg);
}
/* 碎片顏色更有精神 + 裂開時顯示邊框 */
.feature-wrap:hover .feature-split .tile,
.feature-split:hover .tile {
filter: brightness(.92) saturate(1.12) contrast(1.03);
border-color: var(--color-gray-20);
}
/* 裂縫光出現 */
.feature-wrap:hover .feature-split .crack,
.feature-split:hover .crack {
opacity: 1;
}
/* 文字卡片縮放淡入 */
.feature-wrap:hover .feature-split .feature-text,
.feature-split:hover .feature-text {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
}
}CSS 技術實作原理
一張圖切四塊,不需要四張圖片
每個 .tile 都是同一張背景圖:
- tile 本身只有 50% × 50%
- 但背景圖用 200% 200% 放大
- 然後用 background-position 指定顯示哪個象限
所以你只要準備 一張 feature image,就能切出四塊。
為什麼動畫只用 transform?
transform(位移/旋轉/縮放)通常可以走 GPU 合成層,效能最好。
如果你用 top/left 去動,瀏覽器比較容易重新排版,會卡。
最後:整合到 11ty 的使用方式
你的使用方式會長這樣:
- 一般文章(不需要特效):use_feature_split: false
- 特別文章(想要效果):use_feature_split: true
在 11ty 只要維持一致的 frontmatter key,就能像 WordPress 一樣「每篇都有封面」,而且還能加進你的個人風格(裂開特效、資訊卡片、系列標籤都能玩)。
另外 手機沒有 hover,可以改成「進入視窗」才顯示文字,利用 IntersectionObserver 加 style class。
以上範例也可以獨立拆成獨立檔案例如 feature_image.njk,讓 post.njk 只要 {% include "feature_image.njk" %} 一行,管理 layout 更省心力。



