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

如何实现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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:54:13