Framer Motion实现Y轴滚动触发X轴滚动的问题排查
解决方案:视口内触发水平滚动并锁定页面垂直滚动
核心思路
要实现目标交互,需完成三个关键动作:
- 检测目标section是否进入视口
- 当section在视口内时,锁定页面垂直滚动,将页面滚动事件映射为section内部的水平滚动
- 水平滚动完成后,解锁页面垂直滚动,允许继续向下滚动
修改后的代码实现
import { useRef, useEffect, useState } from 'react'; import { motion, useScroll, useTransform, useInView } from 'framer-motion'; export default function TechStack({ darkMode }: TechStackProps) { const targetRef = useRef<HTMLElement>(null); // 监听section是否进入视口(触发阈值为50%可见) const isInView = useInView(targetRef, { once: false, amount: 0.5 }); const { scrollY } = useScroll(); // 存储section对应的页面滚动区间:从开始影响滚动到结束的scrollY范围 const [sectionScrollRange, setSectionScrollRange] = useState<[number, number]>([0, 0]); // 计算section的滚动区间 useEffect(() => { if (!targetRef.current) return; const { top, height } = targetRef.current.getBoundingClientRect(); const windowHeight = window.innerHeight; // 起始点:section顶部进入视口一半时的scrollY值 const startScrollY = window.scrollY + top - windowHeight * 0.5; // 结束点:section底部完全离开视口时的scrollY值 const endScrollY = startScrollY + height + windowHeight; setSectionScrollRange([startScrollY, endScrollY]); }, []); // 将section区间内的垂直滚动映射为水平位移 const translateElement = useTransform( scrollY, sectionScrollRange, ["0%", "-57%"], { clamp: true } // 限制位移在指定范围内 ); // 处理页面滚动锁定逻辑 useEffect(() => { if (!isInView) return; const handleWheel = (e: WheelEvent) => { const currentScrollY = window.scrollY; const [start, end] = sectionScrollRange; // 当处于section滚动区间内时,阻止默认垂直滚动 if (currentScrollY >= start && currentScrollY <= end) { e.preventDefault(); // 根据滚轮方向调整页面滚动位置,间接驱动水平滚动 const scrollStep = (e.deltaY / window.innerHeight) * (end - start); window.scrollTo({ top: Math.max(start, Math.min(end, currentScrollY + scrollStep)) }); } }; window.addEventListener('wheel', handleWheel, { passive: false }); return () => window.removeEventListener('wheel', handleWheel); }, [isInView, sectionScrollRange]); return ( <section ref={targetRef}> <div className={`relative ${darkMode ? "dark" : "light"} flex flex-col items-center justify-center lg:items-start pt-[30%] md:pt-40 pb-20 md:w-full lg:h-screen lg:pb-40 `} style={{ zIndex: 20 }} > <div className="max-w-5xl p-4 z-20 lg:pl-[4rem] sticky top-20"> <h1 className="font-black text-6xl px-4 py-4"> DESIGN. <br /> DEVELOP. <br /> TEST. </h1> <p className="font-light text-2xl leading-1 p-4"> Building a good digital experience requires an even better tool-set of technologies. These are some of the technologies I have worked with recently. </p> </div> {/* 仅在section进入视口时应用水平滚动 */} <motion.div style={{ x: isInView ? translateElement : 0 }} className="translate-this-div-on-scroll" > <div className="tech-stack w-full flex flex-nowrap px-[2rem] md:px-[5rem] gap-8 py-8 z-20 overflow-hidden"> <TechContainer darkMode={darkMode} technologyCategory="FRONT-END DEVELOPMENT" technologyData={frontEndTechnologies} /> <TechContainer darkMode={darkMode} technologyCategory="BACK-END DEVELOPMENT" technologyData={backEndTechnologies} /> <TechContainer darkMode={darkMode} technologyCategory="DATABASE MANAGEMENT" technologyData={databaseTechnologies} /> <TechContainer darkMode={darkMode} technologyCategory="SERVERS" technologyData={serverTechnologies} /> <TechContainer darkMode={darkMode} technologyCategory="VERSION CONTROL" technologyData={versionControlTechnologies} /> <TechContainer darkMode={darkMode} technologyCategory="DESIGN" technologyData={designTechnologies} /> <TechContainer darkMode={darkMode} technologyCategory="IDE" technologyData={IDE} /> <TechContainer darkMode={darkMode} technologyCategory="OTHER TECHNOLOGIES" technologyData={otherTechnologies} /> </div> </motion.div> </div> </section> ); }
关键修改说明
- 视口检测:用
useInView监听section可见状态,仅在视口内时激活水平滚动 - 滚动区间计算:通过
getBoundingClientRect精准计算section对应的页面滚动范围,确保水平滚动只在该区间内触发 - 滚动锁定:监听滚轮事件,在section滚动区间内阻止默认垂直滚动,将滚轮动作映射为水平滚动的进度
- 滚动映射优化:将全局滚动进度改为基于section区间的垂直滚动值映射,让水平滚动的触发时机完全匹配section在视口内的阶段
内容的提问来源于stack exchange,提问作者Satanshu Mishra
相关产品推荐
相关产品推荐

