Next.js 13表单切换页面时重复触发标签动画的解决方法
解决Next.js 13认证表单标签重复动画问题
针对你遇到的页面切换后返回表单,输入框重新聚焦触发标签重复动画的问题,核心原因是页面切换时组件卸载导致状态丢失,返回后重新挂载的组件会把输入框的聚焦事件当作首次交互处理。以下是两种可行的解决方案:
方案一:持久化交互状态与输入值
通过sessionStorage保存输入框的内容和是否被用户触摸过的状态,页面切换后再返回时,组件会读取之前的状态,避免重复触发动画。
示例组件代码:
'use client'; import { useState, useEffect } from 'react'; export default function AuthInput({ name, label, type = 'text' }) { // 从sessionStorage初始化状态,避免页面切换后丢失 const [value, setValue] = useState(() => { return sessionStorage.getItem(`auth_form_${name}`) || ''; }); const [isTouched, setIsTouched] = useState(() => { return sessionStorage.getItem(`auth_form_${name}_touched`) === 'true'; }); // 实时同步状态到sessionStorage useEffect(() => { sessionStorage.setItem(`auth_form_${name}`, value); }, [name, value]); useEffect(() => { sessionStorage.setItem(`auth_form_${name}_touched`, isTouched.toString()); }, [name, isTouched]); const handleFocus = () => { // 仅在首次交互时标记为已触摸 if (!isTouched) setIsTouched(true); }; const handleChange = (e) => { setValue(e.target.value); }; // 标签激活条件:已触摸过 或 输入框有内容 const isLabelActive = isTouched || value.length > 0; return ( <div className="relative mb-5"> <input type={type} name={name} value={value} onChange={handleChange} onFocus={handleFocus} className={`w-full px-4 py-2 border border-gray-300 rounded-lg outline-none transition-all duration-300 ${ isLabelActive ? 'pt-6' : 'pt-2' }`} /> <label className={`absolute left-4 top-2 text-gray-500 transition-all duration-300 ${ isLabelActive ? 'top-0 text-xs font-medium' : 'text-base' }`} > {label} </label> </div> ); }
逻辑说明:
isTouched状态一旦被设为true就不会再变回false,确保用户交互过的输入框标签始终保持激活状态- 用
sessionStorage持久化状态,页面切换后组件重新挂载时能读取到之前的交互记录,不会把返回后的聚焦当作首次操作
方案二:调整标签状态判断逻辑
不依赖focus事件直接触发动画,而是通过输入框内容和交互历史控制标签状态,同时利用useRef避免首次挂载外的重复触发。
示例组件代码:
'use client'; import { useState, useRef, useEffect } from 'react'; export default function AuthInput({ name, label, type = 'text' }) { const [value, setValue] = useState(''); const [isLabelActive, setIsLabelActive] = useState(false); const firstMount = useRef(true); useEffect(() => { // 首次挂载时根据输入内容初始化标签状态 setIsLabelActive(value.length > 0); firstMount.current = false; }, []); const handleFocus = () => { // 仅首次聚焦时触发动画 if (!firstMount.current) return; setIsLabelActive(true); }; const handleBlur = () => { // 失焦时仅当输入为空才恢复标签状态 if (value.length === 0) setIsLabelActive(false); }; const handleChange = (e) => { setValue(e.target.value); // 输入内容变化时同步更新标签状态 setIsLabelActive(e.target.value.length > 0); }; return ( <div className="relative mb-5"> <input type={type} name={name} value={value} onChange={handleChange} onFocus={handleFocus} onBlur={handleBlur} className={`w-full px-4 py-2 border border-gray-300 rounded-lg outline-none transition-all duration-300 ${ isLabelActive ? 'pt-6' : 'pt-2' }`} /> <label className={`absolute left-4 top-2 text-gray-500 transition-all duration-300 ${ isLabelActive ? 'top-0 text-xs font-medium' : 'text-base' }`} > {label} </label> </div> ); }
逻辑说明:
- 用
firstMount标记组件是否为首次挂载,后续页面返回时的重新挂载会跳过聚焦触发的动画逻辑 - 标签状态由输入内容和首次交互共同控制,避免单纯依赖
focus事件导致的重复动画
内容的提问来源于stack exchange,提问作者Moataz Ghazy
相关产品推荐
相关产品推荐

