如何解决React图片组件加载时的跳动问题?Tailwind CSS方案求助
解决图片骨架屏与图片位置偏移导致的跳动问题
我开发了一个图片加载组件,逻辑很简单:图片加载过程中显示骨架屏,加载完成后显示图片。目前功能正常,但存在一个问题:骨架屏渲染时位置偏下,图片加载完成后骨架屏消失,图片会向上跳动。想请教如何用Tailwind让图片精确渲染在骨架屏的位置(相同宽高与位置),消除跳动现象。
原组件代码
import { useEffect, useState } from "react"; import Footer from "~/components/footer/footer"; import Header from "~/components/header/header"; export default function ErrorBoundary() { const [loading, setLoading] = useState(true); const [randomNumber, setRandomNumber] = useState<number>(); useEffect(() => { if (!randomNumber) { setRandomNumber(Math.floor((Math.random() * 100) + 1)) } }, [randomNumber]) return ( <> <Header /> <div className="flex flex-col items-center justify-between text-center h-80 w-80"> <div className=""> { randomNumber && <img className="h-40 w-40" src={`https://robohash.org/${randomNumber}?set=set4`} alt='catLoader' onLoad={() => { setLoading(false) }} /> } {loading && <div role="status" className="animate-pulse md:space-y-0 md:space-x-8 md:flex md:items-center"> <div className="flex items-center justify-center w-40 h-40 bg-gray-300 rounded sm:w-96 dark:bg-gray-700"> <svg className="w-40 h-40 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18"> <path d="M18 0H2a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2Zm-5.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm4.376 10.481A1 1 0 0 1 16 15H4a1 1 0 0 1-.895-1.447l3.5-7A1 1 0 0 1 7.468 6a.965.965 0 0 1 .9.5l2.775 4.757 1.546-1.887a1 1 0 0 1 1.618.1l2.541 4a1 1 0 0 1 .028 1.011Z" /> </svg> </div> <span className="sr-only">Fetching a cat picture...</span> </div> } </div> <h1 className='text-7xl text-secondary-300 align-bottom'>Coming Soon!</h1> </div> <Footer /> </> ) }
核心问题分析
跳动的本质是骨架屏与图片的布局上下文不一致:两者属于互斥的条件渲染DOM元素,且骨架屏外层带有md:flex、md:items-center等额外样式,导致容器的垂直位置、尺寸在切换时发生变化,触发浏览器重排重绘。
修复方案与修改后代码
import { useEffect, useState } from "react"; import Footer from "~/components/footer/footer"; import Header from "~/components/header/header"; export default function ErrorBoundary() { const [loading, setLoading] = useState(true); const [randomNumber, setRandomNumber] = useState<number>(); useEffect(() => { if (!randomNumber) { setRandomNumber(Math.floor((Math.random() * 100) + 1)) } }, [randomNumber]) return ( <> <Header /> <div className="flex flex-col items-center justify-between text-center h-80 w-80"> {/* 统一的父容器:固定宽高+相对定位,作为图片和骨架屏的基准 */} <div className="relative w-40 h-40"> {/* 图片始终存在,通过hidden/block控制显示,避免DOM移除导致布局变化 */} {randomNumber && ( <img className={`w-full h-full object-contain ${loading ? 'hidden' : 'block'}`} src={`https://robohash.org/${randomNumber}?set=set4`} alt='catLoader' onLoad={() => setLoading(false)} /> )} {/* 骨架屏绝对定位,完全覆盖父容器,和图片位置完全重合 */} {loading && ( <div role="status" className="absolute inset-0 animate-pulse flex items-center justify-center bg-gray-300 rounded dark:bg-gray-700" > <svg className="w-40 h-40 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18"> <path d="M18 0H2a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2Zm-5.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm4.376 10.481A1 1 0 0 1 16 15H4a1 1 0 0 1-.895-1.447l3.5-7A1 1 0 0 1 7.468 6a.965.965 0 0 1 .9.5l2.775 4.757 1.546-1.887a1 1 0 0 1 1.618.1l2.541 4a1 1 0 0 1 .028 1.011Z" /> </svg> <span className="sr-only">Fetching a cat picture...</span> </div> )} </div> <h1 className='text-7xl text-secondary-300 align-bottom'>Coming Soon!</h1> </div> <Footer /> </> ) }
关键修改点说明
- 统一布局容器:给图片和骨架屏添加共同的父容器,设置
relative w-40 h-40,确保两者使用完全相同的尺寸基准。 - 绝对定位重叠:骨架屏用
absolute inset-0实现完全覆盖父容器,与图片位置100%重合。 - 避免DOM切换:图片通过
hidden/block控制显示而非移除DOM,消除容器结构变化带来的布局波动。 - 清理冗余样式:移除骨架屏原有的
md:flex、md:space-x-8等干扰布局的样式,保持样式一致性。
内容的提问来源于stack exchange,提问作者Loqgar
相关产品推荐
相关产品推荐

