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 heroPanelRef = ref<HTMLElement | null>(null)
|
||||||
const brandNameRef = ref<HTMLElement | null>(null)
|
const brandNameRef = ref<HTMLElement | null>(null)
|
||||||
const studioNameRef = ref<HTMLElement | null>(null)
|
const studioNameRef = ref<HTMLElement | null>(null)
|
||||||
|
const talkBtnRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
let cleanupHeroScroll: (() => void) | null = null
|
let cleanupHeroScroll: (() => void) | null = null
|
||||||
|
|
||||||
@@ -112,7 +113,39 @@ const setupHeroScroll = () => {
|
|||||||
cleanupHeroScroll = () => gsap.ticker.remove(updateHeroPanelScale)
|
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(() => {
|
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()` 才滑入
|
// 组件挂载时,先将整个 Hero 强行置于屏幕下方,等待外部调用 `play()` 才滑入
|
||||||
if (heroRef.value) gsap.set(heroRef.value, { yPercent: 100 })
|
if (heroRef.value) gsap.set(heroRef.value, { yPercent: 100 })
|
||||||
// 初始化滚动监听逻辑
|
// 初始化滚动监听逻辑
|
||||||
@@ -178,8 +211,39 @@ defineExpose({ play })
|
|||||||
{{ t('hero.description') }}
|
{{ t('hero.description') }}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<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)]">
|
<button
|
||||||
{{ t('hero.letsTalk') }}
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user