style:完善主页动画样式

This commit is contained in:
2026-07-15 18:32:19 +08:00
parent 4e1bf77cb2
commit cb9928936b
3 changed files with 464 additions and 80 deletions
+209 -28
View File
@@ -130,14 +130,17 @@ a:focus-visible {
position: relative;
height: min(32.2vw, 617px);
min-height: 430px;
background: #f4f8ff;
background: linear-gradient(90deg, #f7faff 0%, #e6eefc 100%);
isolation: isolate;
}
.hero-art {
position: absolute;
z-index: -1;
inset: 0;
background: url('/images/lanhu/slices/home-container25-image2@2x.png') center/cover no-repeat;
background-image: url('/images/lanhu/slices/hero-geo-visual@2x.png');
background-repeat: no-repeat;
background-position: right center;
background-size: auto 100%;
}
.hero-content {
height: 100%;
@@ -272,6 +275,7 @@ a:focus-visible {
text-align: center;
}
.section-title h2 {
position: relative;
margin: 0;
color: #414141;
font-weight: 700;
@@ -334,6 +338,7 @@ a:focus-visible {
color: #0875f9;
font-size: 34px;
line-height: 1;
font-variant-numeric: tabular-nums;
}
.stat-item span {
color: #555;
@@ -342,40 +347,145 @@ a:focus-visible {
}
.model-section {
position: relative;
min-height: 791px;
padding: 76px 0 80px;
overflow: hidden;
background: linear-gradient(135deg, #f8fbff 5%, #fff 44%, #eff4ff 100%);
}
.model-section::before {
position: absolute;
right: -8%;
bottom: -42%;
left: -8%;
height: 72%;
content: '';
pointer-events: none;
background:
radial-gradient(circle at 20% 64%, rgba(245, 195, 204, 0.4), transparent 31%),
radial-gradient(circle at 78% 66%, rgba(120, 224, 255, 0.45), transparent 35%);
filter: blur(22px);
}
.model-section > .page-width {
position: relative;
}
.model-layout {
display: grid;
grid-template-columns: 0.95fr 0.95fr 1.2fr;
grid-template-columns: minmax(280px, 370px) minmax(280px, 370px) minmax(310px, 1fr);
align-items: center;
gap: 25px;
margin-top: 52px;
justify-content: center;
gap: 20px;
margin-top: 48px;
}
.ring {
width: 100%;
max-width: 370px;
justify-self: center;
border: 0;
outline: 0;
box-shadow: none;
background: transparent;
}
.ring-diagram {
position: relative;
aspect-ratio: 1;
isolation: isolate;
}
.ring-diagram:focus,
.ring-diagram:focus-visible {
border: 0;
outline: 0;
box-shadow: none;
}
.ring-base {
width: 100%;
height: 100%;
display: block;
object-fit: contain;
border: 0;
outline: 0;
box-shadow: none;
background: transparent;
}
.ring-label {
position: absolute;
z-index: 2;
color: #4b83e8;
font-size: 12px;
font-weight: 600;
line-height: 1;
letter-spacing: 0.04em;
white-space: nowrap;
text-shadow: 0 1px 8px rgba(255, 255, 255, 0.95);
}
.ring-label-1 {
top: 27%;
left: 20%;
transform: rotate(-39deg);
}
.ring-label-2 {
top: 27%;
right: 18%;
transform: rotate(39deg);
}
.ring-label-3 {
right: 17%;
bottom: 25%;
transform: rotate(-39deg);
}
.ring-label-4 {
bottom: 25%;
left: 18%;
transform: rotate(39deg);
}
.ring-center-label {
position: absolute;
z-index: 2;
top: 50%;
left: 50%;
width: 36%;
display: grid;
justify-items: center;
gap: 8px;
color: #fff;
transform: translate(-50%, -45%);
text-align: center;
text-shadow: 0 2px 7px rgba(49, 83, 205, 0.26);
}
.ring-center-label svg {
width: 42px;
height: 42px;
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2.2;
filter: drop-shadow(0 4px 7px rgba(27, 79, 196, 0.22));
}
.ring-center-label strong {
font-size: 16px;
font-weight: 600;
letter-spacing: 0.08em;
white-space: nowrap;
}
.core-value {
padding: 12px 0 0 10px;
padding: 6px 0 0 16px;
}
.core-value h3 {
display: flex;
align-items: center;
gap: 13px;
margin: 0 0 17px;
margin: 0 0 12px;
color: #1578fa;
font-size: 21px;
}
.core-value h3 img {
width: 50px;
height: 50px;
width: 46px;
height: 46px;
object-fit: contain;
}
.core-point {
padding: 15px 0 17px;
padding: 13px 0 14px;
border-bottom: 1px dashed #d3d3d3;
}
.core-point h4 {
@@ -393,13 +503,14 @@ a:focus-visible {
background: linear-gradient(135deg, #a0e1ff, #3c67ff);
}
.core-point p {
margin: 10px 0 0;
max-width: 35em;
margin: 8px 0 0;
color: #757575;
font-size: 16px;
line-height: 1.7;
font-size: 14px;
line-height: 1.65;
}
.core-value .primary-button {
margin-top: 34px;
margin-top: 25px;
}
.advantages-section {
@@ -868,6 +979,72 @@ a:focus-visible {
}
}
@media (min-width: 761px) {
.site-header {
transition:
background 0.3s ease,
box-shadow 0.3s ease,
backdrop-filter 0.3s ease;
}
.site-header.scrolled {
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 10px 30px rgba(68, 122, 196, 0.1);
backdrop-filter: blur(18px) saturate(1.16);
}
.hero-copy {
will-change: transform;
}
.section-title h2::after {
position: absolute;
right: 42%;
bottom: -13px;
left: 42%;
height: 2px;
content: '';
border-radius: 2px;
background: linear-gradient(90deg, transparent, rgba(20, 120, 251, 0.78), transparent);
opacity: 0;
transform: scaleX(0);
transition:
transform 0.75s cubic-bezier(0.22, 1, 0.36, 1),
opacity 0.45s ease;
}
.section-title.is-visible h2::after {
opacity: 1;
transform: scaleX(1);
}
.motion-card {
--spot-x: 50%;
--spot-y: 50%;
--spot-opacity: 0;
position: relative;
isolation: isolate;
}
.motion-card::after {
position: absolute;
z-index: 3;
inset: 0;
content: '';
pointer-events: none;
border-radius: inherit;
background: radial-gradient(
280px circle at var(--spot-x) var(--spot-y),
rgba(71, 145, 255, 0.16),
transparent 58%
);
box-shadow: inset 0 0 0 1px rgba(113, 169, 255, 0.24);
opacity: var(--spot-opacity);
}
.motion-card img {
will-change: transform;
}
.case-background,
.delivery-banner,
.testimonial-track {
will-change: transform;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
@@ -1017,21 +1194,10 @@ a:focus-visible {
height: min(720px, calc(100svh - 55px));
min-height: 570px;
}
.hero-section::after {
position: absolute;
z-index: -1;
inset: 0;
content: '';
background: linear-gradient(
90deg,
rgba(246, 250, 255, 0.98) 0%,
rgba(246, 250, 255, 0.9) 46%,
rgba(246, 250, 255, 0.15) 100%
);
}
.hero-art {
background-position: 66% center;
opacity: 0.86;
background-position: right bottom;
background-size: auto 74%;
opacity: 0.72;
will-change: transform;
}
.hero-content {
@@ -1141,6 +1307,21 @@ a:focus-visible {
max-width: 220px;
will-change: transform;
}
.ring-label {
font-size: clamp(7px, 2.15vw, 9px);
letter-spacing: 0;
}
.ring-center-label {
gap: 4px;
}
.ring-center-label svg {
width: clamp(24px, 7vw, 32px);
height: clamp(24px, 7vw, 32px);
}
.ring-center-label strong {
font-size: clamp(9px, 2.7vw, 12px);
letter-spacing: 0.02em;
}
.core-value {
grid-column: 1 / -1;
padding: 26px 0 0;
+255 -52
View File
@@ -4,9 +4,12 @@ import { ScrollTrigger } from 'gsap/ScrollTrigger'
const consultationOpen = ref(false)
const mobileMenuOpen = ref(false)
const activeSection = ref('home')
const headerScrolled = ref(false)
const notice = ref('')
const pageRoot = ref(null)
let animationContext
let animationMedia
const navItems = [
['首页', 'home'],
@@ -39,8 +42,22 @@ const stats = [
['65%', '客户转化链路效率平均提升', '/images/lanhu/slices/home-group103-image29@2x.png']
]
const corePoints = [
['曝光', '打造AI搜索语境下的品牌信任声量,一次布局 长期复用'],
['获客', '流量,让每一分投入都有回报 让曝光直接落地为可跟进的精准线索,告别无效']
['曝光', '打造AI搜索语境下的品牌信任声量,一次布局长期复用'],
['获客', '让曝光直接沉淀为可跟进的精准线索,告别无效流量']
]
const ringModels = [
{
title: '声量资产环',
image: '/images/lanhu/slices/home-group80-group83-image11@2x.png',
icon: 'asset',
labels: ['真人内容生产', '全域媒体分发', 'AI信任构建', '品牌资产沉淀']
},
{
title: '获客转化环',
image: '/images/lanhu/slices/home-group86-group87-image12@2x.png',
icon: 'conversion',
labels: ['AI搜索曝光', '精准流量承接', '全链路转化', '数据复盘优化']
}
]
const advantageCards = [
['全域媒体资源优势', '5000+权威官媒+10万+优质自媒体矩阵'],
@@ -177,60 +194,228 @@ onMounted(async () => {
reveal('.testimonial-track article', '.testimonial-track', { y: 22, stagger: 0.1 })
reveal('.cta-content', '.cta-section', { y: 26 })
ScrollTrigger.matchMedia({
'(max-width: 760px)': () => {
gsap.to('.logo-track', {
xPercent: -50,
duration: 24,
ease: 'none',
repeat: -1
})
animationMedia = gsap.matchMedia()
gsap.to('.ring:first-child', {
x: -12,
rotation: -2,
ease: 'none',
scrollTrigger: {
trigger: '.model-layout',
start: 'top bottom',
end: 'bottom top',
scrub: 0.8
animationMedia.add('(max-width: 760px)', () => {
gsap.to('.logo-track', {
xPercent: -50,
duration: 24,
ease: 'none',
repeat: -1
})
gsap.to('.ring:first-child', {
x: -12,
rotation: -2,
ease: 'none',
scrollTrigger: {
trigger: '.model-layout',
start: 'top bottom',
end: 'bottom top',
scrub: 0.8
}
})
gsap.to('.ring:nth-child(2)', {
x: 12,
rotation: 2,
ease: 'none',
scrollTrigger: {
trigger: '.model-layout',
start: 'top bottom',
end: 'bottom top',
scrub: 0.8
}
})
gsap.to('.cta-art', {
yPercent: -6,
ease: 'none',
scrollTrigger: {
trigger: '.cta-section',
start: 'top bottom',
end: 'bottom top',
scrub: 0.8
}
})
})
animationMedia.add('(min-width: 761px)', () => {
const cleanup = []
const addListener = (element, event, handler) => {
element.addEventListener(event, handler)
cleanup.push(() => element.removeEventListener(event, handler))
}
ScrollTrigger.create({
start: 80,
end: 'max',
onToggle: ({ isActive }) => {
headerScrolled.value = isActive
}
})
navItems.forEach(([, target]) => {
const trigger = target === 'home' ? '.hero-section' : `#${target}`
if (!document.querySelector(trigger)) return
ScrollTrigger.create({
trigger,
start: 'top 48%',
end: 'bottom 48%',
onEnter: () => {
activeSection.value = target
},
onEnterBack: () => {
activeSection.value = target
}
})
})
gsap.to('.ring:nth-child(2)', {
x: 12,
rotation: 2,
ease: 'none',
scrollTrigger: {
trigger: '.model-layout',
start: 'top bottom',
end: 'bottom top',
scrub: 0.8
}
const hero = document.querySelector('.hero-section')
const heroCopy = document.querySelector('.hero-copy')
if (hero && heroCopy) {
const moveX = gsap.quickTo(heroCopy, 'x', { duration: 0.7, ease: 'power3.out' })
const moveY = gsap.quickTo(heroCopy, 'y', { duration: 0.7, ease: 'power3.out' })
addListener(hero, 'pointermove', (event) => {
const bounds = hero.getBoundingClientRect()
moveX(((event.clientX - bounds.left) / bounds.width - 0.5) * 16)
moveY(((event.clientY - bounds.top) / bounds.height - 0.5) * 10)
})
gsap.to('.cta-art', {
yPercent: -6,
ease: 'none',
scrollTrigger: {
trigger: '.cta-section',
start: 'top bottom',
end: 'bottom top',
scrub: 0.8
}
addListener(hero, 'pointerleave', () => {
moveX(0)
moveY(0)
})
}
document.querySelectorAll('.stat-item strong').forEach((element) => {
const finalText = element.textContent.trim()
const finalValue = Number.parseFloat(finalText)
const suffix = finalText.replace(/[\d.]/g, '')
const counter = { value: 0 }
ScrollTrigger.create({
trigger: element,
start: 'top 88%',
once: true,
onEnter: () => {
gsap.to(counter, {
value: finalValue,
duration: 1.45,
ease: 'power2.out',
onUpdate: () => {
element.textContent = `${Math.round(counter.value)}${suffix}`
}
})
}
})
})
document.querySelectorAll('.section-title').forEach((title) => {
ScrollTrigger.create({
trigger: title,
start: 'top 84%',
once: true,
onEnter: () => title.classList.add('is-visible')
})
})
const interactiveCards = document.querySelectorAll(
'.stat-item, .advantage-grid article, .case-panel article, .industry-grid article, .delivery-grid article'
)
interactiveCards.forEach((card) => {
card.classList.add('motion-card')
const image = card.querySelector('img')
const imageX = image
? gsap.quickTo(image, 'x', { duration: 0.45, ease: 'power3.out' })
: null
const imageY = image
? gsap.quickTo(image, 'y', { duration: 0.45, ease: 'power3.out' })
: null
addListener(card, 'pointermove', (event) => {
const bounds = card.getBoundingClientRect()
const x = event.clientX - bounds.left
const y = event.clientY - bounds.top
card.style.setProperty('--spot-x', `${x}px`)
card.style.setProperty('--spot-y', `${y}px`)
gsap.to(card, { '--spot-opacity': 1, duration: 0.2 })
imageX?.((x / bounds.width - 0.5) * 9)
imageY?.((y / bounds.height - 0.5) * 9)
})
addListener(card, 'pointerleave', () => {
gsap.to(card, { '--spot-opacity': 0, duration: 0.35 })
imageX?.(0)
imageY?.(0)
})
cleanup.push(() => card.classList.remove('motion-card'))
})
gsap.to('.model-layout .ring:first-child', {
x: -34,
rotation: -4,
ease: 'none',
scrollTrigger: {
trigger: '.model-layout',
start: 'top bottom',
end: 'bottom top',
scrub: 1
}
})
gsap.to('.model-layout .ring:nth-child(2)', {
x: 34,
rotation: 4,
ease: 'none',
scrollTrigger: {
trigger: '.model-layout',
start: 'top bottom',
end: 'bottom top',
scrub: 1
}
})
gsap.to('.case-background', {
xPercent: -4,
scale: 1.08,
ease: 'none',
scrollTrigger: {
trigger: '.cases-section',
start: 'top bottom',
end: 'bottom top',
scrub: 1
}
})
gsap.to('.delivery-banner', {
backgroundPosition: '50% 62%',
ease: 'none',
scrollTrigger: {
trigger: '.delivery-section',
start: 'top bottom',
end: 'bottom top',
scrub: 1
}
})
gsap.to('.testimonial-track', {
x: -720,
ease: 'none',
scrollTrigger: {
trigger: '.testimonial-section',
start: 'top bottom',
end: 'bottom top',
scrub: 1
}
})
return () => cleanup.forEach((dispose) => dispose())
})
}, pageRoot.value)
})
onBeforeUnmount(() => animationContext?.revert())
onBeforeUnmount(() => {
animationMedia?.revert()
animationContext?.revert()
})
</script>
<template>
<main id="home" ref="pageRoot" class="site-shell">
<header class="site-header">
<header class="site-header" :class="{ scrolled: headerScrolled }">
<div class="page-width header-inner">
<a href="#home" class="brand" aria-label="大马棒首页"
><img
@@ -241,7 +426,7 @@ onBeforeUnmount(() => animationContext?.revert())
<button
v-for="([label, target], index) in navItems"
:key="target"
:class="{ active: index === 0 }"
:class="{ active: target === activeSection }"
@click="scrollToSection(target)"
>
{{ label }}<span v-if="index > 0 && index !== 3" class="nav-chevron"></span>
@@ -340,16 +525,34 @@ onBeforeUnmount(() => animationContext?.revert())
></SectionTitle
>
<div class="model-layout">
<img
class="ring"
src="/images/lanhu/slices/home-group80-group83-image11@2x.png"
alt="声量资产环"
/>
<img
class="ring"
src="/images/lanhu/slices/home-group86-group87-image12@2x.png"
alt="获客转化环"
/>
<div
v-for="model in ringModels"
:key="model.title"
class="ring ring-diagram"
role="img"
:aria-label="model.title"
>
<img class="ring-base" :src="model.image" alt="" />
<span
v-for="(label, labelIndex) in model.labels"
:key="label"
:class="`ring-label ring-label-${labelIndex + 1}`"
>{{ label }}</span
>
<div class="ring-center-label">
<svg v-if="model.icon === 'asset'" viewBox="0 0 48 48" aria-hidden="true">
<path d="M10 38V16l14-7 14 7v22H10Z" />
<path d="M17 21h4m6 0h4m-14 7h4m6 0h4M22 38v-6h4v6" />
</svg>
<svg v-else viewBox="0 0 48 48" aria-hidden="true">
<circle cx="15" cy="17" r="6" />
<circle cx="34" cy="14" r="5" />
<circle cx="31" cy="34" r="6" />
<path d="m20 18 9-3m2 4v9m-5 4-7-10" />
</svg>
<strong>{{ model.title }}</strong>
</div>
</div>
<div class="core-value">
<h3>
<img src="/images/lanhu/slices/home-group88-image13@2x.png" alt="" />业务核心价值点
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB