为 Fuwari 主题实现两个功能:
- 分享按钮 — 文章页面一个分享图标,点击调用系统原生分享面板(移动端)或复制链接(桌面端)
- 社交卡片预览 — 在
<head>注入og:image等 Open Graph 标签,微信/Telegram 等客户端打开链接时自动渲染为带封面图的卡片
IMPORTANTFuwari 使用 Swup 做页面切换,内容区 DOM 会被整体替换,所以事件绑定不能依赖首次加载时的
getElementById,要用内联onclick或事件委托
一、社交卡片:注入 og:image
1.1 问题
Fuwari 的 Layout.astro 已有 og:title、og:description、og:url 等标签,但没有 og:image。没有这个标签,微信/Telegram 等客户端打开文章链接时不会显示封面图预览。
1.2 给 Layout.astro 添加 ogImage prop
编辑 src/layouts/Layout.astro,在 Props 接口中新增 ogImage 字段:
interface Props { title?: string; banner?: string; description?: string; lang?: string; setOGTypeArticle?: boolean; ogImage?: string; // 新增}解构处同步添加:
let { title, banner, description, lang, setOGTypeArticle, ogImage } = Astro.props;在 <head> 的 OG 标签区域添加:
{ogImage && <meta property="og:image" content={new URL(ogImage, Astro.site).toString()}>}关键: ogImage 与 banner 完全独立。Layout.astro 第 47 行会强制把 banner 覆盖为站点配置的 banner 图,所以不能复用 banner 做 og:image,必须用独立的 prop。
1.3 透传到 MainGridLayout.astro
MainGridLayout.astro 是 Layout 和文章页之间的中间层,需要把 ogImage 透传下去。
编辑 src/layouts/MainGridLayout.astro:
Props 接口加 ogImage?: string,解构加 ogImage,Layout 调用处加 ogImage={ogImage}:
<Layout title={title} banner={banner} description={description} lang={lang} setOGTypeArticle={setOGTypeArticle} ogImage={ogImage}>1.4 在文章页传入封面图
编辑 src/pages/posts/[...slug].astro,在 MainGridLayout 调用处添加 ogImage:
<MainGridLayout ... ogImage={entry.data.image || "/favicon/gravatar.png"} ...>逻辑:有封面图用封面图,没有则 fallback 到 public/favicon/gravatar.png(站点头像,需要放在 public/ 目录下以保证 URL 稳定)。
构建后在浏览器开发者工具检查 <head>,确认 og:image 指向正确的图片 URL。
二、分享按钮:ShareButton.astro
2.1 新建组件
创建 src/components/ShareButton.astro:
---import { Icon } from "astro-icon/components";
interface Props { title: string; url: string; class?: string;}
const { title, url } = Astro.props;const className = Astro.props.class || "";---
<div class:list={["share-btn-container inline-flex items-center", className]}> <button class="share-btn btn-plain rounded-lg h-9 w-9 flex items-center justify-center" data-title={title} data-url={url} onclick="(async function(btn){var url=btn.dataset.url;try{if(navigator.share){await navigator.share({title:btn.dataset.title,url:url})}else{await navigator.clipboard.writeText(url);btn.classList.add('success');clearTimeout(btn._t);btn._t=setTimeout(function(){btn.classList.remove('success')},1500)}}catch(e){}})(this)" > <Icon name="fa6-solid:arrow-up-from-bracket" class="share-btn-icon text-[1.1rem]" /> <Icon name="material-symbols:check-rounded" class="share-btn-success-icon text-[1.1rem]" /> </button> <span class="share-btn-text text-xs text-[var(--primary)] whitespace-nowrap">已复制</span></div>
<style is:inline>.share-btn-container { position: relative; display: inline-flex; align-items: center; gap: 0.375rem; }.share-btn .share-btn-icon { display: inline; }.share-btn .share-btn-success-icon { display: none; }.share-btn.success .share-btn-icon { display: none; }.share-btn.success .share-btn-success-icon { display: inline; }.share-btn.success { color: var(--primary) !important; }.share-btn-text { opacity: 0; transition: opacity 0.2s; }.share-btn-container .share-btn.success ~ .share-btn-text { opacity: 1; }</style>2.2 设计决策说明
为什么用内联 onclick 而不是 <script> + addEventListener?
Swup 切页时会替换 #swup-container 内的整个 DOM。如果用 document.getElementById('share-btn').addEventListener(...) ,首次加载可以工作,但 Swup 导航到新页面后,旧的 DOM 节点被移除,新节点上的事件监听器不存在了。内联 onclick 是 HTML 属性,随 DOM 一起渲染,天然兼容 Swup,不需要在 swup.hooks.on('page:view', ...) 中重新绑定。
为什么用 data-title / data-url 属性?
Astro 的 define:vars 可以把变量注入 <script>,但我们的 onclick 已经是内联的,用 data-* 属性更简洁,也避免了 HTML 属性中转义 JavaScript 的复杂性。
为什么 <style is:inline>?
Astro 默认对 <style> 做 scope 隔离(加 data-astro-cid-xxx 选择器),但我们的 onclick 是原生 HTML 属性,不在 Astro 的 scope 内,scoped 样式无法匹配到。is:inline 让样式成为全局 CSS,配合内联 onclick 正常工作。
2.3 按钮样式
- 使用 Fuwari 原生的
btn-plain类,自带 hover 变色、expand-animation涟漪效果 h-9 w-9(36px)与 Fuwari 的meta-icon尺寸一致- 图标使用
fa6-solid:arrow-up-from-bracket(分享箭头),复制成功后切换为material-symbols:check-rounded(勾号) - “已复制” 文字通过 CSS 相邻兄弟选择器
.share-btn.success ~ .share-btn-text控制显隐,无需 JavaScript 操作
2.4 点击行为
| 环境 | 行为 |
|---|---|
| iOS / Android 浏览器 | 调用 navigator.share(),弹出系统原生分享面板(可选微信、Twitter、复制链接等) |
| 桌面浏览器(无 Web Share API) | navigator.clipboard.writeText(),图标变勾号,显示”已复制”,1.5秒后恢复 |
clearTimeout(btn._t) 防止快速多次点击时 timeout 冲突。
三、集成到文章页面
编辑 src/pages/posts/[...slug].astro。
导入组件:
import ShareButton from "../../components/ShareButton.astro";获取文章 URL:
const postUrl = Astro.url.toString();将 ShareButton 放在 PostMetadata 同行右侧:
<div class="flex items-start justify-between mb-5"> <PostMetadata class="flex-1 min-w-0" ... /> <ShareButton title={entry.data.title} url={postUrl} class="ml-4 shrink-0" /></div>flex-1 min-w-0让 PostMetadata 占满剩余空间,min-w-0防止内容过长时溢出shrink-0防止 ShareButton 被压缩items-start让按钮对齐行首,PostMetadata 换行时不影响按钮位置
四、验证
构建后检查:
- 有封面图的文章:
<head>中og:image指向文章封面图 URL - 无封面图的文章:
og:image指向 fallback 头像 URL - 分享按钮出现在日期/分类/标签行右侧
- 移动端点击:弹出系统分享面板
- 桌面端点击:图标变勾号 + 显示”已复制”
- Swup 页面切换后,新页面的分享按钮功能正常
- 亮色/暗色主题下按钮样式一致
文件清单
| 文件 | 操作 | 说明 |
|---|---|---|
src/layouts/Layout.astro | 修改 | Props 加 ogImage,添加 og:image meta 标签 |
src/layouts/MainGridLayout.astro | 修改 | 透传 ogImage 到 Layout |
src/pages/posts/[...slug].astro | 修改 | 传入 ogImage 和 postUrl,插入 ShareButton |
src/components/ShareButton.astro | 新建 | 分享按钮组件 |
public/favicon/gravatar.png | 已有 | og:image fallback 头像 |