在上篇文章中,我们搞定了卡片等高和基础截断。但当你放大页面仔细看时,一定还会发现一堆让人血压升高的细节:
- “置顶”图钉怎么是个方块?
- 分类图标、标签图标怎么比文字飘高了一点点?
- 分类和标签之间距离太远,强行换行后对齐又全乱了?
- 底部的日期和字数怎么才能加上阅读时间?
这篇文章记录了我对 PostMeta.astro 组件的终极重构,包含大量 CSS 玄学修复。
📌 第一阶段:“置顶”徽章的重构
原本的置顶只是一个简单的图钉 Icon。为了让它更好看,我决定把它改成带有绿色背景的胶囊形状。
在 PostCard.astro 中修改 HTML:
{pinned && ( <span class="m3-blog-postcard__pin"> <Icon name="material-symbols:keep-rounded" /> <span class="m3-blog-postcard__pin-text">置顶</span> </span>)}核心 CSS 魔法:
&__pin display: inline-flex align-items: center gap: 0.2rem padding: 0.15rem 0.5rem border-radius: 9999px background: var(--primary-container) color: var(--on-primary-container) font-size: 0.8rem font-weight: 500 line-height: 1 /* 锁死行高,防止撑大 */ vertical-align: middle transform: translateY(0px) /* 玄学微调:根据实际情况上下移动,对齐文字中线 */ margin-right: 0.4rem🔀 第二阶段:分类与标签的“缝合”与绝对对称
在默认的 Shirone 中,分类是一个 div,标签是另一个 div。这导致它们作为 Flex 子项时,如果放不下,标签会被整体挤到下一行,留下难看的空白。
1. 打碎容器,回归文本流
我们在 HTML 中把分类和标签合并进同一个 span,并在 CSS 里使用 display: contents 打碎 PostMeta 的外壳:
&__item--taxonomy display: inline /* 变成纯文本流 */ line-height: 1.62. 解决斜杠的“不对称”
以前用 <span>/</span> 会受 HTML 换行符影响,导致左右间距不一样。最终我使用了 CSS 伪元素:
&__link--with-sep &::before content: "/" display: inline-block margin: 0 0.4rem color: var(--outline-variant) font-size: 0.875rem3. 终极对齐:图标的物理位移
在纯文本流里,vertical-align: middle 对中文字符完全不友好,图标永远偏上。
解决方案:放弃 middle 的幻想,使用 transform 进行物理按压:
:global(.m3-blog-postmeta) :global(.m3-meta-icon) display: inline-flex width: 0.95em height: 0.95em vertical-align: middle transform: translateY(1px) /* 强行往下压 1px,和文字完美平齐 */ margin-right: 0.2rem⏱️ 第三阶段:底部元数据的分离与计算
为了模仿高端主题的排版,我把“分类/标签”留在了标题下方,把“日期/字数/阅读时间”拆到了卡片最底部。
1. 拆分 PostMeta
在 PostCard.astro 中调用两次 PostMeta 组件:
<!-- 上方:只显示分类和标签 --><PostMeta {published} {category} {tags} hideDate hideWordCount hideUpdate />
<!-- 下方:纯手写底部行,只显示日期和字数 --><div class="m3-blog-postcard__footer"> <span class="m3-blog-postcard__footer-item"> <Icon name="material-symbols:calendar-today-outline-rounded" /> {published} </span></div>2. 阅读时间(前端计算)
Shirone 没有自带阅读时间。我们在 frontmatter 脚本中,按 300字/分钟 粗略计算:
const readingMinutes = wordCount ? Math.max(1, Math.round(parseInt(wordCount.replace(/\D/g, ""), 10) / 300)) : null;然后把它渲染在底部:
{readingMinutes && ( <> <span class="m3-blog-postcard__footer-divider">|</span> <span class="m3-blog-postcard__footer-item"> <Icon name="material-symbols:schedule-outline-rounded" /> {readingMinutes} 分钟 </span> </>)}底部的 CSS 使用 margin-top: auto 沉底,并利用 &__footer-item 统一控制小图标的尺寸,防止它们大小不一。
🪄 第四阶段:列表模式的悬停魔法
最后,我们要实现右侧封面的“高级感”。
- 平时:图片左侧有渐变虚化,融入背景。
- 悬停:虚化消失,边缘变清晰,图片微微放大。
实现原理:
- 放大效果使用
transform: scale(1.05)。 - 虚化消失使用
mask-image的过渡:
&__cover > picture > img transition: transform 0.4s ease
.m3-blog-postcard:hover & > picture > img transform: scale(1.05)
@media (min-width: bp-md) mask-image: linear-gradient(to right, transparent 0%, black 30%) transition: mask-image 0.4s ease
.m3-blog-postcard:hover & mask-image: linear-gradient(to right, transparent 0%, black 5%)- 极其重要的一点:绝对不要为了“禁止点击”而给封面加上
pointer-events: none;,否则鼠标悬停事件会被直接穿透,放大动画永远不会触发!直接使用<div>包裹图片即可,因为div本身就没有链接跳转功能。
总结
这次重构,让我深刻体会到了 CSS 的“玄学”与乐趣。从 display: contents 的妙用,到 -webkit-line-clamp 的截断,再到 transform 的物理像素级微调。每一个微小的细节,最终汇聚成了极其舒适的阅读体验。
至此,我的 Shirone 卡片改造终于画上了完美的句号。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 6 天
部分内容可能已过时