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

React绝对定位元素过渡动画未触发问题求助

Hero滑块动画无法触发问题:Ref更新但DOM样式未变化

我尝试为heroTitle和heroBtn添加过渡动画,但动画无法触发。打印element.current显示样式已更新,但React渲染的当前元素样式没有变化。想要实现标题从下方滑入、按钮淡入的效果,却因样式未更新导致动画失效。

Ref.current已更新

Ref.current更新

DOM未更新

DOM未更新

React JSX代码

import { useState, useEffect, useRef } from "react";
import '../../styles/home.css'
import useSwipe from "../../hooks/useSwipe";

const HeroSlider = () => {
  const [imgIndex, setImgIndex] = useState(0);
  const intervalTim = useRef(null);
  const progressRef = useRef(null);
  const heroTitle = useRef(null);
  const heroBtn = useRef(null);
  const autoplay = false;

  const heroImgs = [
    'https://placehold.co/1920x1080?text=1',
    'https://placehold.co/1920x1080?text=2',
    'https://placehold.co/1920x1080?text=3'
  ]

  function showPrevImage() {
    setImgIndex((index) =>  index === 0 ? heroImgs.length - 1 : index - 1);
  }

  function showNextImage() {
    setImgIndex((index) => index === heroImgs.length - 1 ? 0 : index + 1);
  }

  const { handleTouchStart, handleTouchMove, handleTouchEnd } = useSwipe(showNextImage, showPrevImage);

  console.log(imgIndex)

  const resetProgressBar = () => {
    
    if (progressRef.current) {
      progressRef.current.style.transition = 'none';
      progressRef.current.style.width = '0%';
      setTimeout(() => {
        progressRef.current.style.transition = 'width 5s linear';
        progressRef.current.style.width = '105%';
      }, 25); // 延迟让浏览器重置宽度
    }
  };



// 这段代码理应生效
// 预期在渲染时执行,但样式未更新
  useEffect(() => {
    heroTitle.current.style.bottom = 0;
    heroBtn.current.style.opacity = 1;
  }, [])


 
  useEffect(() => {
    //resetProgressBar();
    console.log('interval render')
    intervalTim.current = autoplay && setInterval(() => {
      showNextImage();
      resetProgressBar();
    }, 5000);

    return () => {
      console.log('clear')
      clearInterval(intervalTim.current)
    };
  }, [imgIndex]);

  return ( 
 <section key={test}>
      <div className="hero-slider">
        <div 
          className="hero-slider__main-img-slider-container"     
          onTouchStart={handleTouchStart}
          onTouchMove={handleTouchMove}
          onTouchEnd={handleTouchEnd}
        >
          {heroImgs.map((imgUrl, i) => (
            <div 
              aria-hidden={imgIndex !== i} 
              style={{transform: `translateX(${-100 * imgIndex}%)`}} 
              key={imgUrl} 
              className="hero-slider__main-img-container"
            >
              <img 
                className="hero-slider__main-img"
                src={imgUrl} 
                alt={`img ${i + 1}`} 
              />
              <div className="hero-slider__hero-text-container">
                <div className="hero-slider__hero-text">
                  <div className="hero-slider__hero-title-container">
        
                    // HeroTitle ref
                    <h1 ref={heroTitle} className="hero-slider__hero-title">Title</h1>


                  </div>

                  // HeroButton ref
                  <button ref={heroBtn} className="hero-slider__hero-btn">shop</button>


                </div>
              </div>
            </div>
          ))}
        </div>
        <div className="hero-slider__navigation-btns-container"> 
          <ul className="hero-slider__navigation-btns-list">
            {heroImgs.map((_, i) => (
              <li key={i}>
                <button 
                  aria-label={`Show image ${i + 1}`}
                  style={i === imgIndex ? {backgroundColor: 'black'} : {backgroundColor: 'white'}}
                  className="hero-slider__navigation-btn" 
                  onClick={() => {
                    setImgIndex(i)
                  }}>
                </button>
              </li>
            ))}
          </ul>
        </div>
        <div className="hero-slider__arrow-btns-container">
          <button aria-label="Show previous image." className="hero-slider__prev-btn" onClick={showPrevImage}>left</button>
          <button aria-label="Show next image." className="hero-slider__next-btn" onClick={showNextImage}>right</button>
        </div>
        <div className="hero-slider__progress-bar-container">
          <div className="hero-slider__progress-bar" ref={progressRef}></div>
        </div>
      </div>
    </section>
  );
}
 
