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

Framer Motion实现Y轴滚动触发X轴滚动的问题排查

解决方案:视口内触发水平滚动并锁定页面垂直滚动

核心思路

要实现目标交互,需完成三个关键动作:

  1. 检测目标section是否进入视口
  2. 当section在视口内时,锁定页面垂直滚动,将页面滚动事件映射为section内部的水平滚动
  3. 水平滚动完成后,解锁页面垂直滚动,允许继续向下滚动

修改后的代码实现

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 04:25:54