You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何动态实现不同尺寸卡片的无空隙最优热力图布局?

实现按排名的无空隙热力图式卡片布局

要实现这种按排名分配尺寸、无空隙紧凑排列的卡片布局,本质是解决矩形装箱问题,核心思路是优先放置大尺寸(高排名)卡片,用小卡片填充剩余空间。下面结合你的Angular代码给出两种实现方案:

一、自定义贪心布局算法(轻量可控)

先按排名排序卡片,排名越高尺寸越大,再用按行填充的贪心策略计算每个卡片的位置,实现无空隙排列:

1. 组件逻辑处理(TS)

先定义卡片数据结构,计算每个卡片的尺寸和布局位置:

interface Card {
  id: number;
  topic: string;
  number: number;
  rank: number; // 1为最高排名
}

@Component({
  selector: 'app-card-layout',
  templateUrl: './card-layout.component.html',
  styleUrls: ['./card-layout.component.css']
})
export class CardLayoutComponent {
  topicColorMap = { /* 你的主题-颜色映射表 */ };
  cards: Card[] = []; // 你的原始卡片数据
  layoutCards: Array<Card & { top: number; left: number; width: number; height: number }> = [];
  containerWidth = 800; // 容器宽度,可根据屏幕尺寸动态调整

  ngOnInit() {
    // 1. 按排名从高到低排序
    this.cards.sort((a, b) => a.rank - b.rank);
    // 2. 计算布局位置和尺寸
    this.layoutCards = this.calculateGreedyLayout(this.cards, this.containerWidth);
  }

  // 按行填充的贪心布局算法
  private calculateGreedyLayout(cards: Card[], containerWidth: number) {
    const layoutResult = [];
    let currentRowY = 0;
    let currentRowX = 0;
    let currentRowMaxHeight = 0;
    const baseArea = 2000; // 最高排名卡片的基础面积,可自定义

    for (const card of cards) {
      // 根据排名计算卡片面积,排名越高面积越大
      const cardArea = baseArea / (card.rank * 0.8 + 0.2); // 调整系数控制尺寸衰减速度
      // 固定宽高比为2:1,也可根据需求修改
      const width = Math.sqrt(cardArea * 2);
      const height = width / 2;

      // 当前卡片超出容器宽度,换行
      if (currentRowX + width > containerWidth) {
        currentRowY += currentRowMaxHeight;
        currentRowX = 0;
        currentRowMaxHeight = 0;
      }

      layoutResult.push({
        ...card,
        top: Math.floor(currentRowY),
        left: Math.floor(currentRowX),
        width: Math.floor(width),
        height: Math.floor(height)
      });

      currentRowX += width;
      currentRowMaxHeight = Math.max(currentRowMaxHeight, height);
    }

    return layoutResult;
  }

  toggleFlip(card: any) {
    card.flip = card.flip === 'flip' ? 'unflip' : 'flip';
  }
}

2. 修改HTML模板

给容器设置相对定位,卡片用绝对定位绑定计算好的位置和尺寸:

<div class="layout-container" [ngStyle]="{ width: containerWidth + 'px' }">
  <div 
    class="tp-wrapper"
    *ngFor="let card of layoutCards"
    [ngStyle]="{ 
      position: 'absolute',
      top: card.top + 'px',
      left: card.left + 'px',
      width: card.width + 'px',
      height: card.height + 'px'
    }"
  >
    <div class="tp-box" (click)="toggleFlip(card)" [@flipState]="card.flip">
      <div
        class="tp-box__side tp-box__front"
        [ngStyle]="{ background: topicColorMap[card.topic] }"
      >
        {{ card.topic }}
      </div>
      <div
        class="tp-box__side tp-box__back"
        [ngStyle]="{ background: topicColorMap[card.topic] }"
      >
        {{ card.number }}
      </div>
    </div>
  </div>
</div>

3. 补充CSS样式

确保卡片内部元素占满容器,实现翻转效果:

.layout-container {
  position: relative;
  border: 1px solid #eee;
  min-height: 400px;
}

.tp-wrapper {
  overflow: hidden;
}

.tp-box {
  width: 100%;
  height: 100%;
  position: relative;
  transform-style: preserve-3d;
  transition: transform 0.6s ease;
}

.tp-box__side {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 18px;
  padding: 10px;
  box-sizing: border-box;
}

.tp-box__back {
  transform: rotateY(180deg);
}

.flip {
  transform: rotateY(180deg);
}

二、使用现成布局库(高效省心)

如果不想自己写算法,可以用angular-gridster2这类专门的网格布局库,它支持自定义卡片尺寸、紧凑排列,还自带拖拽调整功能:

1. 安装依赖

npm install angular-gridster2 --save

2. 配置模块

在你的NgModule中导入GridsterModule:

import { GridsterModule } from 'angular-gridster2';

@NgModule({
  imports: [GridsterModule]
})
export class YourModule { }

3. 组件中配置布局

// 定义gridster的配置项
options: GridsterConfig = {
  gridType: 'fit', // 自适应容器宽度
  compactType: 'compactUp&Left', // 紧凑排列方向
  margin: 0, // 卡片间距设为0,实现无空隙
  outerMargin: false,
  mobileBreakpoint: 640,
  minCols: 1,
  maxCols: 10,
  minRows: 1,
  maxRows: 100,
  defaultItemCols: 2,
  defaultItemRows: 1,
  fixedColWidth: 100, // 固定列宽,也可设为百分比
  fixedRowHeight: 50 // 固定行高,对应2:1的宽高比
};

// 根据排名生成gridster的配置项
gridItems: GridsterItem[] = this.cards.map(card => ({
  cols: Math.max(1, 5 - card.rank), // 排名越高,占的列数越多
  rows: Math.max(1, 3 - Math.floor(card.rank / 2)), // 排名越高,占的行数越多
  y: 0, // 库会自动计算位置
  x: 0,
  content: card // 携带卡片数据
}));

4. 修改HTML模板

<gridster [options]="options">
  <gridster-item *ngFor="let item of gridItems" [item]="item">
    <div class="tp-box" (click)="toggleFlip(item.content)" [@flipState]="item.content.flip">
      <div
        class="tp-box__side tp-box__front"
        [ngStyle]="{ background: topicColorMap[item.content.topic] }"
      >
        {{ item.content.topic }}
      </div>
      <div
        class="tp-box__side tp-box__back"
        [ngStyle]="{ background: topicColorMap[item.content.topic] }"
      >
        {{ item.content.number }}
      </div>
    </div>
  </gridster-item>
</gridster>

进阶优化

如果需要更贴合示例图的不规则紧凑布局,可以改用**二进制空间分割(BSP)**算法,核心是将剩余空间递归分割为两个子空间,优先将大卡片放入最合适的空间,这种算法的布局密度更高,但实现复杂度也更高,适合对布局紧凑度要求极高的场景。

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 20:14:55