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

求助:如何用Angular CDK实现类gridster2的拖拽网格布局

基于Angular CDK实现类Gridster2的拖拽网格布局

核心实现步骤

1. 搭建网格基础布局

先通过CSS定义网格容器和项的基础样式,使用Grid布局实现列阵结构:

.grid-container {
  display: grid;
  grid-template-columns: repeat(12, 1fr); /* 12列网格,可按需调整 */
  gap: 16px;
  padding: 16px;
  min-height: 100vh;
}

.grid-item {
  background-color: #f1f1f1;
  border-radius: 4px;
  padding: 16px;
  cursor: move;
  position: relative;
  transition: all 0.2s ease;
}

2. 集成CDK拖拽排序

借助Angular CDK的CdkDrag和CdkDropList组件,实现基础拖拽能力,并绑定拖拽结束事件处理位置更新:

<div class="grid-container" cdkDropList (cdkDropListDropped)="handleDrop($event)">
  <div 
    *ngFor="let item of gridItems; index as i"
    class="grid-item"
    [style.gridColumnStart]="item.col"
    [style.gridColumnEnd]="item.col + item.colSpan"
    [style.gridRowStart]="item.row"
    [style.gridRowEnd]="item.row + item.rowSpan"
    cdkDrag
  >
    <span>{{ item.title }}</span>
    <!-- 拖拽手柄(可选,限定拖拽触发区域) -->
    <div cdkDragHandle class="drag-handle">☰</div>
    <!-- 右下角拉伸手柄 -->
    <div class="resize-handle" (mousedown)="startResize($event, item)"></div>
  </div>
</div>

3. 拖拽位置计算与碰撞处理

在组件类中实现拖拽后的坐标计算,以及网格项的碰撞检测与自动重排:

import { Component } from '@angular/core';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';

interface GridItem {
  title: string;
  col: number;
  colSpan: number;
  row: number;
  rowSpan: number;
}

@Component({
  selector: 'app-grid',
  templateUrl: './grid.component.html',
  styleUrls: ['./grid.component.css']
})
export class GridComponent {
  gridItems: GridItem[] = [
    { title: '面板1', col: 1, colSpan: 6, row: 1, rowSpan: 2 },
    { title: '面板2', col: 7, colSpan: 6, row: 1, rowSpan: 1 },
    { title: '面板3', col: 7, colSpan: 3, row: 2, rowSpan: 2 },
    { title: '面板4', col: 10, colSpan: 3, row: 2, rowSpan: 1 },
  ];

  handleDrop(event: CdkDragDrop<GridItem[]>) {
    // 先完成数组元素位置交换
    moveItemInArray(this.gridItems, event.previousIndex, event.currentIndex);
    
    const droppedItem = this.gridItems[event.currentIndex];
    const containerRect = event.container.element.nativeElement.getBoundingClientRect();
    const colWidth = containerRect.width / 12;
    const rowHeight = 120; // 可根据实际行高动态计算

    // 根据鼠标落点计算目标列、行
    const targetCol = Math.floor((event.dropPoint.x - containerRect.left) / colWidth) + 1;
    const targetRow = Math.floor((event.dropPoint.y - containerRect.top) / rowHeight) + 1;

    // 更新拖拽项的网格位置
    droppedItem.col = Math.max(1, Math.min(13 - droppedItem.colSpan, targetCol));
    droppedItem.row = Math.max(1, targetRow);

    // 处理与其他项的碰撞,自动调整位置
    this.resolveCollisions(droppedItem);
  }

  resolveCollisions(movedItem: GridItem) {
    this.gridItems.forEach(item => {
      if (item === movedItem) return;

      // 判断是否存在网格重叠
      const isOverlap = 
        item.col < movedItem.col + movedItem.colSpan &&
        item.col + item.colSpan > movedItem.col &&
        item.row < movedItem.row + movedItem.rowSpan &&
        item.row + item.rowSpan > movedItem.row;

      if (isOverlap) {
        // 简单处理:将重叠项向下移动到拖拽项的下方,可扩展更复杂的避让逻辑
        item.row = movedItem.row + movedItem.rowSpan;
        // 递归检查新位置是否仍有重叠
        this.resolveCollisions(item);
      }
    });
  }

4. 实现网格项拉伸调整

给网格项添加拉伸手柄,监听鼠标事件动态调整项的列宽和行高:

.resize-handle {
  position: absolute;
  bottom: 4px;
  right: 4px;
  width: 16px;
  height: 16px;
  background-color: #ccc;
  cursor: nwse-resize;
  border-radius: 2px;
}
startResize(event: MouseEvent, item: GridItem) {
  event.preventDefault();
  const startX = event.clientX;
  const startY = event.clientY;
  const initColSpan = item.colSpan;
  const initRowSpan = item.rowSpan;
  const containerRect = document.querySelector('.grid-container')!.getBoundingClientRect();
  const colWidth = containerRect.width / 12;
  const rowHeight = 120;

  const onMouseMove = (e: MouseEvent) => {
    const deltaX = e.clientX - startX;
    const deltaY = e.clientY - startY;

    // 计算新的列跨度,限制最小1列、最大不超出网格边界
    const newColSpan = Math.max(1, Math.min(13 - item.col, initColSpan + Math.round(deltaX / colWidth)));
    // 计算新的行跨度,限制最小1行
    const newRowSpan = Math.max(1, initRowSpan + Math.round(deltaY / rowHeight));

    item.colSpan = newColSpan;
    item.rowSpan = newRowSpan;

    // 拉伸后检查碰撞
    this.resolveCollisions(item);
  };

  const onMouseUp = () => {
    document.removeEventListener('mousemove', onMouseMove);
    document.removeEventListener('mouseup', onMouseUp);
  };

  document.addEventListener('mousemove', onMouseMove);
  document.addEventListener('mouseup', onMouseUp);
}

优化方向

  • 响应式适配:监听窗口resize事件,动态调整网格列数和行高
  • 拖拽预览优化:自定义cdkDragPreview样式,让拖拽预览更贴合网格项尺寸
  • 布局持久化:将网格项位置数据保存到localStorage或后端,页面刷新后自动恢复
  • 复杂碰撞处理:扩展避让逻辑,支持向左/右/上调整重叠项位置,而非仅向下移动

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 12:45:29