每一张卡片,都是一句诗。每一次切换,都是一次与古人的相遇。
这个卡片特效,可以在博客侧边栏、首页横幅或任意位置嵌入。它会在已收录的诗词中自动轮播,用户也可以手动切换。
效果演示
完整代码如下,可直接保存为 .html 文件在浏览器打开:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>诗词随机卡片 · 南枝已谢</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #faf8f5;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
font-family: 'Georgia', 'Songti SC', serif;
}
/* ========== 卡片容器 ========== */
.poetry-slider {
position: relative;
width: 420px;
max-width: 90vw;
}
/* ========== 卡片 ========== */
.poetry-card {
position: relative;
background: #fdf5ea;
border-left: 4px solid #c9a96e;
border-radius: 0 16px 16px 0;
padding: 32px 36px 28px;
cursor: pointer;
transition: transform 0.3s ease, box-shadow 0.3s ease;
overflow: hidden;
min-height: 180px;
display: flex;
flex-direction: column;
justify-content: center;
}
.poetry-card:hover {
transform: translateY(-3px);
box-shadow: 0 12px 40px rgba(139, 111, 71, 0.12);
}
/* 左上角装饰圆点 */
.poetry-card::before {
content: '';
position: absolute;
top: -20px;
right: -20px;
width: 80px;
height: 80px;
background: radial-gradient(circle, rgba(201,169,110,0.12) 0%, transparent 70%);
border-radius: 50%;
pointer-events: none;
}
/* 标签 */
.card-tag {
display: inline-block;
font-family: -apple-system, sans-serif;
font-size: 10px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: #8b6f47;
background: rgba(139, 111, 71, 0.1);
padding: 3px 12px;
border-radius: 20px;
border: 1px solid rgba(139, 111, 71, 0.2);
margin-bottom: 14px;
width: fit-content;
}
/* 诗句 */
.card-quote {
font-size: 20px;
line-height: 1.8;
color: #3d2e1f;
letter-spacing: 1px;
margin-bottom: 14px;
min-height: 72px;
display: flex;
align-items: center;
}
/* 作者信息 */
.card-meta {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-author {
font-family: -apple-system, sans-serif;
font-size: 13px;
color: #8b6f47;
font-weight: 600;
}
.card-dynasty {
font-family: -apple-system, sans-serif;
font-size: 11px;
color: #9c8c7a;
margin-left: 6px;
}
.card-hint {
font-family: -apple-system, sans-serif;
font-size: 10px;
color: #b0a090;
letter-spacing: 1px;
}
/* ========== 切换动画 ========== */
.poetry-card.fade-out {
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.poetry-card.fade-in {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s ease, transform 0.3s ease;
}
/* ========== 底部切换按钮 ========== */
.slider-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin-top: 16px;
}
.slider-btn {
background: none;
border: 1px solid rgba(139, 111, 71, 0.3);
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #8b6f47;
font-size: 18px;
line-height: 1;
transition: all 0.2s ease;
padding: 0;
font-family: -apple-system, sans-serif;
flex-shrink: 0;
}
.slider-btn:hover {
background: rgba(201, 169, 110, 0.15);
border-color: #c9a96e;
color: #6b5235;
transform: scale(1.08);
}
.slider-btn:active {
transform: scale(0.95);
}
/* 指示点 */
.slider-dots {
display: flex;
gap: 6px;
}
.slider-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(139, 111, 71, 0.2);
border: none;
cursor: pointer;
transition: all 0.3s ease;
}
.slider-dot.active {
background: #c9a96e;
width: 20px;
border-radius: 3px;
}
/* 自动播放标识 */
.auto-badge {
position: absolute;
top: 16px;
right: 16px;
font-family: -apple-system, sans-serif;
font-size: 9px;
color: #c9a96e;
letter-spacing: 1px;
text-transform: uppercase;
opacity: 0.6;
}
.auto-badge.paused { color: #9c8c7a; }
</style>
</head>
<body>
<div class="poetry-slider">
<div class="auto-badge" id="autoBadge">● AUTO</div>
<div class="poetry-card" id="card">
<span class="card-tag" id="cardTag">词</span>
<div class="card-quote" id="cardQuote">落花人独立,微雨燕双飞。</div>
<div class="card-meta">
<div>
<span class="card-author" id="cardAuthor">临江仙 · 晏几道</span>
<span class="card-dynasty" id="cardDynasty">北宋</span>
</div>
<div class="card-hint">点击切换</div>
</div>
</div>
<div class="slider-controls">
<button class="slider-btn" onclick="prevPoem(event)" title="上一首">‹</button>
<div class="slider-dots" id="sliderDots"></div>
<button class="slider-btn" onclick="nextPoem(event)" title="下一首">›</button>
</div>
</div>
<script>
/* ========== 诗词数据 ========== */
const poems = [
{ type: '词', quote: '落花人独立,微雨燕双飞。', author: '晏几道', dynasty: '北宋', title: '临江仙' },
{ type: '词', quote: '才下眉头,却上心头。', author: '李清照', dynasty: '北宋', title: '一剪梅' },
{ type: '词', quote: '多情自古伤离别,更那堪冷落清秋节。', author: '柳永', dynasty: '北宋', title: '雨霖铃' },
{ type: '词', quote: '大江东去,浪淘尽,千古风流人物。', author: '苏轼', dynasty: '北宋', title: '念奴娇' },
{ type: '词', quote: '两情若是久长时,又岂在朝朝暮暮。', author: '秦观', dynasty: '北宋', title: '鹊桥仙' },
{ type: '诗', quote: '飞流直下三千尺,疑是银河落九天。', author: '李白', dynasty: '唐', title: '望庐山瀑布' },
{ type: '词', quote: '天涯何处无芳草。', author: '苏轼', dynasty: '北宋', title: '蝶恋花' },
{ type: '词', quote: '一蓑烟雨任平生。', author: '苏轼', dynasty: '北宋', title: '定风波' },
{ type: '词', quote: '也无风雨也无晴。', author: '苏轼', dynasty: '北宋', title: '定风波' },
{ type: '诗', quote: '会当凌绝顶,一览众山小。', author: '杜甫', dynasty: '唐', title: '望岳' },
{ type: '词', quote: '人生如逆旅,我亦是行人。', author: '苏轼', dynasty: '北宋', title: '临江仙' },
{ type: '诗', quote: '野火烧不尽,春风吹又生。', author: '白居易', dynasty: '唐', title: '赋得古原草送别' },
{ type: '诗', quote: '春风得意马蹄疾,一日看尽长安花。', author: '孟郊', dynasty: '唐', title: '登科后' },
{ type: '诗', quote: '此情可待成追忆,只是当时已惘然。', author: '李商隐', dynasty: '唐', title: '锦瑟' },
{ type: '词', quote: '何如当初莫相识。', author: '李白', dynasty: '唐', title: '秋风词' },
{ type: '词', quote: '醉乡路稳宜频到,此外不堪行。', author: '李煜', dynasty: '南唐', title: '乌夜啼' },
{ type: '词', quote: '而今识尽愁滋味,欲说还休。', author: '辛弃疾', dynasty: '南宋', title: '丑奴儿' },
{ type: '词', quote: '却道天凉好个秋。', author: '辛弃疾', dynasty: '南宋', title: '丑奴儿' },
{ type: '诗', quote: '抽刀断水水更流,举杯消愁愁更愁。', author: '李白', dynasty: '唐', title: '宣州谢朓楼饯别' },
{ type: '诗', quote: '满船清梦压星河。', author: '唐珙', dynasty: '元', title: '题龙阳县青草湖' },
{ type: '词', quote: '从别后,忆相逢,几回魂梦与君同。', author: '晏几道', dynasty: '北宋', title: '鹧鸪天' },
{ type: '词', quote: '那人却在灯火阑珊处。', author: '辛弃疾', dynasty: '南宋', title: '青玉案' },
{ type: '诗', quote: '人生代代无穷已,江月年年望相似。', author: '张若虚', dynasty: '唐', title: '春江花月夜' },
{ type: '诗', quote: '江畔何人初见月?江月何年初照人?', author: '张若虚', dynasty: '唐', title: '春江花月夜' },
{ type: '词', quote: '但愿人长久,千里共婵娟。', author: '苏轼', dynasty: '北宋', title: '水调歌头' },
{ type: '诗', quote: '天生我材必有用,千金散尽还复来。', author: '李白', dynasty: '唐', title: '将进酒' },
{ type: '词', quote: '问君能有几多愁?恰似一江春水向东流。', author: '李煜', dynasty: '南唐', title: '虞美人' },
{ type: '词', quote: '雕栏玉砌应犹在,只是朱颜改。', author: '李煜', dynasty: '南唐', title: '虞美人' },
{ type: '词', quote: '林花谢了春红,太匆匆。', author: '李煜', dynasty: '南唐', title: '乌夜啼' },
{ type: '词', quote: '流水落花春去也,天上人间。', author: '李煜', dynasty: '南唐', title: '浪淘沙' },
{ type: '词', quote: '觉来惆怅消魂误。', author: '晏几道', dynasty: '北宋', title: '蝶恋花' },
{ type: '词', quote: '舞低杨柳楼心月,歌尽桃花扇底风。', author: '晏几道', dynasty: '北宋', title: '鹧鸪天' },
{ type: '诗', quote: '春风又绿江南岸,明月何时照我还。', author: '王安石', dynasty: '北宋', title: '泊船瓜洲' },
{ type: '诗', quote: '千山鸟飞绝,万径人踪灭。', author: '柳宗元', dynasty: '唐', title: '江雪' },
{ type: '诗', quote: '海内存知己,天涯若比邻。', author: '王勃', dynasty: '唐', title: '送杜少府之任蜀州' },
];
let currentIndex = 0;
let autoTimer = null;
let isPlaying = true;
const INTERVAL = 5000;
const card = document.getElementById('card');
const cardTag = document.getElementById('cardTag');
const cardQuote = document.getElementById('cardQuote');
const cardAuthor = document.getElementById('cardAuthor');
const cardDynasty = document.getElementById('cardDynasty');
const sliderDots = document.getElementById('sliderDots');
const autoBadge = document.getElementById('autoBadge');
// 构建指示点
function buildDots() {
sliderDots.innerHTML = '';
for (let i = 0; i < poems.length; i++) {
const dot = document.createElement('button');
dot.className = 'slider-dot' + (i === currentIndex ? ' active' : '');
dot.onclick = (e) => { e.stopPropagation(); goTo(i); };
dot.title = poems[i].title;
sliderDots.appendChild(dot);
}
}
// 更新卡片内容
function updateCard(poem) {
card.classList.add('fade-out');
setTimeout(() => {
cardTag.textContent = poem.type;
cardQuote.textContent = poem.quote;
cardAuthor.textContent = `${poem.title} · ${poem.author}`;
cardDynasty.textContent = poem.dynasty;
card.classList.remove('fade-out');
card.classList.add('fade-in');
setTimeout(() => card.classList.remove('fade-in'), 300);
}, 300);
}
// 更新指示点
function updateDots() {
const dots = sliderDots.querySelectorAll('.slider-dot');
dots.forEach((d, i) => {
d.className = 'slider-dot' + (i === currentIndex ? ' active' : '');
});
}
// 切换到指定诗词
function goTo(index) {
currentIndex = index;
updateCard(poems[currentIndex]);
updateDots();
resetTimer();
}
// 下一首
function nextPoem(e) {
if (e) e.stopPropagation();
currentIndex = (currentIndex + 1) % poems.length;
updateCard(poems[currentIndex]);
updateDots();
resetTimer();
}
// 上一首
function prevPoem(e) {
if (e) e.stopPropagation();
currentIndex = (currentIndex - 1 + poems.length) % poems.length;
updateCard(poems[currentIndex]);
updateDots();
resetTimer();
}
// 自动播放
function startTimer() {
autoTimer = setInterval(() => nextPoem(), INTERVAL);
}
function resetTimer() {
if (autoTimer) clearInterval(autoTimer);
if (isPlaying) startTimer();
}
// 点击暂停/恢复
card.addEventListener('click', () => {
if (isPlaying) {
clearInterval(autoTimer);
isPlaying = false;
autoBadge.textContent = '○ PAUSE';
autoBadge.classList.add('paused');
} else {
isPlaying = true;
autoBadge.textContent = '● AUTO';
autoBadge.classList.remove('paused');
resetTimer();
}
});
buildDots();
startTimer();
</script>
</body>
</html>{/collapse-item}
数据结构
卡片依赖一个简洁的 JSON 数组:
const poems = [
{ type: '词', quote: '落花人独立,微雨燕双飞。', author: '晏几道', dynasty: '北宋', title: '临江仙' },
{ type: '词', quote: '一蓑烟雨任平生。', author: '苏轼', dynasty: '北宋', title: '定风波' },
{ type: '诗', quote: '但愿人长久,千里共婵娟。', author: '苏轼', dynasty: '北宋', title: '水调歌头' },
// ... 可无限扩展
];| 字段 | 说明 |
|---|---|
| type | 左上角标签,"诗"或"词" |
| quote | 卡片中央显示的名句 |
| title | 诗词标题 |
| author | 作者名 |
| dynasty | 朝代 |
核心机制
1. 淡入淡出动画
通过 CSS class 切换实现,不需要任何动画库:
.poetry-card.fade-out {
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.3s, transform 0.3s;
}
.poetry-card.fade-in {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s, transform 0.3s;
}切换时:先加 fade-out(淡出并上移)→ 300ms 后更新内容 → 再加 fade-in(淡入归位)。
2. 自动播放与手动切换
function startTimer() {
autoTimer = setInterval(() => nextPoem(), INTERVAL);
}
function resetTimer() {
clearInterval(autoTimer);
if (isPlaying) startTimer();
}每次手动切换后重置计时器,自动播放不会中断。
3. 点击暂停/恢复
点击卡片本身,切换自动播放状态。"● AUTO"变为"○ PAUSE",适合用户想停下来细读某一句诗的场景。
可调节参数
| 参数 | 默认值 | 作用 |
|---|---|---|
| INTERVAL | 5000 | 自动切换间隔(毫秒) |
| transition | 0.3s | 淡入淡出动画时长 |
| 背景色 | #fdf5ea | 暖米色卡片背景 |
| 边框色 | #c9a96e | 暖金色左侧边框 |
部署建议
独立页面:直接保存为 .HTML 文件即可运行
嵌入博客:将 <style> 部分放入博客主题的 CSS,将 <div>和 <script>部分嵌入 HTML 模板即可
接入已有诗词库:把现有的 30+ 首诗词数据导出为 JSON,替换掉 poems 数组即可

评语 (0)