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

ReactJS中如何实现基于循环数据的UI逐帧更新?

实现React元素的分步移动动画

问题描述

我在UI中创建了网格,希望展示数组给定所有点位的完整移动流程,但UI直接更新为循环的最后一个值。例如position = [1, 2, 3],期望圆圈从1移动到2再到3,而非直接从1跳到3。以下是我的Game组件代码:

import React, { useEffect, useState } from 'react';
import { Button } from 'reactstrap';
import { motion } from "framer-motion";
import './style.css';
import Grid from './grid'
class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            idOfBegin: "1",
            position: 0,
            xAxis: 0,
            yAxis: 0,
            position: [],
            arrOfObj: [100, 99, 98, 97, 96, 95, 94, 93, 92, 91,
                81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
                80, 79, 78, 77, 76, 75, 74, 73, 72, 71,
                61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
                60, 59, 58, 57, 56, 55, 54, 53, 52, 51,
                41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
                40, 39, 38, 37, 36, 35, 34, 33, 32, 31,
                21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
                20, 19, 18, 17, 16, 15, 14, 13, 12, 11,
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10
            ]
        };
    }

    start = () => {
        var a = document.getElementById("3");
        var codinates = a.getBoundingClientRect();
        var x = codinates.left + 35;
        var y = codinates.top + 5;
        console.log(x, y);
        this.setState({ xAxis: x });
        this.setState({ yAxis: y });
    }
    loadPosition = async (event) => {
        fetch('api/groups')
            .then(response => response.json())
            .then(data => {
                //console.log('======== ' +data);
                //groups.map(group => setPosition(group));
                this.setState({ position: data });
            });
    }
    getCellValue = async (event) => {
        this.loadPosition();
        this.state.position.forEach(p => {
            console.log('====== ' + p);
            if (p != null) {
                var a = document.getElementById(p);
                var codinates = a.getBoundingClientRect();
                var x = codinates.left + 35;
                var y = codinates.top + 5;
                //console.log(x,y);
                this.setState({ xAxis: x });
                this.setState({ yAxis: y });
            }
        });
    }
    componentDidMount() {
        this.setState({ xAxis: 45 });
        this.setState({ yAxis: 905 });
    }
 

    render() {
        
                    return (
                        <div>
                            <div className="grid"><Grid arrOfObj={this.state.arrOfObj} />
                                <motion.div animate={{ x: this.state.xAxis, y: this.state.yAxis }}
                                    transition={{ type: "tween", duration: 1 }}
                                ></motion.div>
                            
                            </div>
                            <div className="control">
                                <Button color="primary" onClick={() => this.start()}>Start</Button>
                                <Button color="primary" onClick={() => this.getCellValue()}>Roll Dice</Button>
                            </div>
                        </div>
            
                    );
       
    }
}
export default Game;

问题原因

  • setState是异步批量更新机制,循环中连续调用会直接覆盖之前的状态,最终只应用最后一次更新结果。
  • 未等待前一个动画完成就触发下一次状态更新,导致动画被中断,直接跳到最终位置。

解决方案

修改代码,确保逐个处理位置并等待每个动画完成:

import React from 'react';
import { Button } from 'reactstrap';
import { motion } from "framer-motion";
import './style.css';
import Grid from './grid'

class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            idOfBegin: "1",
            xAxis: 45,
            yAxis: 905,
            position: [],
            arrOfObj: [100, 99, 98, 97, 96, 95, 94, 93, 92, 91,
                81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
                80, 79, 78, 77, 76, 75, 74, 73, 72, 71,
                61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
                60, 59, 58, 57, 56, 55, 54, 53, 52, 51,
                41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
                40, 39, 38, 37, 36, 35, 34, 33, 32, 31,
                21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
                20, 19, 18, 17, 16, 15, 14, 13, 12, 11,
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10
            ]
        };
        this.motionRef = React.createRef();
    }

    start = () => {
        const a = document.getElementById("3");
        const codinates = a.getBoundingClientRect();
        const x = codinates.left + 35;
        const y = codinates.top + 5;
        this.setState({ xAxis: x, yAxis: y });
    }

    loadPosition = () => {
        return fetch('api/groups')
            .then(response => response.json())
            .then(data => {
                this.setState({ position: data });
                return data;
            });
    }

    getCellValue = async () => {
        const positions = await this.loadPosition();
        if (!positions || positions.length === 0) return;

        for (const p of positions) {
            if (!p) continue;
            const a = document.getElementById(p);
            if (!a) continue;
            const codinates = a.getBoundingClientRect();
            const x = codinates.left + 35;
            const y = codinates.top + 5;

            this.setState({ xAxis: x, yAxis: y });
            await new Promise(resolve => setTimeout(resolve, 1100));
        }
    }

    render() {
        return (
            <div>
                <div className="grid">
                    <Grid arrOfObj={this.state.arrOfObj} />
                    <motion.div
                        ref={this.motionRef}
                        animate={{ x: this.state.xAxis, y: this.state.yAxis }}
                        transition={{ type: "tween", duration: 1 }}
                    ></motion.div>
                </div>
                <div className="control">
                    <Button color="primary" onClick={this.start}>Start</Button>
                    <Button color="primary" onClick={this.getCellValue}>Roll Dice</Button>
                </div>
            </div>
        );
    }
}

export default Game;

关键修改点

  • 移除state中重复的position定义,初始化时直接设置xAxis和yAxis,减少不必要的setState调用。
  • 将loadPosition改为返回Promise,确保数据加载完成后再处理移动逻辑。
  • getCellValue改为异步函数,使用for...of循环逐个遍历位置,每次更新状态后等待1.1秒(确保1秒的动画完全结束)。
  • 合并start方法中的两次setState为一次,优化性能。

内容的提问来源于stack exchange,提问作者chk.buddi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 23:06:32