export default HeroSlider;

CSS代码

.hero-slider {
  width: 100%;
  height: 70vh;
  position: relative;
  margin: 0 0 50px;
  font-size: 1rem;
}

.hero-slider__main-img-slider-container {
  display: flex;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.hero-slider__main-img-container {
  flex-shrink: 0;
  flex-grow: 0;
  width: 100%;
  height: 100%;
  position: relative;
  transition: transform 0.3s ease-in-out;
}

.hero-slider__main-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}



/* 待动画元素 */

.hero-slider__hero-text-container {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  padding: 0 0 60px;
}

.hero-slider__hero-text {
  max-width: 1300px;
  margin: 0 auto;
  padding: 0 40px;
  border: 2px solid blue;
}

.hero-slider__hero-title-container {
  border: 2px solid lightseagreen;
  position: relative;
  height: 100px;
}

/* Hero标题和按钮 */

.hero-slider__hero-title {
  font-size: 4em;
  position: absolute;
  bottom: -100px;
  transition: bottom 1s;
}

.hero-slider__hero-btn {
  background-color: black;
  color: white;
  padding: 10px;
  font-size: 1.25em;
  opacity: 0;
  transition: opacity 1s;
}

/* 待动画元素结束 */



.hero-slider__arrow-btns-container {
  position: absolute;
  bottom: -20px;
  right: 40px;
  z-index: 3;
}

.hero-slider__navigation-btns-container {
  position: absolute;
  bottom: 10px;
  left: 0;
  right: 0;
  z-index: 2;
}

.hero-slider__navigation-btns-list {
  max-width: 1300px;
  margin: 0 auto;
  padding: 0 40px;
  display: flex;
  gap: 10px;
  list-style: none;
}

.hero-slider__navigation-btn {
  border: 2px solid #000;
  width: 15px;
  height: 15px;
  border-radius: 50%;
}

.hero-slider__navigation-btn:focus-visible {
  outline: 1px solid #000;
}

.hero-slider__navigation-btn:is(:hover, :focus-visible) {
  transform: scale(1.1);
}

.hero-slider__prev-btn, 
.hero-slider__next-btn {
  background-color: black;
  color: white;
  padding: 10px;
}

.hero-slider__prev-btn {
  margin-right: 10px;
}

.hero-slider__arrow-btns-container button:hover {
  background-color: red;
}

.hero-slider__progress-bar-container {
  overflow: hidden;
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  z-index: 1;
  height: 50px;
}

.hero-slider__progress-bar {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 3px;
  background-color: rgba(255, 255, 255, 0.8);
  width: 0;
  transition: width 5s linear;
  border-top-right-radius: 20px;
  border-bottom-right-radius: 20px;
}

.hero-slider__progress-bar-inital {
  transition: none;
  width: 0%;
}

.hero-slider__progress-bar-animation {
  transition: width 5s linear;
  width: 105%;
}

@media screen and (max-width: 750px) {
  .hero-slider__navigation-btns-list {
    padding: 0 20px;
  }

  .hero-slider__hero-text {
    padding: 0 20px;
  }
}

已尝试的解决方案

  • 添加延迟适配DOM渲染
useEffect(() => {
  setTimeout(() => {
    heroTitle.current.style.bottom = '0';
    heroBtn.current.style.opacity = '1';
  }, 100);
}, []);
  • 使用useState触发useEffect
const [test, setTest] = useState(0);

useEffect(() => {
  setTimeout(() => {
    heroTitle.current.style.bottom = '0';
    heroBtn.current.style.opacity = '1';
  }, 100);
}, [test]);

// 为图片容器添加点击事件测试状态逻辑
<div 
  onClick={() => {setTest((prev) => prev + 1)}}
  className="hero-slider__main-img-slider-container"     
  onTouchStart={handleTouchStart}
  onTouchMove={handleTouchMove}
  onTouchEnd={handleTouchEnd}
>
  • 设置元素初始样式
useEffect(() => {
  // 设置初始状态
  heroTitle.current.style.bottom = '-100px';
  heroBtn.current.style.opacity = '0';

  // 短延迟后设置最终状态
  setTimeout(() => {
    heroTitle.current.style.bottom = '0';
    heroBtn.current.style.opacity = '1';
  }, 100);
}, []);

内容的提问来源于stack exchange,提问作者Angel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 22:57:01