欢迎提示组件

909 字
5 分钟
欢迎提示组件
AI 摘要

Ai
欢迎提示组件
提示

本文是在博主 年华 文章:Firefly 主题 WelcomeToast 组件分析 基础上增加了自己实践过程的一些细节,转载无需和我联系,但请注明文章来源。如果侵权之处,请联系博主进行删除,谢谢~

欢迎提示组件
欢迎提示组件

创建组件#

文件路径:src/components/widget/WelcomeToast.astro

src/components/WelcomeToast.astro
<script>
const VISIT_SESSION_KEY = 'blog_visit_flag';
let hasShownToast = false;
function hasVisitedThisSession() {
try {
return sessionStorage.getItem(VISIT_SESSION_KEY) === '1';
} catch {
return false; // 隐私模式等 sessionStorage 不可用时按未访问处理
}
}
function markVisitedThisSession() {
try {
sessionStorage.setItem(VISIT_SESSION_KEY, '1');
} catch { /* 静默跳过 */ }
}
function createWelcomeToast() {
const toast = document.createElement('div');
toast.id = 'welcome-toast';
// Tailwind 样式控制:自动适配深色模式
toast.className = 'fixed bottom-4 right-4 z-50 transform translate-y-full opacity-0 transition-all duration-500 ease-out';
//使用 bg-white dark:bg-zinc-800 强制适配颜色
toast.innerHTML = `
<div class="bg-white dark:bg-zinc-800 border border-gray-200 dark:border-zinc-700 rounded-lg shadow-lg p-4 max-w-xs">
<div class="flex items-start gap-3">
<div class="flex-shrink-0 text-2xl">👋</div>
<div class="flex-1 min-w-0">
<p id="welcome-message" class="text-sm font-semibold text-gray-900 dark:text-gray-100">
正在加载...
</p>
<p class="text-xs text-gray-600 dark:text-gray-400 mt-2 italic">
欢迎来到我的博客
</p>
</div>
<button
onclick="closeWelcomeToast()"
class="flex-shrink-0 text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 transition-colors"
aria-label="关闭"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</div>
`;
document.body.appendChild(toast);
return toast;
}
function closeWelcomeToast() {
const toast = document.getElementById('welcome-toast');
if (toast) {
toast.classList.add('translate-y-full', 'opacity-0');
toast.classList.remove('translate-y-0', 'opacity-100');
setTimeout(() => { toast.remove(); }, 500);
}
}
async function fetchLocation() {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://v2.xxapi.cn/api/ip', { signal: controller.signal });
const data = await response.json();
if (data.code === 200 && data.data) {
const locationText = `你好,来自${data.data.address}的朋友`;
const messageEl = document.getElementById('welcome-message');
if (messageEl) messageEl.textContent = locationText;
}
} catch (error) {
const messageEl = document.getElementById('welcome-message');
if (messageEl) messageEl.textContent = '你好,欢迎来到我的博客';
} finally {
clearTimeout(timer);
}
}
async function showWelcomeToast() {
if (hasShownToast || hasVisitedThisSession()) return;
const toast = createWelcomeToast();
setTimeout(() => {
toast.classList.remove('translate-y-full', 'opacity-0');
toast.classList.add('translate-y-0', 'opacity-100');
}, 100);
hasShownToast = true;
markVisitedThisSession();
// 延迟到浏览器空闲时再请求第三方 IP 定位接口,避免与首屏关键资源抢带宽
const schedule = window.requestIdleCallback || function (cb) { return setTimeout(cb, 300); };
schedule(() => {
// 用户已提前关闭 toast 时跳过第三方请求,避免无效调用
if (document.getElementById('welcome-toast')) fetchLocation();
});
setTimeout(() => { closeWelcomeToast(); }, 5000);
}
function initWelcome() {
// 检查是否为首页(路径为 "/" 或 "/index.html")
const isHomePage = window.location.pathname === '/' || window.location.pathname === '/index.html';
// 只在首页显示欢迎弹窗
if (isHomePage) {
showWelcomeToast();
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initWelcome);
document.addEventListener('astro:page-load', initWelcome);
} else {
initWelcome();
}
(window as any).closeWelcomeToast = closeWelcomeToast;
</script>
<style>
/* 样式保持不变 */
#welcome-toast { max-width: calc(100vw - 2rem); }
@media (max-width: 640px) {
#welcome-toast {
top: auto; bottom: 1rem; left: 50%; right: auto;
transform: translateX(-50%) translateY(100%);
width: 90%; max-width: none;
transition: transform 0.5s ease-out, opacity 0.5s ease-out;
}
#welcome-toast.translate-y-0 { transform: translateX(-50%) translateY(0); }
#welcome-toast.translate-y-full { transform: translateX(-50%) translateY(100%); }
}
</style>

