1- 。 <!DOCTYPE html>
1+ <!DOCTYPE html>
22< html lang ="zh-CN " class ="dark ">
33< head >
44 < meta charset ="UTF-8 ">
55 < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
6- < title > NexusFlow V2 - 修复增强版 </ title >
6+ < title > NexusFlow</ title >
77
88 < script src ="https://cdn.tailwindcss.com "> </ script >
99
3939 opacity : 0 ;
4040 }
4141
42- .grid-item {
43- width : calc (50% - 16px );
44- margin-bottom : 24px ;
45- opacity : 0 ; /* 初始不可见,等待动画 */
46- }
47-
48- @media (min-width : 768px ) { .grid-item { width : calc (33.333% - 16px ); } }
49- @media (min-width : 1280px ) { .grid-item { width : calc (20% - 16px ); } }
42+ # gallery { position : relative; }
43+ .grid-item { position : absolute; width : 0 ; margin-bottom : 0 ; opacity : 0 ; will-change : transform; }
5044
5145 .img-container {
5246 transform-style : preserve-3d;
@@ -125,9 +119,9 @@ <h1 class="text-xl font-bold tracking-widest">NEXUS<span class="text-neon">FLOW<
125119
126120 <!-- JS 库 -->
127121 < script src ="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js "> </ script >
128- < script src ="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js "> </ script >
129- < script src ="https://unpkg.com/imagesloaded@5/imagesloaded.pkgd.min.js "> </ script >
122+
130123 < script src ="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js "> </ script >
124+ < script src ="wallpaper.js "> </ script >
131125
132126 < script >
133127 // --- 配置 ---
@@ -138,48 +132,76 @@ <h1 class="text-xl font-bold tracking-widest">NEXUS<span class="text-neon">FLOW<
138132
139133 // 状态变量
140134 let isLoading = false ;
141- let msnry ; // Masonry 实例
135+ let columns = 0 ;
136+ let colHeights = [ ] ;
137+ let colLefts = [ ] ;
138+ let columnWidth = 0 ;
142139
143- // API 源
144- const apiSources = [
145- { name : 'Picsum' , fn : ( w , h ) => `https://picsum.photos/id/${ Math . floor ( Math . random ( ) * 1000 ) } /${ w } /${ h } ` } ,
146- { name : 'LoremFlickr' , fn : ( w , h ) => `https://loremflickr.com/${ w } /${ h } /cyberpunk,neon,tech?random=${ Math . random ( ) } ` } ,
147- { name : 'PlaceIMG' , fn : ( w , h ) => `https://placeimg.com/${ w } /${ h } /tech?t=${ Math . random ( ) } ` }
148- ] ;
140+ // 使用聚合抓取函数 fetchWallpapers(来自 wallpaper.js)替代所有单一图片 API
149141
150- // --- 1. 初始化 Masonry ---
151- // 使用原生 JS 初始化,便于控制
152142 const grid = document . querySelector ( '#gallery' ) ;
153- msnry = new Masonry ( grid , {
154- itemSelector : '.grid-item' ,
155- columnWidth : '.grid-item' ,
156- gutter : CONFIG . gutter ,
157- percentPosition : true ,
158- transitionDuration : 0 // 关闭 Masonry 自带动画,用 GSAP
159- } ) ;
143+ function computeColumns ( ) {
144+ const w = grid . clientWidth ;
145+ const cols = w < 768 ? 2 : ( w < 1280 ? 3 : 5 ) ;
146+ if ( cols === columns && columnWidth ) return ;
147+ columns = cols ;
148+ columnWidth = Math . floor ( ( w - CONFIG . gutter * ( columns - 1 ) ) / columns ) ;
149+ colHeights = new Array ( columns ) . fill ( 0 ) ;
150+ colLefts = [ ] ;
151+ for ( let i = 0 ; i < columns ; i ++ ) { colLefts [ i ] = i * ( columnWidth + CONFIG . gutter ) ; }
152+ }
153+ function placeItem ( el ) {
154+ el . style . width = columnWidth + 'px' ;
155+ el . style . position = 'absolute' ;
156+ const h = el . offsetHeight ;
157+ let idx = 0 ; let min = colHeights [ 0 ] ;
158+ for ( let i = 1 ; i < columns ; i ++ ) { if ( colHeights [ i ] < min ) { min = colHeights [ i ] ; idx = i ; } }
159+ el . style . transform = 'translate(' + colLefts [ idx ] + 'px,' + min + 'px)' ;
160+ colHeights [ idx ] = min + h + CONFIG . gutter ;
161+ grid . style . height = Math . max . apply ( null , colHeights ) + 'px' ;
162+ }
163+ function layoutNew ( items ) {
164+ computeColumns ( ) ;
165+ for ( let i = 0 ; i < items . length ; i ++ ) { placeItem ( items [ i ] ) ; }
166+ }
167+ function layoutAll ( ) {
168+ computeColumns ( ) ;
169+ colHeights = new Array ( columns ) . fill ( 0 ) ;
170+ const items = Array . prototype . slice . call ( grid . querySelectorAll ( '.grid-item' ) ) ;
171+ for ( let i = 0 ; i < items . length ; i ++ ) { placeItem ( items [ i ] ) ; }
172+ }
173+ window . __imgLoaded = function ( img ) {
174+ const item = img . closest ( '.grid-item' ) ;
175+ if ( item ) {
176+ const cont = item . querySelector ( '.img-container' ) ;
177+ if ( cont ) cont . style . height = '' ;
178+ layoutAll ( ) ;
179+ }
180+ } ;
181+ window . addEventListener ( 'resize' , function ( ) { layoutAll ( ) ; } ) ;
160182
161- // --- 2. 生成图片数据 ---
162- function createElements ( count ) {
163- let elements = [ ] ;
164- for ( let i = 0 ; i < count ; i ++ ) {
183+ // --- 2. 生成图片元素(基于抓取结果) ---
184+ function buildElements ( items ) {
185+ const elements = [ ] ;
186+ for ( let i = 0 ; i < items . length ; i ++ ) {
187+ const it = items [ i ] || { } ;
165188 const width = 600 ;
166- const height = Math . floor ( Math . random ( ) * ( 800 - 400 + 1 ) ) + 400 ; // 随机高度
167-
168- // 随机选择源 (优先用 Picsum 和 Flickr 因为比较稳)
169- const source = apiSources [ Math . floor ( Math . random ( ) * 2 ) ] ;
170- const url = source . fn ( width , height ) ;
189+ const height = ( it . width && it . height ) ? Math . round ( width * ( it . height / it . width ) ) : Math . floor ( Math . random ( ) * ( 800 - 400 + 1 ) ) + 400 ;
190+ const url = it . url ;
191+ const api = it . source || 'unknown' ;
192+ const dim = ( it . width && it . height ) ? `${ it . width } x${ it . height } ` : `${ width } x${ height } ` ;
171193
172194 const div = document . createElement ( 'div' ) ;
173195 div . className = 'grid-item' ;
174196 div . innerHTML = `
175- <div class="img-container relative rounded-xl overflow-hidden bg-gray-800 cursor-pointer group">
197+ <div class="img-container relative rounded-xl overflow-hidden bg-gray-800 cursor-pointer group" style="height: ${ height } px" >
176198 <div class="skeleton absolute inset-0 z-0"></div>
177199 <img src="${ url } "
178200 class="relative z-10 w-full block opacity-0 transition-opacity duration-700"
179201 loading="lazy"
180- data-api="${ source . name } "
181- data-dim="${ width } x ${ height } "
182- onload="this.style.opacity=1; this.previousElementSibling.style.display='none';"
202+ data-api="${ api } "
203+ data-dim="${ dim } "
204+ onload="this.style.opacity=1; this.previousElementSibling.style.display='none'; if(window.__imgLoaded) __imgLoaded(this); "
183205 onerror="this.parentElement.parentElement.style.display='none'"
184206 >
185207 <div class="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 z-20 flex items-center justify-center">
@@ -193,46 +215,36 @@ <h1 class="text-xl font-bold tracking-widest">NEXUS<span class="text-neon">FLOW<
193215 }
194216
195217 // --- 3. 加载逻辑 ---
196- function loadImages ( ) {
218+ async function loadImages ( ) {
197219 if ( isLoading ) return ;
198220 isLoading = true ;
199-
200- // 显示 Loading
201221 gsap . to ( '#loader' , { opacity : 1 , duration : 0.2 } ) ;
202- document . getElementById ( 'status-indicator' ) . innerText = "DOWNLOADING..." ;
222+ document . getElementById ( 'status-indicator' ) . innerText = "FETCHING WALLPAPERS..." ;
223+
224+ try {
225+ const orientation = ( window . innerWidth > window . innerHeight ) ? 'landscape' : 'portrait' ;
226+ const params = new URLSearchParams ( location . search ) ;
227+ const query = params . get ( 'q' ) || '' ;
228+ const items = await window . fetchWallpapers ( { count : CONFIG . batchSize , orientation, query, sources : [ 'unsplash' , 'picsum' ] } ) ;
229+ const newElems = buildElements ( items ) ;
203230
204- // 模拟一点点延迟,让动画更自然
205- setTimeout ( ( ) => {
206- const newElems = createElements ( CONFIG . batchSize ) ;
207-
208- // 1. 先把元素加入 DOM
209231 const fragment = document . createDocumentFragment ( ) ;
210232 newElems . forEach ( el => fragment . appendChild ( el ) ) ;
211233 grid . appendChild ( fragment ) ;
212-
213- // 2. 追加给 Masonry
214- msnry . appended ( newElems ) ;
215-
216- // 3. 等待这批图片下载完成(或者失败)再重新布局
217- // 这是一个关键点:imagesLoaded 确保图片有高度了再布局,防止重叠
218- imagesLoaded ( grid , function ( ) {
219- msnry . layout ( ) ;
220-
221- // 4. GSAP 入场动画
222- gsap . to ( newElems , {
223- opacity : 1 ,
224- y : 0 ,
225- duration : 0.8 ,
226- stagger : 0.05 ,
227- ease : "power2.out" ,
228- onComplete : ( ) => {
229- isLoading = false ;
230- gsap . to ( '#loader' , { opacity : 0 , duration : 0.2 } ) ;
231- document . getElementById ( 'status-indicator' ) . innerText = "SYSTEM READY" ;
232- }
233- } ) ;
234- } ) ;
235- } , 500 ) ;
234+ layoutNew ( newElems ) ;
235+ gsap . fromTo ( newElems ,
236+ { opacity : 0 } ,
237+ { opacity : 1 , duration : 0.8 , stagger : 0.05 , ease : "power2.out" , onComplete : ( ) => {
238+ isLoading = false ;
239+ gsap . to ( '#loader' , { opacity : 0 , duration : 0.2 } ) ;
240+ document . getElementById ( 'status-indicator' ) . innerText = "SYSTEM READY" ;
241+ } }
242+ ) ;
243+ } catch ( e ) {
244+ isLoading = false ;
245+ gsap . to ( '#loader' , { opacity : 0 , duration : 0.2 } ) ;
246+ document . getElementById ( 'status-indicator' ) . innerText = "NETWORK ERROR" ;
247+ }
236248 }
237249
238250 // --- 4. 无限滚动 (IntersectionObserver) ---
0 commit comments