tencent.jquery.pix.component 1.0.62 → 1.0.63-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,7 +22,7 @@ const DEFAULTS = {
22
22
  };
23
23
 
24
24
  export function Waterfall(optionsInput = {}) {
25
- $ = getEnv();
25
+ $ = getEnv().$;
26
26
 
27
27
  this.options = Object.assign({}, DEFAULTS, optionsInput);
28
28
  const options = this.options;
@@ -30,6 +30,12 @@ export function Waterfall(optionsInput = {}) {
30
30
  // 标记是否有更新元素用的回调函数
31
31
  this.hasUpdateItem = options.updateItem && (options.updateItem.constructor === Function) ? true : false;
32
32
 
33
+ // 新方案:数据ID映射机制
34
+ this.dataIdMap = new Map(); // 数据ID -> 布局信息映射
35
+ this.nextDataId = 0; // 下一个数据ID
36
+ this.renderedDataIds = new Set(); // 已渲染的数据ID集合
37
+
38
+
33
39
  // 间隔字符串转数字
34
40
  if (options.columnGap.constructor === String) {
35
41
  // 如果是rem单位,则需要计算
@@ -85,13 +91,29 @@ export function Waterfall(optionsInput = {}) {
85
91
  this.columnItems.push(this.createColumn(i));
86
92
  }
87
93
 
88
- this.renderIndex = 0; // 渲染索引
89
- this.activeNodes = new Map(); // 当前活跃节点(索引 -> DOM)
94
+ this.renderIndex = 0; // 渲染索引(保留兼容性)
95
+ this.activeNodes = new Map(); // 当前活跃节点(数据ID -> DOM)
90
96
  this.nodePool = []; // DOM 节点池
91
97
 
98
+ // 新方案:初始化数据ID映射
99
+ this.dataIdMap = new Map(); // 数据ID -> 布局信息映射
100
+ this.nextDataId = 0; // 下一个数据ID
101
+ this.renderedDataIds = new Set(); // 已渲染的数据ID集合
102
+
103
+ // 为初始数据分配数据ID
104
+ this.options.data.forEach((item, index) => {
105
+ const dataId = index; //this.nextDataId++;
106
+ this.dataIdMap.set(dataId, {
107
+ data: item,
108
+ originalIndex: index,
109
+ layoutInfo: null // 将在布局时填充
110
+ });
111
+ });
112
+
92
113
  this.init();
93
114
  }
94
115
 
116
+
95
117
  Waterfall.prototype.init = function () {
96
118
  const self = this;
97
119
  const options = this.options;
@@ -99,6 +121,8 @@ Waterfall.prototype.init = function () {
99
121
 
100
122
  this.nodePool = []; // DOM 节点池
101
123
  this.activeNodes = new Map(); // 当前活跃节点(索引 -> DOM)
124
+ this.allReadyNodes = new Map(); // 所有节点(索引 -> DOM)
125
+ this.renderIndex = 0; // 渲染索引(保留兼容性)
102
126
 
103
127
 
104
128
 
@@ -134,8 +158,9 @@ Waterfall.prototype.updateVisibleItems = function (force = false) {
134
158
  const options = this.options;
135
159
  const $container = $(options.container);
136
160
 
137
- const startTop = this.scrollTop;
161
+ const startTop = self.scrollTop; // 当前滚动位置
138
162
  const endTop = startTop + $container.height();
163
+ // console.log('startTop', startTop)
139
164
 
140
165
  // 进行可见区域的渲染更新
141
166
  this.updateCardsInView({
@@ -143,23 +168,41 @@ Waterfall.prototype.updateVisibleItems = function (force = false) {
143
168
  end: endTop,
144
169
  force
145
170
  });
171
+
146
172
  }
147
173
 
148
174
  // 新增卡片
149
- Waterfall.prototype.appendCard = function (data, index, { top, left }) {
175
+ Waterfall.prototype.appendCard = function (data, dataId, { top, left }) {
150
176
  const self = this;
151
177
  const options = this.options;
152
178
  const $container = $(options.container);
153
179
  const $viewport = $container.find('.waterfall-list-viewport');
180
+
181
+ // 新方案:基于数据ID的数据验证
182
+ if (!this.dataIdMap.has(dataId)) {
183
+ console.error('Waterfall: Invalid dataId in appendCard', dataId);
184
+ return null;
185
+ }
186
+
187
+ const dataInfo = this.dataIdMap.get(dataId);
188
+ if (!dataInfo || !dataInfo.data) {
189
+ console.warn('Waterfall: Empty data for dataId', dataId);
190
+ }
191
+
154
192
  const $card = $(
155
193
  `<div class="waterfall-item"
156
- data-index="${index}"
157
- style="position: absolute;transform:translate(${left}px,${top}px); width:${this.columnWidth}px;"
194
+ data-index="${dataId}"
195
+ style="position: absolute;transform:translate(${left}px,${top}px);"
158
196
  >
159
- ${options.renderItem(data, index)}
197
+ ${options.renderItem(data, dataInfo.originalIndex)}
160
198
  </div> `
161
199
  );
162
200
 
201
+
202
+ if (options.columns !== 1) {
203
+ $card.width(this.columnWidth + 'px');
204
+ }
205
+
163
206
  $viewport.append($card);
164
207
  return $card;
165
208
  }
@@ -174,49 +217,100 @@ Waterfall.prototype.updateCardsInView = function ({ start, end, force = false })
174
217
  this.createCards({ end: endBuffer });
175
218
  }
176
219
 
177
- // 基于已有的列信息,进行高度可视区域下的判定 操作已有信息。 如果是新建信息 则依赖上方的createCards方法进行创建,那么在上方会异步创建新卡片。
178
- // 阶段1:复用已有节点
220
+ const startNum = start - options.bufferHeight;
221
+ const endNum = end + options.bufferHeight;
222
+ // 新方案:基于数据ID映射机制
179
223
  const newActiveNodes = new Map();
180
224
  for (let i = 0; i < this.columns; i++) {
181
225
  const column = this.columnItems[i];
182
226
 
183
227
  for (let j = 0; j < column.children.length; j++) {
184
228
  const row = column.children[j];
185
- const renderIndex = row.renderIndex;
229
+ const dataId = row.dataId; // 使用dataId替代renderIndex
230
+
231
+ // 验证数据ID有效性
232
+ if (!this.dataIdMap.has(dataId)) {
233
+ console.warn('Waterfall: Invalid dataId detected', dataId);
234
+ continue;
235
+ }
236
+
237
+ const dataInfo = this.dataIdMap.get(dataId);
238
+
239
+ if (!dataInfo) {
240
+ console.warn('Waterfall: Invalid data for dataId', dataId);
241
+ continue;
242
+ }
243
+
244
+ const data = options.data[dataId]
245
+
186
246
  // 在可视区域内 进行有关卡片的操作
187
- if (row.top <= end && row.bottom >= start) {
247
+ const bool = row.top <= endNum && row.bottom >= startNum;
248
+ if (bool) {
188
249
  // 理论上什么都不动,因为卡片的位置不会变
189
- const $card = this.activeNodes.get(renderIndex);
250
+ const $card = this.activeNodes.get(dataId);
190
251
 
191
252
  let $node = null;
192
- // 如果卡片已经在DOM中,则不用更新位置
253
+
254
+
255
+
256
+ // 遍历当前的节点是否被占用,如果被占用的话,就得要从nodePool中取一个
257
+ let bool = true;
193
258
  if ($card) {
259
+ bool = hasNodeInActives(newActiveNodes, $card)
260
+ }
261
+ // 如果卡片已经在DOM中,则不用更新位置
262
+ if (bool === false) {
194
263
  $node = $card;
195
- this.activeNodes.delete(renderIndex);
264
+ this.activeNodes.delete(dataId);
265
+ // 如果是强更,这里才会采取更新
196
266
  if (force) {
197
- this.updateRenderUI($node, options.data[renderIndex], renderIndex);
267
+ this.updateRenderUI($node, data, dataId);
198
268
  }
269
+
199
270
  } else {
200
- // 卡片不在DOM中,则更新位置
201
- let nodePool = this.nodePool;
202
- if (nodePool.length === 0) {
203
- // 这里是往上方拖动时,可能需要补建的情况
204
- // 如果池子没有节点,那么进行创建
205
- $node = this.appendCard(options.data[renderIndex], renderIndex, {
206
- top: row.top, left: row.left
207
- })
208
- row.$node = $node;
271
+ const $card = this.allReadyNodes.get(dataId);
272
+ // 遍历当前的节点是否被占用,如果被占用的话,就得要从nodePool中取一个
273
+ let bool = true;
274
+ if ($card) {
275
+ bool = hasNodeInActives(newActiveNodes, $card)
276
+ }
277
+ if (bool === false) {
278
+ // 如果成功获取到card并没有占用 那么就复用这个card
279
+ $node = $card;
280
+
281
+ this.updateRenderUI($node, data, dataId);
282
+
209
283
  } else {
210
- $node = $(this.nodePool.pop());
211
- $node.css({
212
- 'transform': `translate(${row.left}px,${row.top}px)`,
213
- }).attr('data-index', renderIndex);
214
- this.updateRenderUI($node, options.data[renderIndex], renderIndex);
215
- row.$node = $node;
284
+ // 卡片不在DOM中,则更新位置
285
+ $node = getNodePoolPop(this.nodePool, newActiveNodes);
286
+ if ($node === null) {
287
+ // 这里是往上方拖动时,可能需要补建的情况
288
+ $node = this.appendCard(data, dataId, {
289
+ top: row.top, left: row.left
290
+ });
291
+ row.$node = $node;
292
+ } else {
293
+ //$node = $(this.nodePool.pop());
294
+ this.updateRenderUI($node, data, dataId);
295
+ row.$node = $node;
296
+ }
216
297
  }
217
298
  }
218
299
 
219
- newActiveNodes.set(renderIndex, $node);
300
+ $node.css({
301
+ 'transform': `translate(${row.left}px,${row.top}px)`,
302
+ }).attr('data-index', dataId);
303
+
304
+
305
+ newActiveNodes.set(dataId, $node);
306
+
307
+ // 清除掉在NodePool中的card
308
+ const index = this.nodePool.indexOf($card);
309
+ if (index !== -1) {
310
+ this.nodePool.splice(index, 1);
311
+ }
312
+
313
+ // console.log('302-row', row.dataId, dataInfo);
220
314
  }
221
315
  }
222
316
  }
@@ -224,11 +318,16 @@ Waterfall.prototype.updateCardsInView = function ({ start, end, force = false })
224
318
  // 阶段2:处理不活跃节点
225
319
  this.activeNodes.forEach($node => {
226
320
  $node.css('transform', `translateY(-9999px)`);// 移出可视区域
227
- this.nodePool.push($node);
321
+ if (this.nodePool.indexOf($node) === -1) {
322
+ this.nodePool.push($node);
323
+ }
228
324
  });
229
325
  this.activeNodes = newActiveNodes;
326
+ // console.log('this.activeNodes', this.activeNodes);
327
+ // console.log('this.nodePool', this.nodePool);
230
328
  }
231
329
 
330
+
232
331
  Waterfall.prototype.getMaxHeight = function () {
233
332
  let maxHeight = 0;
234
333
  for (let i = 0; i < this.columns; i++) {
@@ -276,79 +375,99 @@ Waterfall.prototype.getMinHeightColumn = function () {
276
375
  }
277
376
 
278
377
  // 创建卡片
279
- Waterfall.prototype.createCards = function ({ end }) {
378
+ Waterfall.prototype.createCards = function ({ end, dataId = -1 }) {
280
379
  const self = this;
281
380
  const options = this.options;
282
381
  const $container = $(options.container);
283
- const renderIndex = this.renderIndex;
284
382
 
285
- if (renderIndex >= options.data.length) {
383
+ // 新方案:获取下一个未渲染的数据ID
384
+ let nextDataId = null;
385
+ for (let [dataId, dataInfo] of this.dataIdMap) {
386
+ if (!this.renderedDataIds.has(dataId)) {
387
+ nextDataId = dataId;
388
+ break;
389
+ }
390
+ }
391
+
392
+ // 如果没有更多数据需要渲染
393
+ if (nextDataId === null) {
286
394
  const maxHeight = this.getMaxHeight();
287
- // 设置一次整体高度
288
- $(options.container).find('.waterfall-list-scroll').css('height', maxHeight + options.marginBottom)
395
+ $container.find('.waterfall-list-scroll').css('height', maxHeight + options.marginBottom + 'px');
396
+ return;
397
+ }
398
+
399
+ const dataInfo = this.dataIdMap.get(nextDataId);
400
+ if (!dataInfo || !dataInfo.data) {
401
+ console.warn('Waterfall: Invalid data for dataId', nextDataId);
402
+ this.renderedDataIds.add(nextDataId);
289
403
  return;
290
404
  }
291
405
 
406
+ if (this.renderIndex >= this.options.data.length) {
407
+ return
408
+ }
409
+
292
410
  let column = this.getMinHeightColumn();
293
411
  if (column === null) {
294
- // 没有可用的列,则在第一列创建,说明此时还没有数据
295
412
  column = this.columnItems[0];
296
413
  }
297
414
 
298
415
  const top = column.bottom === 0 ? options.marginTop : (column.bottom + options.rowGap);
299
-
300
- // 设置卡片位置
301
- const position = {
302
- top,
303
- left: column.left,
304
- }
305
-
416
+ const position = { top, left: column.left };
306
417
  const row = createDefaultRow(position);
307
418
 
419
+ this.renderIndex += 1;
308
420
 
309
- // 添加卡片
421
+ // 添加卡片,使用dataId作为唯一标识
310
422
  let $card = null;
311
423
  if (this.nodePool.length === 0) {
312
- $card = this.appendCard(options.data[renderIndex], renderIndex, position);
424
+ $card = this.appendCard(dataInfo.data, nextDataId, position);
313
425
  } else {
314
426
  $card = $(this.nodePool.pop());
315
427
  $card.css({
316
428
  'transform': `translate(${row.left}px,${row.top}px)`,
317
- }).attr('data-index', renderIndex);
318
- this.updateRenderUI($card, options.data[renderIndex], renderIndex);
429
+ }).attr('data-index', nextDataId);
430
+ this.updateRenderUI($card, dataInfo.data, nextDataId);
319
431
  }
320
432
 
321
-
322
433
  row.$node = $card;
434
+ row.dataId = nextDataId; // 使用dataId替代renderIndex
435
+ if (dataId !== -1) {
436
+ row.dataId = dataId;
437
+ }
323
438
 
324
- // 把新增的卡片放进 activeNodes, 当前是 展示状态的
325
- this.activeNodes.set(renderIndex, $card);
439
+ // 记录布局信息
440
+ dataInfo.layoutInfo = {
441
+ columnIndex: this.columnItems.indexOf(column),
442
+ position: position,
443
+ row: row
444
+ };
326
445
 
446
+ // 把新增的卡片放进 activeNodes
447
+ this.activeNodes.set(nextDataId, $card);
448
+ this.allReadyNodes.set(nextDataId, $card);
449
+ this.renderedDataIds.add(nextDataId);
327
450
 
328
451
  // 更新列的底部距离
329
452
  column.bottom = top + $card.height();
330
453
  column.children.push(row);
331
-
332
- // 计算当前卡片的位置
333
454
  row.bottom = column.bottom;
334
- row.renderIndex = renderIndex;
335
-
336
- this.renderIndex += 1;
337
-
338
- let hasNextData = this.renderIndex < options.data.length;
339
455
 
456
+ // 检查是否需要继续创建卡片
340
457
  const minHeight = this.getMinHeight();
341
- if (hasNextData && (minHeight < end)) {
458
+ const hasMoreData = this.renderedDataIds.size < this.dataIdMap.size;
459
+
460
+ if (hasMoreData && (minHeight < end)) {
342
461
  window.requestAnimationFrame(() => {
343
462
  this.createCards({ end });
344
463
  });
345
464
  } else {
346
465
  const maxHeight = this.getMaxHeight();
347
- // 设置一次整体高度
348
- $(options.container).find('.waterfall-list-scroll').css('height', maxHeight + options.marginBottom)
466
+ $(options.container).find('.waterfall-list-scroll').css('height', maxHeight + options.marginBottom);
349
467
  }
350
468
  }
351
469
 
470
+
352
471
  Waterfall.prototype.createColumn = function (index) {
353
472
  const res = {
354
473
  left: 0,
@@ -365,33 +484,135 @@ Waterfall.prototype.createColumn = function (index) {
365
484
  return res;
366
485
  }
367
486
 
368
- Waterfall.prototype.updateRenderUI = function ($node, data, index) {
487
+ Waterfall.prototype.updateRenderUI = function ($node, data, dataId) {
369
488
  const options = this.options;
489
+
490
+ // 新方案:基于数据ID的数据验证
491
+ if (!this.dataIdMap.has(dataId)) {
492
+ console.error('Waterfall: Invalid dataId in updateRenderUI', dataId);
493
+ return;
494
+ }
495
+
496
+ const dataInfo = this.dataIdMap.get(dataId);
497
+ if (!dataInfo || !dataInfo.data) {
498
+ console.warn('Waterfall: Empty data for dataId', dataId);
499
+ }
500
+
370
501
  if (this.hasUpdateItem === true) {
371
- options.updateItem($node, data, index)
502
+ options.updateItem($node, data, dataInfo.originalIndex)
372
503
  } else {
373
- $node.html(options.renderItem(data, index));
504
+ $node.html(options.renderItem(data, dataInfo.originalIndex));
374
505
  }
375
506
  }
376
507
 
508
+
509
+
377
510
  Waterfall.prototype.updateData = function (newData) {
378
- this.options.data = newData;
511
+ const options = this.options;
512
+ options.data = newData;
513
+
514
+ // 新方案:重新建立数据ID映射
515
+ //this.dataIdMap.clear();
516
+ //this.renderedDataIds.clear();
517
+ //this.nextDataId = 0;
518
+
519
+ // 为每个数据项分配唯一ID
520
+ options.data.forEach((item, index) => {
521
+ const dataId = index; // this.nextDataId++;
522
+ if (!this.allReadyNodes.has(dataId)) {
523
+ this.dataIdMap.set(dataId, {
524
+ data: item,
525
+ originalIndex: index,
526
+ layoutInfo: null // 将在布局时填充
527
+ });
528
+ // 如果没有准备好这个数据,这里要创建一个占位节点
529
+ this.createCards({ end: 0, dataId });
530
+ }
531
+ });
532
+
379
533
  this.updateVisibleItems(true); // 强制更新渲染
534
+
535
+ // 重新计算所有卡片位置并更新位置
536
+ // this.updatePointCards();
380
537
  }
381
538
 
539
+
540
+
541
+ // 重新计算设置一遍所有的卡片位置
542
+ Waterfall.prototype.updatePointCards = function () {
543
+ const self = this;
544
+ const options = this.options;
545
+ const columnItems = this.columnItems;
546
+ let top = options.marginTop;
547
+ for (let i = 0; i < columnItems.length; i++) {
548
+ const column = columnItems[i];
549
+ // 这里为了简化,各列的瀑布流保持不动,只更新各列下节点的top位置,不做节点的跨列位移
550
+ for (let j = 0; j < column.children.length; j++) {
551
+ const row = column.children[j];
552
+ const $card = row.$node;
553
+
554
+ // 验证数据ID有效性
555
+ if (!this.dataIdMap.has(row.dataId)) {
556
+ console.warn('Waterfall: Invalid dataId in updatePointCards', row.dataId);
557
+ continue;
558
+ }
559
+
560
+ // 第一个的top不需要更新
561
+ if (j === 0) {
562
+ row.bottom = top + $card.height();
563
+ } else {
564
+ row.top = column.children[j - 1].bottom + options.rowGap;
565
+ row.bottom = row.top + $card.height();
566
+ // 更新卡片位置
567
+ // $card.css({
568
+ // 'transform': `translate(${row.left}px,${row.top}px)`,
569
+ // })
570
+ }
571
+
572
+ }
573
+ // 设置一次该列的bottom
574
+ column.bottom = getBottomByColumn(column);
575
+ }
576
+ }
577
+
578
+
382
579
  function createDefaultRow({ top, left }) {
383
580
  return {
384
581
  top,
385
582
  left,
386
583
  bottom: 0,
387
584
  $node: null,
388
- renderIndex: -1
585
+ dataId: -1 // 新方案:使用dataId替代renderIndex
389
586
  }
390
587
  }
391
588
 
392
- function getBottomByColumn(rows) {
393
- if (rows.children.length === 0) {
589
+
590
+ function getBottomByColumn(column) {
591
+ if (column.children.length === 0) {
394
592
  return 0;
395
593
  }
396
- return rows[rows.length - 1].bottom;
594
+ const child = column.children;
595
+ return child[child.length - 1].bottom;
596
+ }
597
+
598
+ function hasNodeInActives(mapObj, $node) {
599
+ let bool = false
600
+ mapObj.forEach((item) => {
601
+ if (item === $node) {
602
+ bool = true;
603
+ }
604
+ });
605
+ return bool;
606
+ }
607
+
608
+ // 从众多jq对象数组取得一个不重复的
609
+ function getNodePoolPop(nodePool, actives) {
610
+ for (let i = 0; i < nodePool.length; i++) {
611
+ const $node = nodePool[i];
612
+ if (!hasNodeInActives(actives, $node)) {
613
+ nodePool.splice(i, 1);
614
+ return $node;
615
+ }
616
+ }
617
+ return null;
397
618
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tencent.jquery.pix.component",
3
- "version": "1.0.62",
3
+ "version": "1.0.63-beta.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [