This commit is contained in:
谢思 2026-03-25 17:04:15 +08:00
commit b1635ba336
7 changed files with 214 additions and 61 deletions

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState, useRef } from 'react';
import './index.scss';
import featureLeft1 from '../../../images/factory/feature-left-1.webp';
import featureLeft1 from '../../../images/factory/feature-left-1.png';
import featureLeft2 from '../../../images/factory/feature-left-2.jpg';
import featureright1 from '../../../images/factory/feature-right-1.webp';
import featureright2 from '../../../images/factory/feature-right-2.jpg';
@ -53,6 +53,7 @@ function Feature(props) {
const getStandard = () => {
getSourceList({ id: factoryZoneId, keywords: standardId }).then(res => {
const rows = []
if (!res || !res.data || !res.data.rows) return
res.data.rows.forEach(e => {
let extendData = {}
if (e.extendData) {

View File

@ -104,7 +104,7 @@
background-image: url('../../../images/factory/feature-left-bg2.webp');
img {
width: 620px;
margin-top: 32px;
// margin-top: 32px;
}
}

View File

@ -1,7 +1,8 @@
import React, { useEffect, useRef, useState, forwardRef } from 'react';
import React, { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
import './index.scss';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { ScrollToPlugin } from 'gsap/ScrollToPlugin';
import KFIcon from '../../../components/KFIcon';
import { Link } from 'react-router-dom';
import { getProjectsLists, getSourceList, httpUrl } from '../../api';
@ -10,7 +11,7 @@ import { downloadFunc } from '../../../forge/Utils/utils'
import '../../common.scss'
import { Modal } from 'antd';
gsap.registerPlugin(ScrollTrigger);
gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);
const resourceInfo = [
{ name: '项目', title: '工厂开源项目', icon: 'icon-xiangmu', desc: '基于标准化生产线开发的项目源码、依赖包源码、标准化生产线源码等', path: '/sf/resources/project' },
@ -26,6 +27,8 @@ const Resource = forwardRef(({ zoneId }, ref) => {
const r4Ref = useRef(null);
const containerRef = useRef(null);
const timelineRef = useRef(null);
const scrollTriggerRef = useRef(null);
const wrapperRef = useRef(null); // IntersectionObserver ref
const [isVisible, setIsVisible] = useState(false);
const [resourceList1, setResourceList1] = useState([]);
@ -34,6 +37,14 @@ const Resource = forwardRef(({ zoneId }, ref) => {
const [resourceList4, setResourceList4] = useState([]);
const [manualList, setManualList] = useState([]);
//
const scrollOffsetConfig = useRef({
0: 0, // resource-1
1: 0, // resource-2
2: 0, // resource-3
3: 300, // resource-4
});
useEffect(() => {
if (!zoneId) return;
getToolsList();
@ -55,61 +66,152 @@ const Resource = forwardRef(({ zoneId }, ref) => {
}
}, { threshold: 0.1 });
if (ref.current) {
observer.observe(ref.current);
if (wrapperRef.current) {
observer.observe(wrapperRef.current);
}
return () => observer.disconnect();
}, []);
// GSAP
useEffect(() => {
if (!containerRef.current) return;
//
const panels = [r1Ref.current, r2Ref.current, r3Ref.current, r4Ref.current];
//
timelineRef.current = gsap.timeline({
scrollTrigger: {
trigger: containerRef.current,
start: "top top",
end: "+=800",
scrub: 1,
pin: false,
anticipatePin: 1,
markers: false,
// DOM offsetParent
const checkAndInit = () => {
const container = containerRef.current;
const panels = [r1Ref.current, r2Ref.current, r3Ref.current, r4Ref.current];
// DOM
if (!container || !container.offsetParent || panels.some(p => !p || !p.offsetParent)) {
return false;
}
});
//
panels.forEach((panel, index) => {
const animations = [
{ y: "-5%", scale: 0.85, duration: 1 },
{ y: "-50%", scale: 0.9, duration: 1 },
{ y: "-265%", scale: 0.95, duration: 1.2 },
{ y: "-415%", scale: 0.98, duration: 2 }
];
try {
//
timelineRef.current = gsap.timeline({
scrollTrigger: {
trigger: container,
start: "top top",
end: "+=800",
scrub: 1,
pin: false,
anticipatePin: 1,
markers: false,
onUpdate: (self) => {
scrollTriggerRef.current = self;
}
}
});
timelineRef.current.to(panel, {
...animations[index],
onComplete: () => {
panel.classList.add('animated-panel');
},
onReverseComplete: () => {
panel.classList.remove('animated-panel');
}
}, 0);
});
//
const animations = [
{ y: "-5%", scale: 0.85, duration: 1 },
{ y: "-50%", scale: 0.9, duration: 1 },
{ y: "-265%", scale: 0.95, duration: 1.2 },
{ y: "-415%", scale: 0.98, duration: 2 }
];
panels.forEach((panel, index) => {
if (panel && animations[index]) {
timelineRef.current.to(panel, {
...animations[index],
onComplete: () => {
panel.classList.add('animated-panel');
},
onReverseComplete: () => {
panel.classList.remove('animated-panel');
}
}, 0);
}
});
return true;
} catch (error) {
console.error('GSAP animation init error:', error);
return false;
}
};
// 100ms
let attempts = 0;
const maxAttempts = 100; // 10
const tryInit = () => {
if (checkAndInit()) {
console.log('Resource GSAP animation initialized');
} else if (attempts < maxAttempts) {
attempts++;
setTimeout(tryInit, 100);
} else {
console.error('Failed to initialize Resource GSAP animation');
}
};
//
tryInit();
//
return () => {
if (timelineRef.current) {
timelineRef.current.kill();
}
ScrollTrigger.getAll().forEach(trigger => trigger.kill());
// ScrollTrigger
ScrollTrigger.getAll().forEach(trigger => {
if (trigger.trigger === containerRef.current) {
trigger.kill();
}
});
};
}, []);
//
// progressValues: (0-1)
const progressValues = [0, 0.25, 0.5, 0.75];
const handlePanelClick = (index, event) => {
//
if (event) {
const target = event.target;
//
if (target.closest('button') ||
target.closest('a') ||
target.closest('.item-btn') ||
target.closest('.content-item1') ||
target.closest('.content-item2') ||
target.closest('.content-item3')) {
return;
}
}
if (!scrollTriggerRef.current) return;
const st = scrollTriggerRef.current;
const targetProgress = progressValues[index];
//
// start end
const start = st.start;
const end = st.end;
const targetScroll = start + (end - start) * targetProgress + scrollOffsetConfig.current[index];
//
gsap.to(window, {
scrollTo: {
y: targetScroll,
autoKill: false
},
duration: 0.8,
ease: "power2.inOut"
});
};
//
useImperativeHandle(ref, () => ({
scrollToPanel: handlePanelClick,
// /
getScrollOffset: (index) => scrollOffsetConfig.current[index],
setScrollOffset: (index, value) => { scrollOffsetConfig.current[index] = value; },
getAllOffsets: () => ({ ...scrollOffsetConfig.current }),
}));
const getLeftContent = (index) => {
const item = resourceInfo[index];
return <div className="content-left">
@ -126,6 +228,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
const getVersionList = () => {
getSourceList({ id: factoryZoneId, keywords: versionId }).then(res => {
if (!res || !res.data || !res.data.rows) return
setResourceList2(res.data.rows.slice(0, 1));
});
}
@ -133,6 +236,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
const getToolsList = () => {
getSourceList({ id: factoryZoneId, keywords: toolId }).then(res => {
const rows = []
if (!res || !res.data || !res.data.rows) return
res.data.rows.forEach(e => {
let extendData = {}
if (e.extendData) {
@ -148,6 +252,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
const getCommonToolsList = () => {
getSourceList({ id: factoryZoneId, keywords: commontoolId }).then(res => {
const rows = []
if (!res || !res.data || !res.data.rows) return
res.data.rows.forEach(e => {
let extendData = {}
if (e.extendData) {
@ -168,6 +273,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
const getManualList = () => {
getSourceList({ id: factoryZoneId, keywords: manualId }).then(res => {
const rows = []
if (!res || !res.data || !res.data.rows) return
res.data.rows.forEach(e => {
let extendData = {}
if (e.extendData) {
@ -182,14 +288,22 @@ const Resource = forwardRef(({ zoneId }, ref) => {
return (
<div
className={`factory-resource factory-module ${isVisible ? 'animate-active' : ''}`}
ref={ref}
ref={(node) => {
// ref forwarded ref
wrapperRef.current = node;
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
ref.current = node;
}
}}
>
<div className="resource-container" ref={containerRef}>
<h1 className="module-title">共建共用共享为软件工厂赋能</h1>
<div className="panels-stack">
<div ref={r1Ref} className="resource-1">
<div ref={r1Ref} className="resource-1" onClick={(e) => handlePanelClick(0, e)} style={{ cursor: 'pointer' }}>
<div className="panel-content panel-content-1">
{
getLeftContent(1)
@ -230,7 +344,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
</div>
</div>
<div ref={r2Ref} className="resource-2">
<div ref={r2Ref} className="resource-2" onClick={(e) => handlePanelClick(1, e)} style={{ cursor: 'pointer' }}>
<div className="panel-content panel-content-2">
{
getLeftContent(0)
@ -270,7 +384,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
</div>
</div>
<div ref={r3Ref} className="resource-3">
<div ref={r3Ref} className="resource-3" onClick={(e) => handlePanelClick(2, e)} style={{ cursor: 'pointer' }}>
<div className="panel-content panel-content-3">
{
getLeftContent(2)
@ -302,7 +416,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
</div>
</div>
</div>
<div ref={r4Ref} className="resource-4">
<div ref={r4Ref} className="resource-4" onClick={(e) => handlePanelClick(3, e)} style={{ cursor: 'pointer' }}>
<div className="panel-content panel-content-4">
{
getLeftContent(3)

View File

@ -73,6 +73,7 @@
display: flex;
flex-direction: column;
margin-right: 15px;
cursor: default;
.item-head {
display: flex;
@ -162,6 +163,7 @@
align-items: center;
padding: 30px 35px;
margin-right: 18px;
cursor: default;
.item-name {
max-width: 210px;
@ -198,6 +200,7 @@
display: flex;
flex-direction: column;
margin-right: 15px;
cursor: default;
.item-desc {
margin-top: 27px;
@ -243,6 +246,7 @@
display: flex;
flex-direction: column;
margin-right: 15px;
cursor: default;
.item-desc {
margin-top: 27px;

View File

@ -100,26 +100,60 @@ function Index(props) {
}, [])
useEffect(() => {
const resource = resourceRef.current;
const resource2 = resource2Ref.current;
if (!resource || !resource2) return;
const cleanup = [];
const pinContainer = ScrollTrigger.create({
trigger: resource,
start: "top top",
endTrigger: ".factory-resource .panels-stack", // 使Resource
end: "bottom top", //
pin: resource,
pinSpacing: false,
scrub: true
});
cleanup.push(() => pinContainer.kill());
//
const checkAndInit = () => {
// DOM useImperativeHandle 使 ref DOM
const resourceEl = document.querySelector('.factory-resource');
const endTriggerEl = document.querySelector('.factory-resource .panels-stack');
if (!resourceEl || !resourceEl.offsetParent || !endTriggerEl || !endTriggerEl.offsetParent) {
return false;
}
try {
const pinContainer = ScrollTrigger.create({
trigger: resourceEl,
start: "top top",
endTrigger: endTriggerEl,
end: "bottom top",
pin: resourceEl,
pinSpacing: false,
scrub: true
});
cleanup.push(() => pinContainer.kill());
console.log('pinContainer initialized');
return true;
} catch (error) {
console.error('Error creating pinContainer ScrollTrigger:', error);
return false;
}
};
//
let attempts = 0;
const maxAttempts = 50;
const tryInit = () => {
if (checkAndInit()) {
//
} else if (attempts < maxAttempts) {
attempts++;
setTimeout(tryInit, 100);
} else {
console.warn('Failed to initialize pinContainer after max attempts');
}
};
// Resource
const timeoutId = setTimeout(tryInit, 200);
cleanup.push(() => clearTimeout(timeoutId));
return () => {
cleanup.forEach(fn => fn());
};
}, [resourceRef, resource2Ref]);
}, []);
const setupScrollListeners = () => {
window.addEventListener('scroll', helpScroll);

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB