求助:如何用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
相关产品推荐
相关产品推荐

