React中如何拆分文字并基于条件实现元素位置属性动画
问题描述
我正在尝试使用React实现字母汤文字散落效果,目前遇到两个问题:
- 已经实现了部分CSS动画属性,但拆分后的单个字符无法正常渲染,没法在组件内展示拆分效果
- 尝试用useState存储字符拆分结果时,触发了组件无限重渲染的问题
当前组件文件路径:/src/components/SlideCard.jsx,现有代码如下:
import React, {useState} from "react"; export default function SlideCard(props) { //const [ split, setSplitted ] = useState([]); //console.log(split); const { id, idx, title } = props; const inputText = title; const scatter = { position: "absolute", top: "", left: "", zIndex: "initial", transition: "left 2s, top 2s" }; if (id !== idx) { //split if not active// inputText.split("").map((letter) => { var randLeft = Math.floor(Math.random() * 400); var randTop = Math.floor(Math.random() * 200); scatter.top = +randTop + "px"; scatter.left = +randLeft + "px"; console.log(letter); //How can I use this split inside the main component return letter; //return setSplitted(letter); // I tried setting it to a useState, but don't know why it is giving me an infinite loop. }); } else { scatter.top = "50%"; scatter.left = "50%"; } //main component return <div style={scatter}>{inputText}</div>; }
问题根因
- 无限循环问题:在组件渲染的执行流里直接调用state更新方法
setSplitted,state变更会触发组件重新渲染,重渲染时又会再次执行到setSplitted,反复触发就形成了死循环。 - 字符无法渲染问题:虽然调用了
split("").map()处理字符,但没有把map返回的结果放到JSX中渲染;同时反复修改同一个scatter样式对象,就算渲染了字符,所有字符也会共用最后一次生成的随机坐标,无法实现每个字符独立散落的效果。 - 额外问题:随机坐标写在渲染主流程里,每次组件重渲染都会重新生成随机值,会导致字符位置不停跳变,动画效果完全失效。
修复实现
正确的实现逻辑是:
- 用
useMemo在文字内容变化时一次性生成所有字符的随机散落坐标,避免每次重渲染都重新计算坐标,也不需要额外的state存储 - 每个拆分后的字符单独包裹一层标签,绑定属于自己的独立样式
- 激活状态(id === idx)下,所有字符回到统一位置拼成完整文字,非激活状态下使用预生成的随机坐标散落
修正后的代码:
import React, { useMemo } from "react"; export default function SlideCard(props) { const { id, idx, title } = props; const isActive = id === idx; // 仅当title变化时,一次性生成每个字符的随机散落坐标 const letterPositions = useMemo(() => { return title.split("").map(() => ({ randLeft: Math.floor(Math.random() * 400), randTop: Math.floor(Math.random() * 200) })) }, [title]); // 容器基础样式 const containerStyle = { position: "relative", width: "400px", height: "200px" }; // 单字符基础样式 const baseLetterStyle = { position: "absolute", transition: "left 2s, top 2s, transform 2s", transform: isActive ? "translate(-50%, -50%)" : "none", zIndex: isActive ? 10 : "initial" }; return ( <div style={containerStyle}> {title.split("").map((letter, index) => { const pos = letterPositions[index]; // 根据激活状态切换字符位置 const letterStyle = isActive ? { ...baseLetterStyle, top: "50%", left: "50%" } : { ...baseLetterStyle, top: `${pos.randTop}px`, left: `${pos.randLeft}px` }; return <span key={index} style={letterStyle}>{letter}</span> })} </div> ); }
如果需要调整字符拼成完整文字时的字间距,可以在激活状态下给每个字符加动态的left偏移,按字符索引乘以单字宽度即可实现正常排版。
内容的提问来源于stack exchange,提问作者Bhushan
相关产品推荐
相关产品推荐

