feat: 引入 Linear/Vercel 级鼠标跟随响应式交互特效:背景极光流体光球、卡片聚光灯跟随流光、3D 视差透视微倾斜与极客跟随光环
Build and Deploy Blog / deploy (push) Successful in 8s

This commit is contained in:
2026-10-22 17:43:19 +08:00
parent 3bee3f5663
commit 2b63ea1ed4
+171 -5
View File
@@ -209,11 +209,97 @@
.code-block-wrapper .hljs-meta { .code-block-wrapper .hljs-meta {
color: #58a6ff !important; /* 天蓝元数据 */ color: #58a6ff !important; /* 天蓝元数据 */
} }
/* ================= 鼠标响应式交互特效 (Linear / Vercel 风格) ================= */
/* 1. 聚光灯卡片边缘与表面流光跟随 */
.spotlight-card {
position: relative;
overflow: hidden;
transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.25s ease, border-color 0.25s ease;
}
.spotlight-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: radial-gradient(450px circle at var(--mouse-x, -999px) var(--mouse-y, -999px), rgba(14, 165, 233, 0.14), transparent 75%);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
z-index: 1;
}
.dark .spotlight-card::before {
background: radial-gradient(450px circle at var(--mouse-x, -999px) var(--mouse-y, -999px), rgba(56, 189, 248, 0.22), rgba(99, 102, 241, 0.1) 45%, transparent 75%);
}
.spotlight-card:hover::before {
opacity: 1;
}
/* 2. 3D 视差微倾斜容器 */
.tilt-card {
transform-style: preserve-3d;
transition: transform 0.12s ease-out;
will-change: transform;
}
/* 3. 极客光标跟随光圈与准星光点 (仅桌面鼠标端启用) */
.cursor-follower {
position: fixed;
top: 0;
left: 0;
width: 36px;
height: 36px;
border-radius: 9999px;
border: 1.5px solid rgba(56, 189, 248, 0.45);
background: rgba(56, 189, 248, 0.05);
pointer-events: none;
z-index: 9999;
transform: translate(-50%, -50%);
transition: width 0.2s cubic-bezier(0.2, 0.8, 0.2, 1), height 0.2s cubic-bezier(0.2, 0.8, 0.2, 1), border-color 0.2s, background-color 0.2s;
display: none;
backdrop-filter: blur(0.5px);
}
@media (hover: hover) and (pointer: fine) {
.cursor-follower { display: block; }
}
.cursor-follower.hovering {
width: 58px;
height: 58px;
border-color: rgba(99, 102, 241, 0.75);
background: rgba(99, 102, 241, 0.1);
}
.cursor-dot {
position: fixed;
top: 0;
left: 0;
width: 6px;
height: 6px;
border-radius: 9999px;
background: #38bdf8;
pointer-events: none;
z-index: 10000;
transform: translate(-50%, -50%);
box-shadow: 0 0 10px #38bdf8;
display: none;
}
@media (hover: hover) and (pointer: fine) {
.cursor-dot { display: block; }
}
</style> </style>
</head> </head>
<body class="bg-slate-50 dark:bg-slate-950 text-slate-800 dark:text-slate-100 min-h-screen transition-colors duration-200 antialiased font-sans selection:bg-sky-500 selection:text-white"> <body class="bg-slate-50 dark:bg-slate-950 text-slate-800 dark:text-slate-100 min-h-screen transition-colors duration-200 antialiased font-sans selection:bg-sky-500 selection:text-white">
<div id="app" v-cloak class="flex flex-col min-h-screen"> <div id="app" v-cloak class="flex flex-col min-h-screen relative overflow-x-hidden">
<!-- 鼠标跟随全局科技极光光晕 (Ambient Fluid Cursor Follower) -->
<div id="ambient-cursor"
class="fixed pointer-events-none z-0 w-[550px] h-[550px] -translate-x-1/2 -translate-y-1/2 rounded-full blur-[110px] opacity-40 dark:opacity-25 transition-opacity duration-500"
style="background: radial-gradient(circle, rgba(56, 189, 248, 0.35) 0%, rgba(99, 102, 241, 0.22) 40%, transparent 70%);">
</div>
<!-- 极客光标跟随光环与准星光点 (仅桌面鼠标端) -->
<div id="cursor-follower" class="cursor-follower"></div>
<div id="cursor-dot" class="cursor-dot"></div>
<!-- 顶部滚动阅读进度条 (仅阅读长文时显示) --> <!-- 顶部滚动阅读进度条 (仅阅读长文时显示) -->
<div v-show="currentView === 'post'" <div v-show="currentView === 'post'"
@@ -376,8 +462,8 @@
<!-- ======================= 视图 1:主页文章流 (HOME VIEW) ======================= --> <!-- ======================= 视图 1:主页文章流 (HOME VIEW) ======================= -->
<main v-if="currentView === 'home'" class="flex-1 max-w-6xl w-full mx-auto px-4 sm:px-6 py-8"> <main v-if="currentView === 'home'" class="flex-1 max-w-6xl w-full mx-auto px-4 sm:px-6 py-8">
<!-- Hero 博主全栈名片与状态展台 --> <!-- Hero 博主全栈名片与状态展台 (内置 3D 视差与聚光灯) -->
<section class="mb-10 p-6 sm:p-8 rounded-3xl bg-white dark:bg-slate-900 border border-slate-200/80 dark:border-slate-800 shadow-sm relative overflow-hidden"> <section class="tilt-card spotlight-card mb-10 p-6 sm:p-8 rounded-3xl bg-white dark:bg-slate-900 border border-slate-200/80 dark:border-slate-800 shadow-sm relative overflow-hidden">
<!-- 科技感背景光晕 --> <!-- 科技感背景光晕 -->
<div class="absolute -right-20 -top-20 w-72 h-72 bg-gradient-to-br from-sky-400/20 via-indigo-500/15 to-purple-500/10 rounded-full blur-3xl pointer-events-none"></div> <div class="absolute -right-20 -top-20 w-72 h-72 bg-gradient-to-br from-sky-400/20 via-indigo-500/15 to-purple-500/10 rounded-full blur-3xl pointer-events-none"></div>
<div class="absolute -left-20 -bottom-20 w-72 h-72 bg-gradient-to-tr from-emerald-400/15 to-sky-500/10 rounded-full blur-3xl pointer-events-none"></div> <div class="absolute -left-20 -bottom-20 w-72 h-72 bg-gradient-to-tr from-emerald-400/15 to-sky-500/10 rounded-full blur-3xl pointer-events-none"></div>
@@ -480,7 +566,7 @@
<div v-if="posts.length > 0" class="space-y-5"> <div v-if="posts.length > 0" class="space-y-5">
<article v-for="post in posts" :key="post.id" <article v-for="post in posts" :key="post.id"
@click="viewPost(post)" @click="viewPost(post)"
class="group p-6 sm:p-7 rounded-2xl bg-white dark:bg-slate-900 border border-slate-200/80 dark:border-slate-800 shadow-sm hover:shadow-xl hover:border-sky-500/40 dark:hover:border-sky-500/40 transition-all duration-300 cursor-pointer relative overflow-hidden"> class="spotlight-card group p-6 sm:p-7 rounded-3xl bg-white dark:bg-slate-900 border border-slate-200/80 dark:border-slate-800 shadow-sm hover:shadow-xl hover:border-sky-500/40 dark:hover:border-sky-500/40 transition-all duration-300 cursor-pointer relative overflow-hidden">
<!-- 顶部精美标签与时间 --> <!-- 顶部精美标签与时间 -->
<div class="flex flex-wrap items-center justify-between gap-2 mb-3"> <div class="flex flex-wrap items-center justify-between gap-2 mb-3">
@@ -805,7 +891,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div v-for="cat in categoryList" :key="cat.name" <div v-for="cat in categoryList" :key="cat.name"
@click="filterByCategory(cat.name)" @click="filterByCategory(cat.name)"
class="p-6 rounded-3xl bg-white dark:bg-slate-900 border border-slate-200/80 dark:border-slate-800 hover:border-sky-500/50 shadow-sm hover:shadow-lg transition-all cursor-pointer group"> class="spotlight-card p-6 rounded-3xl bg-white dark:bg-slate-900 border border-slate-200/80 dark:border-slate-800 hover:border-sky-500/50 shadow-sm hover:shadow-xl transition-all cursor-pointer group">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<div class="text-3xl p-3 rounded-2xl bg-sky-50 dark:bg-sky-950/60 w-fit"> <div class="text-3xl p-3 rounded-2xl bg-sky-50 dark:bg-sky-950/60 w-fit">
{{ cat.icon }} {{ cat.icon }}
@@ -2091,6 +2177,9 @@
}); });
} }
// 启动 Linear / Vercel 鼠标跟随响应交互系统
initMouseEffects();
window.addEventListener('scroll', handleScroll); window.addEventListener('scroll', handleScroll);
window.addEventListener('hashchange', handleHashRoute); window.addEventListener('hashchange', handleHashRoute);
@@ -2100,6 +2189,83 @@
handleHashRoute(); handleHashRoute();
}); });
// ============ 鼠标响应式交互系统核心驱动 (Linear / Vercel 风格) ============
function initMouseEffects() {
const ambient = document.getElementById('ambient-cursor');
const follower = document.getElementById('cursor-follower');
const dot = document.getElementById('cursor-dot');
let mouseX = window.innerWidth / 2;
let mouseY = window.innerHeight / 2;
let ambX = mouseX, ambY = mouseY;
let folX = mouseX, folY = mouseY;
window.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
if (dot) {
dot.style.left = `${mouseX}px`;
dot.style.top = `${mouseY}px`;
}
// 聚光灯流光跟随计算
const cards = document.querySelectorAll('.spotlight-card');
cards.forEach(card => {
const rect = card.getBoundingClientRect();
if (mouseX >= rect.left - 120 && mouseX <= rect.right + 120 &&
mouseY >= rect.top - 120 && mouseY <= rect.bottom + 120) {
const x = mouseX - rect.left;
const y = mouseY - rect.top;
card.style.setProperty('--mouse-x', `${x}px`);
card.style.setProperty('--mouse-y', `${y}px`);
}
});
// 3D 陀螺仪视差倾斜
const tiltCards = document.querySelectorAll('.tilt-card');
tiltCards.forEach(card => {
const rect = card.getBoundingClientRect();
if (mouseX >= rect.left && mouseX <= rect.right && mouseY >= rect.top && mouseY <= rect.bottom) {
const normX = (mouseX - rect.left) / rect.width - 0.5;
const normY = (mouseY - rect.top) / rect.height - 0.5;
card.style.transform = `perspective(1000px) rotateY(${normX * 6}deg) rotateX(${-normY * 6}deg) translateY(-2px)`;
} else {
card.style.transform = 'perspective(1000px) rotateY(0deg) rotateX(0deg) translateY(0px)';
}
});
});
// 交互悬停扩圈检测
document.addEventListener('mouseover', (e) => {
if (e.target.closest('a, button, input, textarea, select, .spotlight-card, [role="button"]')) {
follower?.classList.add('hovering');
} else {
follower?.classList.remove('hovering');
}
});
// 60FPS 平滑阻尼插值渲染 (Lerp)
function renderLoop() {
ambX += (mouseX - ambX) * 0.05;
ambY += (mouseY - ambY) * 0.05;
if (ambient) {
ambient.style.left = `${ambX}px`;
ambient.style.top = `${ambY}px`;
}
folX += (mouseX - folX) * 0.2;
folY += (mouseY - folY) * 0.2;
if (follower) {
follower.style.left = `${folX}px`;
follower.style.top = `${folY}px`;
}
requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);
}
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('scroll', handleScroll); window.removeEventListener('scroll', handleScroll);
window.removeEventListener('hashchange', handleHashRoute); window.removeEventListener('hashchange', handleHashRoute);