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

点击按钮实现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;
}

问题根源

  1. 同时维护animate和animateLeft两个状态,点击时会交替触发两个类的切换,导致组件先应用一个动画类,马上又切换到另一个,视觉上出现两次翻转
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 17:50:37