如何实现mat-grid-tile的鼠标拖拽调整大小?附基础布局代码
实现Mat-Grid-Tile的拖拽调整大小
嘿,我来帮你搞定这个需求!Angular Material的<mat-grid-list>本身并没有内置拖拽调整tile大小的功能,但我们可以通过自定义指令结合鼠标事件监听来实现,核心思路就是监听拖拽动作,动态修改目标tile的colspan/rowspan属性,同时调整相邻tile的对应属性来维持布局的完整性。
下面是一步步的实现方案:
1. 先给布局加拖拽手柄和样式
首先,我们需要给需要调整大小的tile添加一个拖拽触发区域(手柄),这样用户知道哪里可以拖拽,同时加一些样式让交互更清晰:
<div> <mat-grid-list cols="12" rowHeight="100px"> <!-- 左侧导航tile:右侧加拖拽手柄调整宽度 --> <mat-grid-tile #leftTile colspan="3" rowspan="9" class="resizable-tile"> <left-nav></left-nav> <!-- 右侧拖拽手柄 --> <div class="drag-handle drag-handle-right"></div> </mat-grid-tile> <!-- 右上导航tile:底部加拖拽手柄调整高度 --> <mat-grid-tile #topRightTile colspan="9" rowspan="4" class="resizable-tile"> <right-top-nav></right-top-nav> <!-- 底部拖拽手柄 --> <div class="drag-handle drag-handle-bottom"></div> </mat-grid-tile> <!-- 右下导航tile --> <mat-grid-tile #bottomRightTile colspan="9" rowspan="5"> <right-bottom-nav></right-bottom-nav> </mat-grid-tile> </mat-grid-list> </div>
对应的CSS样式,让手柄可见且有正确的光标提示:
.resizable-tile { position: relative; } .drag-handle { position: absolute; background-color: rgba(0, 0, 0, 0.2); transition: background-color 0.2s; } .drag-handle:hover { background-color: rgba(0, 0, 0, 0.4); } .drag-handle-right { width: 8px; top: 0; right: 0; height: 100%; cursor: col-resize; /* 水平拖拽光标 */ } .drag-handle-bottom { height: 8px; bottom: 0; left: 0; width: 100%; cursor: row-resize; /* 垂直拖拽光标 */ }
2. 写一个自定义拖拽指令
接下来创建Angular指令ResizeTileDirective,用来处理鼠标的拖拽逻辑,计算拖拽距离并转换为tile的colspan/rowspan变化:
import { Directive, ElementRef, HostListener, Input, Output, EventEmitter } from '@angular/core'; import { MatGridTile } from '@angular/material/grid-list'; @Directive({ selector: '[appResizeTile]' }) export class ResizeTileDirective { @Input() tile!: MatGridTile; // 传入目标mat-grid-tile实例 @Input() resizeDirection: 'width' | 'height' = 'width'; // 拖拽方向:宽度/高度 @Input() minSize = 1; // 最小允许的colspan/rowspan @Input() maxSize = 12; // 最大允许的colspan/rowspan @Output() sizeChanged = new EventEmitter<{ colspan?: number, rowspan?: number }>(); private isDragging = false; private startX = 0; private startY = 0; private initialColspan = 0; private initialRowspan = 0; constructor(private el: ElementRef) {} @HostListener('mousedown', ['$event']) onMouseDown(event: MouseEvent) { event.preventDefault(); this.isDragging = true; // 记录拖拽起始位置和tile初始大小 this.startX = event.clientX; this.startY = event.clientY; this.initialColspan = this.tile.colspan; this.initialRowspan = this.tile.rowspan; // 添加全局鼠标事件监听,确保拖拽时即使鼠标离开手柄也能响应 document.addEventListener('mousemove', this.handleMouseMove.bind(this)); document.addEventListener('mouseup', this.handleMouseUp.bind(this)); } private handleMouseMove(event: MouseEvent) { if (!this.isDragging) return; if (this.resizeDirection === 'width') { // 计算每列的宽度:grid总宽度除以总列数 const gridTotalWidth = this.el.nativeElement.closest('mat-grid-list')?.offsetWidth || 0; const colWidth = gridTotalWidth / 12; // 计算鼠标移动的距离,转换为列数变化 const deltaX = event.clientX - this.startX; const deltaCols = Math.round(deltaX / colWidth); // 限制在min和max范围内 const newColspan = Math.max(this.minSize, Math.min(this.maxSize, this.initialColspan + deltaCols)); if (newColspan !== this.tile.colspan) { this.tile.colspan = newColspan; this.sizeChanged.emit({ colspan: newColspan }); } } else if (this.resizeDirection === 'height') { // 行高是固定的100px,直接计算行数变化 const rowHeight = 100; const deltaY = event.clientY - this.startY; const deltaRows = Math.round(deltaY / rowHeight); const newRowspan = Math.max(this.minSize, Math.min(this.maxSize, this.initialRowspan + deltaRows)); if (newRowspan !== this.tile.rowspan) { this.tile.rowspan = newRowspan; this.sizeChanged.emit({ rowspan: newRowspan }); } } } private handleMouseUp() { this.isDragging = false; // 移除全局事件监听 document.removeEventListener('mousemove', this.handleMouseMove.bind(this)); document.removeEventListener('mouseup', this.handleMouseUp.bind(this)); } }
3. 在组件中使用指令并处理布局联动
现在把指令绑定到拖拽手柄上,同时处理sizeChanged事件,让相邻的tile同步调整大小,保证整个grid的布局不会乱:
<div> <mat-grid-list cols="12" rowHeight="100px"> <mat-grid-tile #leftTile colspan="3" rowspan="9" class="resizable-tile"> <left-nav></left-nav> <div class="drag-handle drag-handle-right" appResizeTile [tile]="leftTile" resizeDirection="width" [minSize]="1" [maxSize]="8" (sizeChanged)="onLeftTileResize($event)"></div> </mat-grid-tile> <mat-grid-tile #topRightTile colspan="9" rowspan="4" class="resizable-tile"> <right-top-nav></right-top-nav> <div class="drag-handle drag-handle-bottom" appResizeTile [tile]="topRightTile" resizeDirection="height" [minSize]="1" [maxSize]="7" (sizeChanged)="onTopRightTileResize($event)"></div> </mat-grid-tile> <mat-grid-tile #bottomRightTile colspan="9" rowspan="5"> <right-bottom-nav></right-bottom-nav> </mat-grid-tile> </mat-grid-list> </div>
然后在组件类里实现事件处理方法:
import { Component, ViewChild } from '@angular/core'; import { MatGridTile } from '@angular/material/grid-list'; @Component({ selector: 'app-your-layout-component', templateUrl: './your-layout-component.component.html', styleUrls: ['./your-layout-component.component.css'] }) export class YourLayoutComponent { @ViewChild('bottomRightTile') bottomRightTile!: MatGridTile; onLeftTileResize(event: { colspan?: number }) { if (event.colspan) { // 总列数是12,所以右侧两个tile的colspan要等于12减去左侧tile的colspan this.bottomRightTile.colspan = 12 - event.colspan; // 如果右上tile的colspan需要同步调整,也可以在这里设置 } } onTopRightTileResize(event: { rowspan?: number }) { if (event.rowspan) { // 总行数是9(左侧tile的rowspan是9),所以右下tile的rowspan等于9减去右上tile的rowspan this.bottomRightTile.rowspan = 9 - event.rowspan; } } }
4. 一些需要注意的细节
- 别忘了在你的Angular模块中导入
MatGridListModule和MatGridTileModule,还有自定义的ResizeTileDirective。 - 如果你的grid是响应式的(比如列数随窗口大小变化),需要监听窗口resize事件,动态重新计算每列的宽度。
- 可以给拖拽过程添加视觉反馈,比如给正在拖拽的tile加个边框,或者显示一条辅助线提示调整后的位置。
- 可以根据需求扩展拖拽方向,比如给tile添加四个方向的手柄,同时支持宽高调整。
内容的提问来源于stack exchange,提问作者Tanmoy Bhattacharjee
相关产品推荐
相关产品推荐

