Skip to content

Commit 73eec1a

Browse files
committed
feat(ChronologyOfStellarTrails): 重构详情页动画并添加交互效果
- 使用 GSAP 替换原有的 CSS 动画,实现更平滑的旋转和切换效果 - 添加鼠标悬停和点击按钮时的动画效果,提升用户体验 - 新增波纹效果和发光动画,增强视觉吸引力 - 优化页面加载时的动画顺序,提升整体流畅度
1 parent cc2961c commit 73eec1a

4 files changed

Lines changed: 328 additions & 96 deletions

File tree

pages/ChronologyOfStellarTrailsDetails.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ <h3>降落时间:2005.10.17 16:00</h3>
9494
</div>
9595
</div>
9696
<script src="../build/gsap.js"></script>
97+
<script src="../build/scrolltrigger.js"></script>
9798
<script src="../build/three_r145.js"></script>
9899
<script src="../build/three_objloader.js"></script>
99100
<script src="../scripts/common/StarBackground.js"></script>

scripts/ChronologyOfStellarTrails/detail.js

Lines changed: 222 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,13 @@ function initOrbitAnimation() {
396396
const speed = baseSpeed + (index * 5);
397397
const direction = index % 2 === 0 ? 1 : -1; // 1顺时针,-1逆时针
398398

399-
// 设置轨道圆的动画
400-
circle.style.animation = `orbit ${speed}s linear ${direction === 1 ? '' : 'reverse'} infinite`;
399+
// 使用GSAP创建更平滑的旋转动画
400+
gsap.to(circle, {
401+
rotation: 360 * direction,
402+
duration: speed,
403+
repeat: -1,
404+
ease: "none"
405+
});
401406

402407
// 在每个轨道上添加点
403408
const dotCount = index === 0 ? 0 : 3 + index; // 第一个轨道没有点,其他轨道点数递增
@@ -483,62 +488,217 @@ function updateMissionContent(index, direction) { // index: 目标任务的索
483488
`;
484489
// 更新任务描述
485490
missionDetailContent.textContent = mission.description;
486-
487-
// 应用滑入动画
488-
let slideInAnimation = '';
489-
if (direction === 'next') {
490-
slideInAnimation = `slide-in-up-rotate ${animationDuration / 1000}s ease-in-out forwards`; // 新内容从底部向上滑入并旋转
491-
} else if (direction === 'prev') {
492-
slideInAnimation = `slide-in-down-rotate ${animationDuration / 1000}s ease-in-out forwards`; // 新内容从顶部向下滑入并旋转
493-
} else { // initial 初始加载
494-
slideInAnimation = `slide-in-up-rotate ${animationDuration / 1000}s ease-in-out forwards`; // 默认初始动画 (从底部向上滑入并旋转)
495-
}
496-
infoContainer.style.animation = 'none'; // 清除之前的动画状态,确保新动画能够触发
497-
requestAnimationFrame(() => { // 确保样式刷新后再应用新动画
498-
infoContainer.style.animation = slideInAnimation;
499-
});
500-
501-
502-
setTimeout(() => {
503-
isAnimating = false; // 动画结束后,重置动画标志
504-
}, animationDuration);
505491
};
506492

507493
if (direction === 'initial') { // 如果是初始加载
508-
infoContainer.style.opacity = '0'; // 初始时设置为透明,以配合滑入动画
509494
performUpdate();
510-
requestAnimationFrame(() => { // 确保 opacity:0 已应用,再开始动画(设为1,动画本身会处理opacity)
511-
infoContainer.style.opacity = '1';
512-
});
495+
// 使用GSAP动画显示初始内容
496+
gsap.fromTo(infoContainer,
497+
{ opacity: 0, y: 50 },
498+
{ opacity: 1, y: 0, duration: 0.8, ease: "power2.out" }
499+
);
500+
setTimeout(() => {
501+
isAnimating = false; // 动画结束后,重置动画标志
502+
}, 800);
513503
} else {
514-
let slideOutAnimation = '';
504+
// 使用GSAP Timeline创建更流畅的切换动画
505+
const timeline = gsap.timeline({
506+
onComplete: () => {
507+
isAnimating = false; // 动画结束后,重置动画标志
508+
}
509+
});
510+
511+
// 根据方向设置滑出动画
515512
if (direction === 'next') {
516-
slideOutAnimation = `slide-out-up-rotate ${animationDuration / 1000}s ease-in-out forwards`; // 当前内容向上滑出并旋转
517-
} else if (direction === 'prev') {
518-
slideOutAnimation = `slide-out-down-rotate ${animationDuration / 1000}s ease-in-out forwards`; // 当前内容向下滑出并旋转
513+
// 向上滑出
514+
timeline.to(infoContainer, {
515+
y: -window.innerHeight,
516+
opacity: 0,
517+
duration: 0.5,
518+
ease: "power2.in"
519+
});
520+
} else {
521+
// 向下滑出
522+
timeline.to(infoContainer, {
523+
y: window.innerHeight,
524+
opacity: 0,
525+
duration: 0.5,
526+
ease: "power2.in"
527+
});
519528
}
520-
infoContainer.style.animation = 'none';
521-
requestAnimationFrame(() => {
522-
infoContainer.style.animation = slideOutAnimation;
523-
});
524529

525-
setTimeout(performUpdate, animationDuration); // 滑出动画结束后执行内容更新和滑入动画
530+
// 在动画中间点更新内容
531+
timeline.add(() => {
532+
performUpdate();
533+
// 重置容器位置和透明度以准备滑入动画
534+
gsap.set(infoContainer, { y: direction === 'next' ? window.innerHeight : -window.innerHeight, opacity: 0 });
535+
}, 0.25);
536+
537+
// 根据方向设置滑入动画
538+
if (direction === 'next') {
539+
// 从下方滑入
540+
timeline.fromTo(infoContainer,
541+
{ y: window.innerHeight, opacity: 0 },
542+
{ y: 0, opacity: 1, duration: 0.5, ease: "power2.out" },
543+
0.3
544+
);
545+
} else {
546+
// 从上方滑入
547+
timeline.fromTo(infoContainer,
548+
{ y: -window.innerHeight, opacity: 0 },
549+
{ y: 0, opacity: 1, duration: 0.5, ease: "power2.out" },
550+
0.3
551+
);
552+
}
526553
}
527554
}
528555

529556
// 处理导航按钮点击
530557
function handleNavigation() {
531-
lastCircle.addEventListener("click", function () {
558+
// 创建波纹效果的函数
559+
function createRipple(event, button) {
560+
const circle = document.createElement("span");
561+
circle.classList.add("ripple");
562+
const diameter = Math.max(button.clientWidth, button.clientHeight);
563+
const radius = diameter / 2;
564+
circle.style.width = circle.style.height = `${diameter}px`;
565+
circle.style.left = `${event.clientX - button.getBoundingClientRect().left - radius}px`;
566+
circle.style.top = `${event.clientY - button.getBoundingClientRect().top - radius}px`;
567+
button.appendChild(circle);
568+
569+
// 清理波纹元素
570+
setTimeout(() => {
571+
circle.remove();
572+
}, 600);
573+
}
574+
575+
lastCircle.addEventListener("mouseenter", function () {
576+
// 悬停进入动画
577+
gsap.to(lastCircle, {
578+
scale: 1.1,
579+
boxShadow: "inset 0px 0px 3px 2px rgba(128, 48, 150), 0 0 0 8px rgba(152, 117, 161, 0.4)",
580+
duration: 0.3,
581+
ease: "power2.out"
582+
});
583+
584+
// SVG图标颜色变化
585+
gsap.to(lastCircle.querySelector('svg path'), {
586+
fill: "#a0d8ff",
587+
duration: 0.3,
588+
ease: "power2.out"
589+
});
590+
});
591+
592+
lastCircle.addEventListener("mouseleave", function () {
593+
// 悬停离开动画
594+
gsap.to(lastCircle, {
595+
scale: 1,
596+
boxShadow: "inset 0px 0px 3px 2px rgba(128, 48, 150), 0 0 0 0 rgba(152, 117, 161, 0.4)",
597+
duration: 0.3,
598+
ease: "power2.out"
599+
});
600+
601+
// SVG图标颜色恢复
602+
gsap.to(lastCircle.querySelector('svg path'), {
603+
fill: "#ffffff",
604+
duration: 0.3,
605+
ease: "power2.out"
606+
});
607+
});
608+
609+
nextCircle.addEventListener("mouseenter", function () {
610+
// 悬停进入动画
611+
gsap.to(nextCircle, {
612+
scale: 1.1,
613+
boxShadow: "inset 0px 0px 3px 2px rgba(128, 48, 150), 0 0 0 8px rgba(152, 117, 161, 0.4)",
614+
duration: 0.3,
615+
ease: "power2.out"
616+
});
617+
618+
// SVG图标颜色变化
619+
gsap.to(nextCircle.querySelector('svg path'), {
620+
fill: "#a0d8ff",
621+
duration: 0.3,
622+
ease: "power2.out"
623+
});
624+
});
625+
626+
nextCircle.addEventListener("mouseleave", function () {
627+
// 悬停离开动画
628+
gsap.to(nextCircle, {
629+
scale: 1,
630+
boxShadow: "inset 0px 0px 3px 2px rgba(128, 48, 150), 0 0 0 0 rgba(152, 117, 161, 0.4)",
631+
duration: 0.3,
632+
ease: "power2.out"
633+
});
634+
635+
// SVG图标颜色恢复
636+
gsap.to(nextCircle.querySelector('svg path'), {
637+
fill: "#ffffff",
638+
duration: 0.3,
639+
ease: "power2.out"
640+
});
641+
});
642+
643+
lastCircle.addEventListener("click", function (event) {
532644
if (isAnimating) return;
645+
646+
// 创建波纹效果
647+
createRipple(event, lastCircle);
648+
649+
// 按钮点击动画
650+
const tl = gsap.timeline();
651+
tl.to(lastCircle, {
652+
scale: 0.9,
653+
duration: 0.1,
654+
ease: "power2.out"
655+
})
656+
.to(lastCircle, {
657+
scale: 1,
658+
duration: 0.2,
659+
ease: "back.out(1.7)"
660+
});
661+
662+
// SVG图标旋转动画
663+
gsap.to(lastCircle.querySelector('svg'), {
664+
rotation: 450,
665+
duration: 0.5,
666+
ease: "back.out(1.7)"
667+
});
668+
533669
currentMissionIndex--;
534670
if (currentMissionIndex < 0) {
535671
currentMissionIndex = missionData.length - 1; // 循环到最后一个
536672
}
537673
updateMissionContent(currentMissionIndex, 'prev');
538674
});
539675

540-
nextCircle.addEventListener("click", function () {
676+
nextCircle.addEventListener("click", function (event) {
541677
if (isAnimating) return;
678+
679+
// 创建波纹效果
680+
createRipple(event, nextCircle);
681+
682+
// 按钮点击动画
683+
const tl = gsap.timeline();
684+
tl.to(nextCircle, {
685+
scale: 0.9,
686+
duration: 0.1,
687+
ease: "power2.out"
688+
})
689+
.to(nextCircle, {
690+
scale: 1,
691+
duration: 0.2,
692+
ease: "back.out(1.7)"
693+
});
694+
695+
// SVG图标旋转动画
696+
gsap.to(nextCircle.querySelector('svg'), {
697+
rotation: -450,
698+
duration: 0.5,
699+
ease: "back.out(1.7)"
700+
});
701+
542702
currentMissionIndex++;
543703
if (currentMissionIndex >= missionData.length) {
544704
currentMissionIndex = 0; // 循环到第一个
@@ -600,12 +760,38 @@ window.addEventListener('resize', () => {
600760

601761
// 初始化页面
602762
document.addEventListener("DOMContentLoaded", function () {
763+
// 初始化GSAP ScrollTrigger
764+
gsap.registerPlugin(ScrollTrigger);
765+
603766
initCurrentMissionIndex(); // 根据localStorage初始化任务索引
604767
StartBackground(); // 初始化星空背景和流星效果
605768
initOrbitAnimation(); // 初始化轨道动画
606769
updateMissionContent(currentMissionIndex, 'initial'); // 初始加载对应的任务数据
607770
handleNavigation(); // 设置导航按钮的点击事件监听
608771
handleBackButton(); // 设置返回按钮的点击事件监听
772+
773+
// 添加按钮入场动画
774+
gsap.from([lastCircle, nextCircle], {
775+
duration: 0.8,
776+
opacity: 0,
777+
y: 50,
778+
stagger: 0.2,
779+
ease: "back.out(1.7)",
780+
onComplete: function() {
781+
// 添加按钮发光效果以吸引注意
782+
setTimeout(() => {
783+
lastCircle.classList.add('glow');
784+
nextCircle.classList.add('glow');
785+
786+
// 3秒后移除发光效果
787+
setTimeout(() => {
788+
lastCircle.classList.remove('glow');
789+
nextCircle.classList.remove('glow');
790+
}, 3000);
791+
}, 500);
792+
}
793+
});
794+
609795
// 初始化鼠标控制器
610796
new Mouse({
611797
defaultCursor: '../assets/images/common/MouseDefault.svg',

0 commit comments

Comments
 (0)