使用组件#

文件路径:src/layouts/MainGridLayout.astro

---
import WelcomeToast from "@components/widget/WelcomeToast.astro"; //欢迎提示组件
---
{/* 首页欢迎提示弹窗:SSR 按首页门控,非首页不下发该脚本(组件内仍有 pathname 兜底判断) */}
{isHomePageCheck && <WelcomeToast />}

显示逻辑配置#

  • 访客只在首次访问时显示欢迎弹窗(原作者)
function initWelcome() {
// 检查是否已显示过欢迎弹窗
if (!sessionStorage.getItem(VISIT_SESSION_KEY)) {
sessionStorage.setItem(VISIT_SESSION_KEY, 'true');
showWelcomeToast(); // 显示欢迎弹窗
}
}
  • 访客只会在首页刷新时显示,非首页刷新不会显示(正在使用)
function initWelcome() {
// 检查是否为首页(路径为 "/" 或 "/index.html")
const isHomePage = window.location.pathname === '/' || window.location.pathname === '/index.html';
// 只在首页显示欢迎弹窗
if (isHomePage) {
showWelcomeToast();
}
}
  • 访客每次刷新页面时,欢迎提示组件会重新显示,不限于文章页。
function initWelcome() {
// 显示欢迎弹窗
showWelcomeToast();
}

行为变化#

  • 26.10.03
  • 首页首次访问:正常弹窗,空闲时请求定位 API — 与之前一致
  • 同会话内整页刷新首页: 不再重复弹窗 (新行为)
  • 非首页:不再下载/执行 WelcomeToast 脚本(新行为)
  • 组件内pathname 判断保留,作为兜底

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
欢迎提示组件
https://seasir.top/posts/WelcomePrompt/
作者
Hyde
发布于
2026-06-02
许可协议
CC BY-NC-SA 4.0
相关文章智能推荐
1
恋爱计时组件
Firefly最近给博客加了个小彩蛋——「恋爱计时组件」,从零撸了一个记录纪念日的小工具。写了组件代码、配了全局注册、还兼顾了桌面端侧边栏和移动端的显示逻辑,连 TypeScript 类型定义和双方昵称/日期的配置项都琢磨得挺细。虽然只是个轻量小功能,但每次看到倒计时跳动,心里都悄悄甜一下 😊
2
PNPM 11 适配 CNB 构建安全兼容方案
Firefly解决PNPM 11+ 版本 升级安全策略后,CNB(Cloud Native Buildpacks)云构建环境依赖安装失败 的问题,同时明确临时兼容方案、长期安全最优方案、团队使用规范,兼顾项目构建可用性与供应链安全。 适用场景:使用 PNPM 11+、CNB 流水线构建、项目存在带构建脚本的第三方依赖/私有依赖 适配环境:本地开发环境、CNB 容器构建环境
3
AI 摘要实现
Firefly最近给博客折腾了个 AI 摘要功能,效果挺有意思。今天就把我在 Firefly 项目里摸爬滚打总结的实战经验全盘托出。从挑大模型、写构建脚本到搞定打字机动画,顺便分享几个我踩过的坑,希望能帮想给博客加 AI 元素的你少走点弯路。
4
自助友链申请
Firefly最近折腾了一下,终于把站里的自助友链申请功能搞定了!对比一圈后,我选了 Supabase 搭配 EdgeOne 边缘函数。从数据库配置、前端改造到函数部署,完整流程都整理好了。想给博客加个友链入口的朋友,直接来抄作业吧!
5
集成朋友圈与笔记本功能
Firefly最近折腾博客,参考大佬方案把朋友圈和笔记本功能安排上了。这篇算是我的实操复盘,补充了不少配置细节。从前端组件到后台管理,一步步带你用 GitHub Gist 零成本搞定这两个模块,想给博客加点料的朋友直接来抄作业啦。
随机文章随机推荐

评论区

文章目录