1055 字
5 分钟
Shirone 像素级打磨:文章元数据(Meta)与悬停交互的重构

在上篇文章中,我们搞定了卡片等高和基础截断。但当你放大页面仔细看时,一定还会发现一堆让人血压升高的细节:

  • “置顶”图钉怎么是个方块?
  • 分类图标、标签图标怎么比文字飘高了一点点?
  • 分类和标签之间距离太远,强行换行后对齐又全乱了?
  • 底部的日期和字数怎么才能加上阅读时间?

这篇文章记录了我对 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.6

2. 解决斜杠的“不对称”#

以前用 <span>/</span> 会受 HTML 换行符影响,导致左右间距不一样。最终我使用了 CSS 伪元素:

&__link--with-sep
&::before
content: "/"
display: inline-block
margin: 0 0.4rem
color: var(--outline-variant)
font-size: 0.875rem

3. 终极对齐:图标的物理位移#

在纯文本流里,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 统一控制小图标的尺寸,防止它们大小不一。


🪄 第四阶段:列表模式的悬停魔法#

最后,我们要实现右侧封面的“高级感”。

  • 平时:图片左侧有渐变虚化,融入背景。
  • 悬停:虚化消失,边缘变清晰,图片微微放大。

实现原理:

  1. 放大效果使用 transform: scale(1.05)。
  2. 虚化消失使用 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%)
  1. 极其重要的一点:绝对不要为了“禁止点击”而给封面加上 pointer-events: none;,否则鼠标悬停事件会被直接穿透,放大动画永远不会触发!直接使用 <div> 包裹图片即可,因为 div 本身就没有链接跳转功能。

总结#

这次重构,让我深刻体会到了 CSS 的“玄学”与乐趣。从 display: contents 的妙用,到 -webkit-line-clamp 的截断,再到 transform 的物理像素级微调。每一个微小的细节,最终汇聚成了极其舒适的阅读体验。

至此,我的 Shirone 卡片改造终于画上了完美的句号。

Shirone 像素级打磨:文章元数据(Meta)与悬停交互的重构
https://shirone.mysqil.com/posts/shirone-像素级打磨/
作者
残梦断忆
发布于
2026-10-25
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取