1227 字
6 分钟
Fuwari文章分享功能实现
2026-05-06
Purely By AI

为 Fuwari 主题实现两个功能:

  1. 分享按钮 — 文章页面一个分享图标,点击调用系统原生分享面板(移动端)或复制链接(桌面端)
  2. 社交卡片预览 — 在 <head> 注入 og:image 等 Open Graph 标签,微信/Telegram 等客户端打开链接时自动渲染为带封面图的卡片
IMPORTANT

Fuwari 使用 Swup 做页面切换,内容区 DOM 会被整体替换,所以事件绑定不能依赖首次加载时的 getElementById,要用内联 onclick 或事件委托

一、社交卡片:注入 og:image#

1.1 问题#

Fuwari 的 Layout.astro 已有 og:titleog:descriptionog: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()}>}

关键: ogImagebanner 完全独立。Layout.astro 第 47 行会强制把 banner 覆盖为站点配置的 banner 图,所以不能复用 bannerog: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 换行时不影响按钮位置

四、验证#

构建后检查:

  1. 有封面图的文章:<head>og:image 指向文章封面图 URL
  2. 无封面图的文章:og:image 指向 fallback 头像 URL
  3. 分享按钮出现在日期/分类/标签行右侧
  4. 移动端点击:弹出系统分享面板
  5. 桌面端点击:图标变勾号 + 显示”已复制”
  6. Swup 页面切换后,新页面的分享按钮功能正常
  7. 亮色/暗色主题下按钮样式一致

文件清单#

文件操作说明
src/layouts/Layout.astro修改Props 加 ogImage,添加 og:image meta 标签
src/layouts/MainGridLayout.astro修改透传 ogImage 到 Layout
src/pages/posts/[...slug].astro修改传入 ogImagepostUrl,插入 ShareButton
src/components/ShareButton.astro新建分享按钮组件
public/favicon/gravatar.png已有og:image fallback 头像
这篇文章对你有帮助吗?