tencent.jquery.pix.component 1.0.61 → 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.
@@ -1,6 +1,8 @@
1
- import { $, windowEnv } from "../config.js";
1
+ import { getEnv } from "../config.js";
2
2
  import { remToPx } from "../../utils/utils.js";
3
3
 
4
+ let $ = null;
5
+
4
6
  // 默认配置
5
7
  const DEFAULTS = {
6
8
  columns: 2, // 列数
@@ -20,12 +22,20 @@ const DEFAULTS = {
20
22
  };
21
23
 
22
24
  export function Waterfall(optionsInput = {}) {
25
+ $ = getEnv().$;
26
+
23
27
  this.options = Object.assign({}, DEFAULTS, optionsInput);
24
28
  const options = this.options;
25
29
  const $container = $(options.container);
26
30
  // 标记是否有更新元素用的回调函数
27
31
  this.hasUpdateItem = options.updateItem && (options.updateItem.constructor === Function) ? true : false;
28
32
 
33
+ // 新方案:数据ID映射机制
34
+ this.dataIdMap = new Map(); // 数据ID -> 布局信息映射
35
+ this.nextDataId = 0; // 下一个数据ID
36
+ this.renderedDataIds = new Set(); // 已渲染的数据ID集合
37
+
38
+
29
39
  // 间隔字符串转数字
30
40
  if (options.columnGap.constructor === String) {
31
41
  // 如果是rem单位,则需要计算
@@ -81,13 +91,29 @@ export function Waterfall(optionsInput = {}) {
81
91
  this.columnItems.push(this.createColumn(i));
82
92
  }
83
93
 
84
- this.renderIndex = 0; // 渲染索引
85
- this.activeNodes = new Map(); // 当前活跃节点(索引 -> DOM)
94
+ this.renderIndex = 0; // 渲染索引(保留兼容性)
95
+ this.activeNodes = new Map(); // 当前活跃节点(数据ID -> DOM)
86
96
  this.nodePool = []; // DOM 节点池
87
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
+
88
113
  this.init();
89
114
  }
90
115
 
116
+
91
117
  Waterfall.prototype.init = function () {
92
118
  const self = this;
93
119
  const options = this.options;
@@ -95,6 +121,8 @@ Waterfall.prototype.init = function () {
95
121
 
96
122
  this.nodePool = []; // DOM 节点池
97
123
  this.activeNodes = new Map(); // 当前活跃节点(索引 -> DOM)
124
+ this.allReadyNodes = new Map(); // 所有节点(索引 -> DOM)
125
+ this.renderIndex = 0; // 渲染索引(保留兼容性)
98
126
 
99
127
 
100
128
 
@@ -130,8 +158,9 @@ Waterfall.prototype.updateVisibleItems = function (force = false) {
130
158
  const options = this.options;
131
159
  const $container = $(options.container);
132
160
 
133
- const startTop = this.scrollTop;
161
+ const startTop = self.scrollTop; // 当前滚动位置
134
162
  const endTop = startTop + $container.height();
163
+ // console.log('startTop', startTop)
135
164
 
136
165
  // 进行可见区域的渲染更新
137
166
  this.updateCardsInView({
@@ -139,23 +168,41 @@ Waterfall.prototype.updateVisibleItems = function (force = false) {
139
168
  end: endTop,
140
169
  force
141
170
  });
171
+
142
172
  }
143
173
 
144
174
  // 新增卡片
145
- Waterfall.prototype.appendCard = function (data, index, { top, left }) {
175
+ Waterfall.prototype.appendCard = function (data, dataId, { top, left }) {
146
176
  const self = this;
147
177
  const options = this.options;
148
178
  const $container = $(options.container);
149
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
+
150
192
  const $card = $(
151
193
  `<div class="waterfall-item"
152
- data-index="${index}"
153
- 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);"
154
196
  >
155
- ${options.renderItem(data, index)}
197
+ ${options.renderItem(data, dataInfo.originalIndex)}
156
198
  </div> `
157
199
  );
158
200
 
201
+
202
+ if (options.columns !== 1) {
203
+ $card.width(this.columnWidth + 'px');
204
+ }
205
+
159
206
  $viewport.append($card);
160
207
  return $card;
161
208
  }
@@ -170,49 +217,100 @@ Waterfall.prototype.updateCardsInView = function ({ start, end, force = false })
170
217
  this.createCards({ end: endBuffer });
171
218
  }
172
219
 
173
- // 基于已有的列信息,进行高度可视区域下的判定 操作已有信息。 如果是新建信息 则依赖上方的createCards方法进行创建,那么在上方会异步创建新卡片。
174
- // 阶段1:复用已有节点
220
+ const startNum = start - options.bufferHeight;
221
+ const endNum = end + options.bufferHeight;
222
+ // 新方案:基于数据ID映射机制
175
223
  const newActiveNodes = new Map();
176
224
  for (let i = 0; i < this.columns; i++) {
177
225
  const column = this.columnItems[i];
178
226
 
179
227
  for (let j = 0; j < column.children.length; j++) {
180
228
  const row = column.children[j];
181
- 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
+
182
246
  // 在可视区域内 进行有关卡片的操作
183
- if (row.top <= end && row.bottom >= start) {
247
+ const bool = row.top <= endNum && row.bottom >= startNum;
248
+ if (bool) {
184
249
  // 理论上什么都不动,因为卡片的位置不会变
185
- const $card = this.activeNodes.get(renderIndex);
250
+ const $card = this.activeNodes.get(dataId);
186
251
 
187
252
  let $node = null;
188
- // 如果卡片已经在DOM中,则不用更新位置
253
+
254
+
255
+
256
+ // 遍历当前的节点是否被占用,如果被占用的话,就得要从nodePool中取一个
257
+ let bool = true;
189
258
  if ($card) {
259
+ bool = hasNodeInActives(newActiveNodes, $card)
260
+ }
261
+ // 如果卡片已经在DOM中,则不用更新位置
262
+ if (bool === false) {
190
263
  $node = $card;
191
- this.activeNodes.delete(renderIndex);
264
+ this.activeNodes.delete(dataId);
265
+ // 如果是强更,这里才会采取更新
192
266
  if (force) {
193
- this.updateRenderUI($node, options.data[renderIndex], renderIndex);
267
+ this.updateRenderUI($node, data, dataId);
194
268
  }
269
+
195
270
  } else {
196
- // 卡片不在DOM中,则更新位置
197
- let nodePool = this.nodePool;
198
- if (nodePool.length === 0) {
199
- // 这里是往上方拖动时,可能需要补建的情况
200
- // 如果池子没有节点,那么进行创建
201
- $node = this.appendCard(options.data[renderIndex], renderIndex, {
202
- top: row.top, left: row.left
203
- })
204
- 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
+
205
283
  } else {
206
- $node = $(this.nodePool.pop());
207
- $node.css({
208
- 'transform': `translate(${row.left}px,${row.top}px)`,
209
- }).attr('data-index', renderIndex);
210
- this.updateRenderUI($node, options.data[renderIndex], renderIndex);
211
- 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
+ }
212
297
  }
213
298
  }
214
299
 
215
- 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);
216
314
  }
217
315
  }
218
316
  }
@@ -220,11 +318,16 @@ Waterfall.prototype.updateCardsInView = function ({ start, end, force = false })
220
318
  // 阶段2:处理不活跃节点
221
319
  this.activeNodes.forEach($node => {
222
320
  $node.css('transform', `translateY(-9999px)`);// 移出可视区域
223
- this.nodePool.push($node);
321
+ if (this.nodePool.indexOf($node) === -1) {
322
+ this.nodePool.push($node);
323
+ }
224
324
  });
225
325
  this.activeNodes = newActiveNodes;
326
+ // console.log('this.activeNodes', this.activeNodes);
327
+ // console.log('this.nodePool', this.nodePool);
226
328
  }
227
329
 
330
+
228
331
  Waterfall.prototype.getMaxHeight = function () {
229
332
  let maxHeight = 0;
230
333
  for (let i = 0; i < this.columns; i++) {
@@ -272,79 +375,99 @@ Waterfall.prototype.getMinHeightColumn = function () {
272
375
  }
273
376
 
274
377
  // 创建卡片
275
- Waterfall.prototype.createCards = function ({ end }) {
378
+ Waterfall.prototype.createCards = function ({ end, dataId = -1 }) {
276
379
  const self = this;
277
380
  const options = this.options;
278
381
  const $container = $(options.container);
279
- const renderIndex = this.renderIndex;
280
382
 
281
- 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) {
282
394
  const maxHeight = this.getMaxHeight();
283
- // 设置一次整体高度
284
- $(options.container).find('.waterfall-list-scroll').css('height', maxHeight + options.marginBottom)
395
+ $container.find('.waterfall-list-scroll').css('height', maxHeight + options.marginBottom + 'px');
285
396
  return;
286
397
  }
287
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);
403
+ return;
404
+ }
405
+
406
+ if (this.renderIndex >= this.options.data.length) {
407
+ return
408
+ }
409
+
288
410
  let column = this.getMinHeightColumn();
289
411
  if (column === null) {
290
- // 没有可用的列,则在第一列创建,说明此时还没有数据
291
412
  column = this.columnItems[0];
292
413
  }
293
414
 
294
415
  const top = column.bottom === 0 ? options.marginTop : (column.bottom + options.rowGap);
295
-
296
- // 设置卡片位置
297
- const position = {
298
- top,
299
- left: column.left,
300
- }
301
-
416
+ const position = { top, left: column.left };
302
417
  const row = createDefaultRow(position);
303
418
 
419
+ this.renderIndex += 1;
304
420
 
305
- // 添加卡片
421
+ // 添加卡片,使用dataId作为唯一标识
306
422
  let $card = null;
307
423
  if (this.nodePool.length === 0) {
308
- $card = this.appendCard(options.data[renderIndex], renderIndex, position);
424
+ $card = this.appendCard(dataInfo.data, nextDataId, position);
309
425
  } else {
310
426
  $card = $(this.nodePool.pop());
311
427
  $card.css({
312
428
  'transform': `translate(${row.left}px,${row.top}px)`,
313
- }).attr('data-index', renderIndex);
314
- this.updateRenderUI($card, options.data[renderIndex], renderIndex);
429
+ }).attr('data-index', nextDataId);
430
+ this.updateRenderUI($card, dataInfo.data, nextDataId);
315
431
  }
316
432
 
317
-
318
433
  row.$node = $card;
434
+ row.dataId = nextDataId; // 使用dataId替代renderIndex
435
+ if (dataId !== -1) {
436
+ row.dataId = dataId;
437
+ }
319
438
 
320
- // 把新增的卡片放进 activeNodes, 当前是 展示状态的
321
- this.activeNodes.set(renderIndex, $card);
439
+ // 记录布局信息
440
+ dataInfo.layoutInfo = {
441
+ columnIndex: this.columnItems.indexOf(column),
442
+ position: position,
443
+ row: row
444
+ };
322
445
 
446
+ // 把新增的卡片放进 activeNodes
447
+ this.activeNodes.set(nextDataId, $card);
448
+ this.allReadyNodes.set(nextDataId, $card);
449
+ this.renderedDataIds.add(nextDataId);
323
450
 
324
451
  // 更新列的底部距离
325
452
  column.bottom = top + $card.height();
326
453
  column.children.push(row);
327
-
328
- // 计算当前卡片的位置
329
454
  row.bottom = column.bottom;
330
- row.renderIndex = renderIndex;
331
-
332
- this.renderIndex += 1;
333
-
334
- let hasNextData = this.renderIndex < options.data.length;
335
455
 
456
+ // 检查是否需要继续创建卡片
336
457
  const minHeight = this.getMinHeight();
337
- if (hasNextData && (minHeight < end)) {
458
+ const hasMoreData = this.renderedDataIds.size < this.dataIdMap.size;
459
+
460
+ if (hasMoreData && (minHeight < end)) {
338
461
  window.requestAnimationFrame(() => {
339
462
  this.createCards({ end });
340
463
  });
341
464
  } else {
342
465
  const maxHeight = this.getMaxHeight();
343
- // 设置一次整体高度
344
- $(options.container).find('.waterfall-list-scroll').css('height', maxHeight + options.marginBottom)
466
+ $(options.container).find('.waterfall-list-scroll').css('height', maxHeight + options.marginBottom);
345
467
  }
346
468
  }
347
469
 
470
+
348
471
  Waterfall.prototype.createColumn = function (index) {
349
472
  const res = {
350
473
  left: 0,
@@ -361,33 +484,135 @@ Waterfall.prototype.createColumn = function (index) {
361
484
  return res;
362
485
  }
363
486
 
364
- Waterfall.prototype.updateRenderUI = function ($node, data, index) {
487
+ Waterfall.prototype.updateRenderUI = function ($node, data, dataId) {
365
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
+
366
501
  if (this.hasUpdateItem === true) {
367
- options.updateItem($node, data, index)
502
+ options.updateItem($node, data, dataInfo.originalIndex)
368
503
  } else {
369
- $node.html(options.renderItem(data, index));
504
+ $node.html(options.renderItem(data, dataInfo.originalIndex));
370
505
  }
371
506
  }
372
507
 
508
+
509
+
373
510
  Waterfall.prototype.updateData = function (newData) {
374
- 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
+
375
533
  this.updateVisibleItems(true); // 强制更新渲染
534
+
535
+ // 重新计算所有卡片位置并更新位置
536
+ // this.updatePointCards();
376
537
  }
377
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
+
378
579
  function createDefaultRow({ top, left }) {
379
580
  return {
380
581
  top,
381
582
  left,
382
583
  bottom: 0,
383
584
  $node: null,
384
- renderIndex: -1
585
+ dataId: -1 // 新方案:使用dataId替代renderIndex
385
586
  }
386
587
  }
387
588
 
388
- function getBottomByColumn(rows) {
389
- if (rows.children.length === 0) {
589
+
590
+ function getBottomByColumn(column) {
591
+ if (column.children.length === 0) {
390
592
  return 0;
391
593
  }
392
- 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;
393
618
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tencent.jquery.pix.component",
3
- "version": "1.0.61",
3
+ "version": "1.0.63-beta.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [