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

为Grid布局切换添加过渡动画的实现问题

问题描述

我创建了一个3*3的Grid布局,初始状态下div1占据4个网格单元格;当点击其他div时,该div会占据4个单元格,div1收缩为1个单元格。我尝试为div添加transition属性来减缓收缩与展开的速度,但动画并未生效。

原代码

App.jsx

import React, { useState } from 'react';
import './App.css';

const App = () => {
  const initialGrid = {
    div1: { gridArea: '1 / 1 / 3 / 3' },
    div2: { gridArea: '1 / 3 / 2 / 4' },
    div3: { gridArea: '2 / 3 / 3 / 4' },
    div4: { gridArea: '3 / 3 / 4 / 4' },
    div5: { gridArea: '3 / 2 / 4 / 3' },
    div6: { gridArea: '3 / 1 / 4 / 2' },
  };

  const [grid, setGrid] = useState(initialGrid);

  const handleClick = (div) => {
    switch (div) {
      case 'div1':
        setGrid({
          ...initialGrid,
          div1: { gridArea: '1 / 1 / 3 / 3' },
        });
        break;
      case 'div2':
        setGrid({
          ...initialGrid,
          div1: { gridArea: '1 / 1 / 2 / 2' },
          div2: { gridArea: '1 / 2 / 3 / 4' },
          div3: { gridArea: '3 / 3 / 4/ 4' },
          div4: { gridArea: '3 / 2 / 4 / 3' },
          div5: { gridArea: '3 / 1 / 4 / 2' },
          div6: { gridArea: '2 / 2 / 3/ 1' },
        });
        break;
      case 'div3':
        setGrid({
          ...initialGrid,
          div1: { gridArea: '1 / 2 / 2 / 2' },
          div2: { gridArea: '1 / 3 / 2 / 4' },
          div3: { gridArea: '2 / 2 / 4/ 4' },
          div4: { gridArea: '3 / 1 / 4 / 2' },
          div5: { gridArea: '2 / 1 / 2 / 2' },
          div6: { gridArea: '1 / 1 / 2 / 2' },
        });
        break;
      case 'div4':
        setGrid({
          ...initialGrid,
          div1: { gridArea: '1 / 3 / 2 / 4' },
          div2: { gridArea: '2 / 3 / 3 / 4' },
          div3: { gridArea: '3 / 3 / 4 / 4' },
          div4: { gridArea: '2 / 1 / 4 / 3' },
          div5: { gridArea: '1 / 1 / 2 / 2' },
          div6: { gridArea: '1 / 2 / 2 / 2' },
        });
        break;
      case 'div5':
        setGrid({
          ...initialGrid,
          div1: { gridArea: '2 / 3 / 3 / 4' },
          div2: { gridArea: '1 / 3 / 2 / 4' },
          div3: { gridArea: '3 / 2 / 4 / 3' },
          div4: { gridArea: '3 / 3 / 4 / 4' },
          div5: { gridArea: '1 / 1 / 3 / 3' },
          div6: { gridArea: '3 / 1 / 4 / 2' },
        });
        break;
      case 'div6':
        setGrid({
          ...initialGrid,
          div1: { gridArea: '1 / 1 / 2 / 2' },
          div2: { gridArea: '1 / 3 / 2 / 4' },
          div3: { gridArea: '2 / 3 / 3 / 4' },
          div4: { gridArea: '3 / 3 / 4 / 4' },
          div5: { gridArea: '1 / 2 / 2 / 2' },
          div6: { gridArea: '2 / 1 / 4 / 3' },
        });
        break;
      default:
        setGrid(initialGrid);
    }
  };

  return (
    <div className="grid-container">
      <div
        className="grid-item"
        style={{ ...grid.div1, backgroundColor: 'red' }}
        onClick={() => handleClick('div1')}
      >
        1
      </div>
      <div
        className="grid-item"
        style={{ ...grid.div2, backgroundColor: 'blue' }}
        onClick={() => handleClick('div2')}
      >
        2
      </div>
      <div
        className="grid-item"
        style={{ ...grid.div3, backgroundColor: 'green' }}
        onClick={() => handleClick('div3')}
      >
        3
      </div>
      <div
        className="grid-item"
        style={{ ...grid.div4, backgroundColor: 'black' }}
        onClick={() => handleClick('div4')}
      >
        4
      </div>
      <div
        className="grid-item"
        style={{ ...grid.div5, backgroundColor: 'orange' }}
        onClick={() => handleClick('div5')}
      >
        5
      </div>
      <div
        className="grid-item"
        style={{ ...grid.div6, backgroundColor: 'purple' }}
        onClick={() => handleClick('div6')}
      >
        6
      </div>
    </div>
  );
};

export default App;

App.css

.grid-container {
  display: grid;
  grid-template-rows: repeat(4, 100px);
  grid-template-columns: repeat(4, 100px);
  gap: 5px;
  width: 410px;
  margin: 0 auto;
  transition: all 2000ms ease-in-out;
}

.grid-item {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #000;
  font-size: 2.5rem;
  color: white;
  transition: all 2000ms ease-in-out;
}
解决方案

问题原因

CSS的transition属性仅支持可插值的数值型属性,而grid-area是复合属性,其值为四个离散位置值的组合(行起始/列起始/行结束/列结束),浏览器无法对这类离散值进行平滑过渡计算,因此添加transition后无动画效果。

修复步骤

  1. 将复合属性gridArea拆分为单独的gridRowStart、gridRowEnd、gridColumnStart、gridColumnEnd属性,这些均为数值型属性,支持过渡动画。
  2. 调整React状态中的网格布局定义,使用拆分后的行列属性替代gridArea。
  3. 在CSS中明确指定过渡的网格属性(或保留all),确保浏览器能识别可过渡的属性。

修改后的代码

App.jsx

import React, { useState } from 'react';
import './App.css';

const App = () => {
  // 使用拆分的行列属性替代复合gridArea
  const initialGrid = {
    div1: { gridRowStart: 1, gridRowEnd: 3, gridColumnStart: 1, gridColumnEnd: 3 },
    div2: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 3, gridColumnEnd: 4 },
    div3: { gridRowStart: 2, gridRowEnd: 3, gridColumnStart: 3, gridColumnEnd: 4 },
    div4: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 3, gridColumnEnd: 4 },
    div5: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 2, gridColumnEnd: 3 },
    div6: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 1, gridColumnEnd: 2 },
  };

  const [grid, setGrid] = useState(initialGrid);

  const handleClick = (div) => {
    switch (div) {
      case 'div1':
        setGrid(initialGrid);
        break;
      case 'div2':
        setGrid({
          ...initialGrid,
          div1: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 1, gridColumnEnd: 2 },
          div2: { gridRowStart: 1, gridRowEnd: 3, gridColumnStart: 2, gridColumnEnd: 4 },
          div3: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 3, gridColumnEnd: 4 },
          div4: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 2, gridColumnEnd: 3 },
          div5: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 1, gridColumnEnd: 2 },
          div6: { gridRowStart: 2, gridRowEnd: 3, gridColumnStart: 1, gridColumnEnd: 2 },
        });
        break;
      case 'div3':
        setGrid({
          ...initialGrid,
          div1: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 2, gridColumnEnd: 3 },
          div2: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 3, gridColumnEnd: 4 },
          div3: { gridRowStart: 2, gridRowEnd: 4, gridColumnStart: 2, gridColumnEnd: 4 },
          div4: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 1, gridColumnEnd: 2 },
          div5: { gridRowStart: 2, gridRowEnd: 3, gridColumnStart: 1, gridColumnEnd: 2 },
          div6: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 1, gridColumnEnd: 2 },
        });
        break;
      case 'div4':
        setGrid({
          ...initialGrid,
          div1: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 3, gridColumnEnd: 4 },
          div2: { gridRowStart: 2, gridRowEnd: 3, gridColumnStart: 3, gridColumnEnd: 4 },
          div3: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 3, gridColumnEnd: 4 },
          div4: { gridRowStart: 2, gridRowEnd: 4, gridColumnStart: 1, gridColumnEnd: 3 },
          div5: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 1, gridColumnEnd: 2 },
          div6: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 2, gridColumnEnd: 3 },
        });
        break;
      case 'div5':
        setGrid({
          ...initialGrid,
          div1: { gridRowStart: 2, gridRowEnd: 3, gridColumnStart: 3, gridColumnEnd: 4 },
          div2: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 3, gridColumnEnd: 4 },
          div3: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 2, gridColumnEnd: 3 },
          div4: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 3, gridColumnEnd: 4 },
          div5: { gridRowStart: 1, gridRowEnd: 3, gridColumnStart: 1, gridColumnEnd: 3 },
          div6: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 1, gridColumnEnd: 2 },
        });
        break;
      case 'div6':
        setGrid({
          ...initialGrid,
          div1: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 1, gridColumnEnd: 2 },
          div2: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 3, gridColumnEnd: 4 },
          div3: { gridRowStart: 2, gridRowEnd: 3, gridColumnStart: 3, gridColumnEnd: 4 },
          div4: { gridRowStart: 3, gridRowEnd: 4, gridColumnStart: 3, gridColumnEnd: 4 },
          div5: { gridRowStart: 1, gridRowEnd: 2, gridColumnStart: 2, gridColumnEnd: 3 },
          div6: { gridRowStart: 2, gridRowEnd: 4, gridColumnStart: 1, gridColumnEnd: 3 },
        });
        break;
      default:
        setGrid(initialGrid);
    }
  };

  return (
    <div className="grid-container">
      <div
        className="grid-item"
        style={{ ...grid.div1, backgroundColor: 'red' }}
        onClick={() => handleClick('div1')}
      >
        1
      </div>
      <div
        className="grid-item"
        style={{ ...grid.div2, backgroundColor: 'blue' }}
        onClick={() => handleClick('div2')}
      >
        2
      </div>
      <div
        className="grid-item"
        style={{ ...grid.div3, backgroundColor: 'green' }}
        onClick={() => handleClick('div3')}
      >
        3
      </div>
      <div
        className="grid-item"
        style={{ ...grid.div4, backgroundColor: 'black' }}
        onClick={() => handleClick('div4')}
      >
        4
      </div>
      <div
        className="grid-item"
        style={{ ...grid.div5, backgroundColor: 'orange' }}
        onClick={() => handleClick('div5')}
      >
        5
      </div>
      <div
        className="grid-item"
        style={{ ...grid.div6, backgroundColor: 'purple' }}
        onClick={() => handleClick('div6')}
      >
        6
      </div>
    </div>
  );
};

export default App;

App.css

.grid-container {
  display: grid;
  grid-template-rows: repeat(4, 100px);
  grid-template-columns: repeat(4, 100px);
  gap: 5px;
  width: 410px;
  margin: 0 auto;
}

.grid-item {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #000;
  font-size: 2.5rem;
  color: white;
  /* 明确指定过渡的网格属性,比all更高效 */
  transition: grid-row-start 0.5s ease, grid-row-end 0.5s ease,
    grid-column-start 0.5s ease, grid-column-end 0.5s ease;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 13:29:52