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

使用react-locomotive-scroll时@react-three/fiber无法渲染的问题

问题描述
  • 集成react-locomotive-scroll后,@react-three/fiber组件无法正常渲染;移除locomotive-scroll后组件显示恢复正常
  • 附带滚动条重复显示的问题
  • 该冲突在多个@react-three/fiber示例中均出现

相关代码

_app.tsx

import { useRouter } from 'next/router';
import type { AppProps } from 'next/app';
import { useRef } from 'react';
import { LocomotiveScrollProvider as RLSProvider } from 'react-locomotive-scroll';

import 'locomotive-scroll/dist/locomotive-scroll.css';
import '@styles/globals.scss';

function MyApp({ Component, pageProps }: AppProps) {
  const { asPath } = useRouter();
  const containerRef = useRef(null);
  return (
    <RLSProvider
      options={{
        smooth: true,
        // ... all available Locomotive Scroll instance options
      }}
      watch={
        [
          //..all the dependencies you want to watch to update the scroll.
          //  Basicaly, you would want to watch page/location changes
          //  For exemple, on Next.js you would want to watch properties like `router.asPath` (you may want to add more criterias if the instance should be update on locations with query parameters)
        ]
      }
      location={asPath}
      onLocationChange={(scroll: any) => scroll.scrollTo(0, { duration: 0, disableLerp: true })}
      containerRef={containerRef}
    >
      <div data-scroll-container ref={containerRef}>
        <Component {...pageProps} />
      </div>
    </RLSProvider>
  );
}

export default MyApp;

@react-three/fiber组件代码

import * as THREE from "three"
import { useRef, useState} from "react"
import { Canvas, extend, useFrame } from "@react-three/fiber"
import { useTexture, shaderMaterial } from "@react-three/drei"


interface ImageProps {
  scaleY: number;
  scaleX: number;

  cssY?: number | string; 
  cssX?: number | string; 

  image1: string; 
  image2: string; 

  right?: number | string; 
  left?:  number | string;

}


export const ImageFadeMaterial = shaderMaterial(
  {
    effectFactor: 1.2,
    dispFactor: 0,
    tex: undefined,
    tex2: undefined,
    disp: undefined
  },
  ` varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }`,
  ` varying vec2 vUv;
    uniform sampler2D tex;
    uniform sampler2D tex2;
    uniform sampler2D disp;
    uniform float _rot;
    uniform float dispFactor;
    uniform float effectFactor;
    void main() {
      vec2 uv = vUv;
      vec4 disp = texture2D(disp, uv);
      vec2 distortedPosition = vec2(uv.x + dispFactor * (disp.r*effectFactor), uv.y);
      vec2 distortedPosition2 = vec2(uv.x - (1.0 - dispFactor) * (disp.r*effectFactor), uv.y);
      vec4 _texture = texture2D(tex, distortedPosition);
      vec4 _texture2 = texture2D(tex2, distortedPosition2);
      vec4 finalTexture = mix(_texture, _texture2, dispFactor);
      gl_FragColor = finalTexture;
      #include <tonemapping_fragment>
      #include <encodings_fragment>
    }`
)

extend({ ImageFadeMaterial })

function FadingImage({ scaleY, scaleX, image1, image2 } : ImageProps) {
  const ref = useRef(null)
  const [texture1, texture2, dispTexture] = useTexture([image1, image2, "/displacement.jpg"])
  const [hovered, setHover] = useState(false)
  useFrame(() => {
    ref.current.dispFactor = THREE.MathUtils.lerp(ref.current.dispFactor, hovered ? 1 : 0, 0.075)
  })
  return (
    <mesh onPointerOver={() => setHover(true)} onPointerOut={() => setHover(false)} scale={[scaleX, scaleY, 1]}>
      <planeGeometry />
      <imageFadeMaterial ref={ref} tex={texture1} tex2={texture2} disp={dispTexture} toneMapped={false} />
    </mesh>
  )
}


const ImageShader = ({ scaleY, scaleX, cssY, cssX, image1, image2, right, left } : ImageProps) => {
  return (
    <div style={{
      height: cssY, 
      width: cssX,

      marginRight: right,
      marginLeft: left,
    }}>
      <Canvas camera={{ position: [0, 0, 2], fov: 50 }}>
        <FadingImage scaleX={scaleX} scaleY={scaleY} image1={image1} image2={image2}/>
      </Canvas>
    </div>
  )
}

export default ImageShader

解决方案

1. 修复渲染冲突

Locomotive Scroll会修改容器的样式和事件监听,干扰Three.js画布渲染。可以通过阻止事件冒泡、配置Canvas独立上下文解决:

修改ImageShader组件:

import { useEffect, useRef } from "react"

const ImageShader = ({ scaleY, scaleX, cssY, cssX, image1, image2, right, left } : ImageProps) => {
  const canvasContainerRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    if (canvasContainerRef.current) {
      // 阻止Locomotive Scroll捕获画布区域的滚动/触摸事件
      canvasContainerRef.current.addEventListener('wheel', (e) => e.stopPropagation());
      canvasContainerRef.current.addEventListener('touchmove', (e) => e.stopPropagation());
    }
  }, []);
  
  return (
    <div 
      ref={canvasContainerRef}
      style={{
        height: cssY, 
        width: cssX,
        marginRight: right,
        marginLeft: left,
      }}
    >
      <Canvas 
        camera={{ position: [0, 0, 2], fov: 50 }}
        gl={{ preserveDrawingBuffer: true }}
      >
        <FadingImage scaleX={scaleX} scaleY={scaleY} image1={image1} image2={image2}/>
      </Canvas>
    </div>
  )
}

2. 解决滚动条重复问题

Locomotive Scroll默认生成自定义滚动条,若和原生滚动条重复,可配置禁用原生滚动条:

修改_app.tsx的Locomotive配置:

<RLSProvider
  options={{
    smooth: true,
    scrollbar: true,
    nativeScroll: false, // 禁用原生滚动条
  }}
  watch={[asPath]}
  location={asPath}
  onLocationChange={(scroll: any) => scroll.scrollTo(0, { duration: 0, disableLerp: true })}
  containerRef={containerRef}
>

或添加全局CSS:

body {
  overflow: hidden;
}

3. 极端情况处理

若上述方法无效,可将Three.js组件移出Locomotive的滚动容器:

修改_app.tsx结构:

function MyApp({ Component, pageProps }: AppProps) {
  const { asPath } = useRouter();
  const containerRef = useRef(null);
  return (
    <div>
      <div id="threejs-container"></div>
      <RLSProvider
        options={{ smooth: true }}
        watch={[asPath]}
        location={asPath}
        onLocationChange={(scroll: any) => scroll.scrollTo(0, { duration: 0, disableLerp: true })}
        containerRef={containerRef}
      >
        <div data-scroll-container ref={containerRef}>
          <Component {...pageProps} />
        </div>
      </RLSProvider>
    </div>
  );
}

修改ImageShader的Canvas容器:

<Canvas 
  camera={{ position: [0, 0, 2], fov: 50 }}
  container={document.getElementById('threejs-container')}
>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 14:15:51