如何在滚动时多次切换HTML :root类并添加过渡与特效
解决React TS滚动切换背景及特效问题
问题说明
我正在用React TypeScript做一个小项目,作为新手遇到两个核心问题:
- 无法实现滚动时多次切换背景色并添加过渡效果;
- 希望为不同背景色添加特效,比如从纯蓝色背景滚动过特定阈值后,切换为粉色背景并添加花瓣飘落效果。
解决方案
一、实现滚动多次切换背景色+过渡效果
原来的代码直接操作DOM修改样式,不符合React状态管理逻辑,也无法触发组件重渲染实现多次切换。改用React状态+滚动监听来实现:
修改后的App.tsx
import "./App.css"; import { useEffect, useState } from "react"; import PetalEffect from "./PetalEffect"; // 定义滚动阈值与对应背景色的映射 interface BackgroundConfig { threshold: number; color: string; } function App() { const [currentBg, setCurrentBg] = useState<BackgroundConfig>({ threshold: 0, color: "#1646A4" // 初始蓝色 }); // 定义所有背景配置 const bgConfigs: BackgroundConfig[] = [ { threshold: 0, color: "#1646A4" }, { threshold: 522, color: "#FF69B4" }, // 粉色 { threshold: 1500, color: "#000" } // 黑色 ]; useEffect(() => { const handleScroll = () => { const scrollY = window.scrollY; // 从后往前找第一个满足阈值的配置 const matchedConfig = [...bgConfigs].reverse().find(config => scrollY >= config.threshold); if (matchedConfig && matchedConfig.color !== currentBg.color) { setCurrentBg(matchedConfig); } }; // 添加滚动监听 window.addEventListener("scroll", handleScroll); // 初始化执行一次 handleScroll(); // 清理监听 return () => window.removeEventListener("scroll", handleScroll); }, [currentBg.color, bgConfigs]); // 给根元素设置当前背景色 useEffect(() => { document.documentElement.style.backgroundColor = currentBg.color; }, [currentBg.color]); return ( <div> {/* 根据当前背景渲染特效 */} {currentBg.color === "#FF69B4" && <PetalEffect />} <nav className="navbar"> <div className="menus"> <h3>Home</h3> <h3>First</h3> <h3>Second</h3> <h3>Third</h3> </div> </nav> <div className="intro"> <p>WELCOME!</p> </div> <div className="about"> <p className="summary"> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehender it in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p> </div> <div className="poem1"> <p className="summary"> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehender it in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p> </div> </div> ); } export default App;
修改后的App.css(添加过渡效果)
body { animation: slide 1.5s ease; } @keyframes slide { from { transform: translateX(-100vw); opacity: 0; } to { transform: translateX(0vw); opacity: 1; } } :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 800; color-scheme: light dark; background-color: #1646A4; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; scroll-behavior: smooth; /* 添加背景色过渡效果 */ transition: background-color 0.5s ease-in-out; } /* 其他原有样式保持不变 */ .navbar { position: sticky; width: 120px; top: 20vh; left: 2vw; height: 300px; background-color: #145DA0; border-radius: 20px; } .menus { display: flex; flex-direction: column; justify-content: space-evenly; font-size: 18px; font-style: normal; line-height: normal; text-align: center; color: #052844; } .menus h3 { position: relative; } .menus h3::after { content: ""; position: absolute; background-color: aliceblue; height: 3px; width: 0; left: 50%; transform: translateX(-50%); bottom: -10px; transition: width 0.1s ease-in-out; transform-origin: center; } .menus h3:hover::after { width: 60%; } .intro { position: relative; font-size: 120px; text-align: center; top: -30vw; } .about { display: flex; justify-content: center; align-items: center; position: relative; width: 700px; height: 300px; top: -72vh; background-color: rgba(55, 190, 176, 0.6); border-radius: 25px; box-shadow: 2px 0px 0px 2px rgba(0, 0, 0, 0.25) inset; margin-inline: auto; margin-bottom: 500px; } .summary { width: 500px; height: 200px; opacity: 1; white-space: pre-line; color: #86d7cf; } .poem1{ display: flex; justify-content: center; align-items: center; width: 700px; height: 300px; background-color: rgba(55, 190, 176, 0.6); border-radius: 25px; box-shadow: 2px 0px 0px 2px rgba(0, 0, 0, 0.25) inset; margin-inline: auto; margin-bottom: 1000px; } /* 花瓣特效样式 */ .petal-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10; } .petal { position: absolute; width: 20px; height: 20px; background-color: #ffb6c1; border-radius: 50% 0 50% 0; animation: fall linear infinite; } @keyframes fall { 0% { transform: translateY(-10vh) rotate(0deg); opacity: 1; } 100% { transform: translateY(110vh) rotate(360deg); opacity: 0; } }
二、实现花瓣飘落特效
创建PetalEffect.tsx组件,生成随机飘落的花瓣:
import { useEffect, useState } from "react"; const PetalEffect = () => { const [petals, setPetals] = useState<Array<{id: number, left: number, delay: number, duration: number}>>([]); useEffect(() => { // 生成15个花瓣 const newPetals = Array.from({length: 15}, (_, i) => ({ id: i, left: Math.random() * 100, // 随机水平位置 delay: Math.random() * 5, // 随机延迟 duration: 5 + Math.random() * 5 // 随机下落时长 })); setPetals(newPetals); // 组件卸载时清空花瓣 return () => setPetals([]); }, []); return ( <div className="petal-container"> {petals.map(petal => ( <div key={petal.id} className="petal" style={{ left: `${petal.left}%`, animationDelay: `${petal.delay}s`, animationDuration: `${petal.duration}s` }} /> ))} </div> ); }; export default PetalEffect;
关键说明
- 滚动监听与状态管理:用
useEffect添加滚动监听,通过状态currentBg控制背景色,确保React正确重渲染; - 过渡效果:给
:root添加transition: background-color 0.5s ease-in-out,实现背景色切换的平滑过渡; - 特效触发:根据当前背景色状态,条件渲染
PetalEffect组件,仅在粉色背景时显示花瓣; - 阈值配置:通过
bgConfigs数组可灵活添加更多滚动阈值和对应背景色,扩展方便。
内容的提问来源于stack exchange,提问作者Alex Attah
相关产品推荐
相关产品推荐

