feat(home): 为首页对话按钮添加悬停动画效果
添加GSAP动画效果,当鼠标悬停在对话按钮上时,文字会向上滑动并显示克隆文字,同时背景色块从底部滑入。离开时恢复原始状态。
This commit is contained in:
@@ -18,6 +18,7 @@ const heroRef = ref<HTMLElement | null>(null)
|
||||
const heroPanelRef = ref<HTMLElement | null>(null)
|
||||
const brandNameRef = ref<HTMLElement | null>(null)
|
||||
const studioNameRef = ref<HTMLElement | null>(null)
|
||||
const talkBtnRef = ref<HTMLElement | null>(null)
|
||||
|
||||
let cleanupHeroScroll: (() => void) | null = null
|
||||
|
||||
@@ -112,7 +113,39 @@ const setupHeroScroll = () => {
|
||||
cleanupHeroScroll = () => gsap.ticker.remove(updateHeroPanelScale)
|
||||
}
|
||||
|
||||
const onTalkHover = () => {
|
||||
if (!talkBtnRef.value) return
|
||||
const origs = talkBtnRef.value.querySelectorAll('.btn-char-orig')
|
||||
const clones = talkBtnRef.value.querySelectorAll('.btn-char-clone')
|
||||
const bg = talkBtnRef.value.querySelector('.btn-bg-hover')
|
||||
|
||||
// 背景从底部滑入 (yPercent: 100 -> 0)
|
||||
gsap.to(bg, { yPercent: 0, duration: 0.4, ease: 'power3.out', overwrite: true })
|
||||
|
||||
gsap.to(origs, { yPercent: -100, duration: 0.4, stagger: { amount: 0.2, from: 'start' }, ease: 'power3.out', overwrite: true })
|
||||
gsap.to(clones, { yPercent: 0, duration: 0.4, stagger: { amount: 0.2, from: 'start' }, ease: 'power3.out', overwrite: true })
|
||||
}
|
||||
|
||||
const onTalkLeave = () => {
|
||||
if (!talkBtnRef.value) return
|
||||
const origs = talkBtnRef.value.querySelectorAll('.btn-char-orig')
|
||||
const clones = talkBtnRef.value.querySelectorAll('.btn-char-clone')
|
||||
const bg = talkBtnRef.value.querySelector('.btn-bg-hover')
|
||||
|
||||
// 背景向下反向滑出回收 (yPercent: 0 -> 100)
|
||||
gsap.to(bg, { yPercent: 100, duration: 0.4, ease: 'power3.out', overwrite: true })
|
||||
|
||||
gsap.to(origs, { yPercent: 0, duration: 0.4, stagger: { amount: 0.2, from: 'end' }, ease: 'power3.out', overwrite: true })
|
||||
gsap.to(clones, { yPercent: 100, duration: 0.4, stagger: { amount: 0.2, from: 'end' }, ease: 'power3.out', overwrite: true })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (talkBtnRef.value) {
|
||||
const clones = talkBtnRef.value.querySelectorAll('.btn-char-clone')
|
||||
const bg = talkBtnRef.value.querySelector('.btn-bg-hover')
|
||||
gsap.set(clones, { yPercent: 100 })
|
||||
gsap.set(bg, { yPercent: 100 })
|
||||
}
|
||||
// 组件挂载时,先将整个 Hero 强行置于屏幕下方,等待外部调用 `play()` 才滑入
|
||||
if (heroRef.value) gsap.set(heroRef.value, { yPercent: 100 })
|
||||
// 初始化滚动监听逻辑
|
||||
@@ -178,8 +211,39 @@ defineExpose({ play })
|
||||
{{ t('hero.description') }}
|
||||
</p>
|
||||
<div>
|
||||
<button class="bg-black text-white px-7 py-3.5 rounded-[2rem] font-semibold text-base transition-transform hover:scale-105 shadow-[0_15px_30px_-5px_rgba(0,0,0,0.3)]">
|
||||
{{ t('hero.letsTalk') }}
|
||||
<button
|
||||
ref="talkBtnRef"
|
||||
@mouseenter="onTalkHover"
|
||||
@mouseleave="onTalkLeave"
|
||||
class="relative overflow-hidden bg-black text-white px-7 py-3.5 rounded-[2rem] font-semibold text-[1rem] shadow-[0_15px_30px_-5px_rgba(0,0,0,0.3)] flex items-center justify-center group"
|
||||
>
|
||||
<!-- 默认黑色背景层,防止文字动画时漏底 -->
|
||||
<div class="absolute inset-0 bg-black rounded-[2rem]"></div>
|
||||
|
||||
<!-- Hover 时的灰色背景色块层 -->
|
||||
<div
|
||||
class="btn-bg-hover absolute inset-0 bg-[#333333] rounded-[2rem] will-change-transform"
|
||||
></div>
|
||||
|
||||
<!-- 文本层 -->
|
||||
<span class="relative inline-flex overflow-hidden z-10">
|
||||
<span class="inline-flex">
|
||||
<span
|
||||
v-for="(char, index) in t('hero.letsTalk').split('')"
|
||||
:key="`orig-${index}`"
|
||||
class="btn-char-orig block will-change-transform"
|
||||
v-html="char === ' ' ? ' ' : char"
|
||||
></span>
|
||||
</span>
|
||||
<span class="absolute inset-0 inline-flex" aria-hidden="true">
|
||||
<span
|
||||
v-for="(char, index) in t('hero.letsTalk').split('')"
|
||||
:key="`clone-${index}`"
|
||||
class="btn-char-clone block will-change-transform"
|
||||
v-html="char === ' ' ? ' ' : char"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user