点击按钮实现React登录组件单次翻转与语言切换问题求助
问题:登录组件点击切换语言时触发两次翻转,需实现单次翻转效果
点击切换语言按钮时,期望登录组件完成单次翻转动画,但当前实现会出现两次翻转(最终位置正确,但动画重复)。
用户原代码
React组件代码
/* eslint-disable jsx-a11y/anchor-is-valid */ import {useEffect,useState} from 'react' import {Outlet, Link} from 'react-router-dom' import {toAbsoluteUrl} from '../../../_metronic/helpers' import "./authLayout.css" import data from "./ar.json" import { useLang,setLanguage } from './../../../_metronic/i18n/Metronici18n'; type FlexDirection = 'ltr' | 'rtl'; const directionMap: { [key: string]: FlexDirection } = { left: 'ltr', right: 'rtl' }; const AuthLayout = () => { const lang = useLang() const [animate, setAnimate] = useState(false); const [animateLeft, setAnimateLeft] = useState(false); const [direction, setDirection] = useState("left") const [language, setLanguage] = useState('en'); const languageTranslations = { en: { username: 'username', password: 'password', signin : "Sign In", continue:"CONTINUE" }, ar: { username: 'اسم االمستخدم', password: 'كلمه السر', signin :"تسجيل الدخول", continue:"استمر" }, }; const text = languageTranslations[language]; useEffect(() => { const root = document.getElementById('root') if (root) { root.style.height = '100%' } return () => { if (root) { root.style.height = 'auto' } } }, []) const handleArabicDirection = () => { setAnimate(!animate) if(direction === "left") { setAnimateLeft(!animateLeft) setLanguage('ar') setDirection("right") } else if(direction === "right"){ setAnimateLeft(!animateLeft) } } const handleEnglishDirection = () => { if(direction === "left"){ setAnimateLeft(!animateLeft) } else if(direction === "right"){ setAnimateLeft(!animateLeft) setDirection( "left" ) setLanguage('en') } setAnimate(!animate) } useEffect(() => { localStorage.setItem('text', JSON.stringify(text)); }, [text]); return ( <div> {/* begin::Body */} {/* begin::Form */} {/* begin::Wrapper */} <div className={`w-lg-500px p-10 box ${animate ? 'animateRight' : animateLeft ? 'animateLeft' : ""}`} style={{ direction: directionMap[direction] }}> <button onClick={handleEnglishDirection}>English</button> <button onClick={handleArabicDirection}>Arabic</button> </div> {/* end::Wrapper */} {/* end::Form */} {/* begin::Footer */} {/* begin::Links */} <a href='#' className='px-5' target='_blank'>Terms</a> <a href='#' className='px-5' target='_blank'>Plans</a> <a href='#' className='px-5' target='_blank'>Contact Us</a> {/* end::Links */} {/* end::Footer */} {/* end::Body */} {/* begin::Aside */} <div className='d-flex flex-lg-row-fluid w-lg-50 bgi-size-cover bgi-position-center order-1 order-lg-2' style={{backgroundImage: `url(${toAbsoluteUrl('/media/misc/auth-bg.png')})`}}> {/* begin::Content */} {/* begin::Logo */} <img alt='Logo' src={toAbsoluteUrl('/media/logos/custom-1.png')} className='h-75px' /> {/* end::Logo */} {/* begin::Image */} <img className='mx-auto w-275px w-md-50 w-xl-500px mb-10 mb-lg-20' src={toAbsoluteUrl('/media/misc/auth-screens.png')} alt='' /> {/* end::Image */} {/* begin::Title */} <h2>Fast, Efficient and Productive</h2> {/* end::Title */} {/* begin::Text */} <p>In this kind of post, the blogger introduces a person they’ve interviewed <br /> and provides some background information about the interviewee and their <br /> work following this is a transcript of the interview.</p> {/* end::Text */} {/* end::Content */} {/* end::Aside */} </div> ) } export {AuthLayout}
CSS代码
.box { transform: rotateY(0deg); transition: transform 7s; } .animateRight { transform: rotateY(360deg) !important; } .animateLeft { transform: rotateY(-360deg) !important; }
问题根源
- 同时维护
animate和animateLeft两个状态,点击时会交替触发两个类的切换,导致组件先应用一个动画类,马上又切换到另一个,视觉上出现两次翻转 - CSS中
rotateY(360deg)和rotateY(-360deg)本质是回到初始位置,切换类时会触发两次过渡动画
修复方案
1. 简化状态管理
用单一状态控制动画方向,避免多状态冲突;动画结束后重置状态,确保下次点击能重新触发。
2. 调整CSS动画逻辑
改用rotateY(180deg)实现半翻转,视觉上是完整的翻转效果,且不会因为360度回到初始位置导致重复过渡。
3. 修改事件处理函数
统一语言切换和动画触发逻辑,只在需要切换语言时触发动画,避免无意义的动画触发。
修改后的代码
React组件代码
/* eslint-disable jsx-a11y/anchor-is-valid */ import { useEffect, useState } from 'react' import { Outlet, Link } from 'react-router-dom' import { toAbsoluteUrl } from '../../../_metronic/helpers' import "./authLayout.css" import data from "./ar.json" import { useLang, setLanguage } from './../../../_metronic/i18n/Metronici18n'; type FlexDirection = 'ltr' | 'rtl'; const directionMap: { [key: string]: FlexDirection } = { left: 'ltr', right: 'rtl' }; const AuthLayout = () => { const lang = useLang() // 用单一状态控制动画方向:none/right/left const [animationDirection, setAnimationDirection] = useState<'none' | 'right' | 'left'>('none'); const [direction, setDirection] = useState("left") const [language, setLanguage] = useState('en'); const languageTranslations = { en: { username: 'username', password: 'password', signin: "Sign In", continue: "CONTINUE" }, ar: { username: 'اسم االمستخدم', password: 'كلمه السر', signin: "تسجيل الدخول", continue: "استمر" }, }; const text = languageTranslations[language]; useEffect(() => { const root = document.getElementById('root') if (root) root.style.height = '100%' return () => { if (root) root.style.height = 'auto' } }, []) // 动画结束后重置状态,以便下次点击能再次触发 const handleAnimationEnd = () => { setAnimationDirection('none'); } const handleArabicDirection = () => { if (direction === "left") { setAnimationDirection('right'); setLanguage('ar'); setDirection("right"); } } const handleEnglishDirection = () => { if (direction === "right") { setAnimationDirection('left'); setLanguage('en'); setDirection("left"); } } useEffect(() => { localStorage.setItem('text', JSON.stringify(text)); }, [text]); return ( <div> {/* begin::Body */} {/* begin::Form */} {/* begin::Wrapper */} <div className={`w-lg-500px p-10 box ${animationDirection === 'right' ? 'animateRight' : animationDirection === 'left' ? 'animateLeft' : ""}`} style={{ direction: directionMap[direction] }} onAnimationEnd={handleAnimationEnd} > <button onClick={handleEnglishDirection}>English</button> <button onClick={handleArabicDirection}>Arabic</button> </div> {/* end::Wrapper */} {/* end::Form */} {/* begin::Footer */} {/* begin::Links */} <a href='#' className='px-5' target='_blank'>Terms</a> <a href='#' className='px-5' target='_blank'>Plans</a> <a href='#' className='px-5' target='_blank'>Contact Us</a> {/* end::Links */} {/* end::Footer */} {/* end::Body */} {/* begin::Aside */} <div className='d-flex flex-lg-row-fluid w-lg-50 bgi-size-cover bgi-position-center order-1 order-lg-2' style={{ backgroundImage: `url(${toAbsoluteUrl('/media/misc/auth-bg.png')})` }}> {/* begin::Content */} {/* begin::Logo */} <img alt='Logo' src={toAbsoluteUrl('/media/logos/custom-1.png')} className='h-75px' /> {/* end::Logo */} {/* begin::Image */} <img className='mx-auto w-275px w-md-50 w-xl-500px mb-10 mb-lg-20' src={toAbsoluteUrl('/media/misc/auth-screens.png')} alt='' /> {/* end::Image */} {/* begin::Title */} <h2>Fast, Efficient and Productive</h2> {/* end::Title */} {/* begin::Text */} <p>In this kind of post, the blogger introduces a person they’ve interviewed <br /> and provides some background information about the interviewee and their <br /> work following this is a transcript of the interview.</p> {/* end::Text */} {/* end::Content */} {/* end::Aside */} </div> </div> ) } export { AuthLayout }
CSS代码
.box { transform: rotateY(0deg); transition: transform 0.7s ease; /* 缩短动画时间更合理 */ } .animateRight { transform: rotateY(180deg); } .animateLeft { transform: rotateY(-180deg); }
内容的提问来源于stack exchange,提问作者Zahid Hussain
相关产品推荐
相关产品推荐

