Abhi
Abhi

Reputation: 21

How to add dom elements behind the three.js model in canvas?

I want to move to last section "Color Transitions" text and background image behind of the 3d model. I am using react-three/fiber, drei Deployed preview - https://astonishing-pony-00dd84.netlify.app/

//App.tsx
import { Canvas } from '@react-three/fiber';
import { ScrollControls, Scroll } from '@react-three/drei';
import { Scene } from './components/Scene';
import { ScrollContainer } from './components/ScrollContainer';

function App() {
  return (
    <div className="w-full h-screen bg-gray-900">
      <Canvas
        camera={{ position: [0, 0, 5], fov: 75 }}
        className="w-full h-full"
      >
        <ScrollControls pages={3} damping={0.1}>
          <ambientLight intensity={0.5} />
          <pointLight position={[10, 10, 10]} />
          <Scene />
          <Scroll html>
            <ScrollContainer>
              <div className="absolute inset-0 pointer-events-none" />
            </ScrollContainer>
          </Scroll>
        </ScrollControls>
      </Canvas>
    </div>
  );
}

export default App;
//ScrollContainer.tsx
import React from 'react';
import { ScrollSection } from './ScrollSection';

interface ScrollContainerProps {
  children: React.ReactNode;
}

export function ScrollContainer({ children }: ScrollContainerProps) {
  return (
    <div className="h-[300vh]">
      <div className="sticky top-0 h-screen">
        {children}
      </div>
      <div className="px-4 py-8 space-y-96">
        <ScrollSection 
          title="Dynamic Movement"
          description="Watch as the object flows smoothly from side to side as you scroll."
        />
        <ScrollSection 
          title="Fluid Animation"
          description="The object dances through space with synchronized rotations and movements."
        />

       {/* I want to add image background behind the model but text in front of model only for this section*/}
        <ScrollSection 
          title="Color Transitions"
          description="Experience smooth color transitions as the object moves through its path."
          variant="with-background"
        />
      </div>
    </div>
  );
}
//ScrollSection.tsx
import { ReactNode } from 'react';

interface ScrollSectionProps {
  title: string;
  description: string;
  variant?: 'default' | 'with-background';
}

export function ScrollSection({ title, description, variant = 'default' }: ScrollSectionProps) {
  const baseClasses = "max-w-2xl mx-auto p-8 rounded-lg";
  const variantClasses = {
    default: "",
    'with-background': "bg-[url('https://images.unsplash.com/photo-1451187580459-43490279c0fa?q=80&w=2072')] bg-cover bg-center bg-no-repeat"
  };

  return (
    <div className={`${baseClasses} ${variantClasses[variant]}`}>
      <h2 className="text-4xl font-bold text-white">{title}</h2>
      <p className="mt-4 text-lg text-gray-300">
        {description}
      </p>
    </div>
  );
}

Additional Files

//Scene.tsx
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { useScroll, Environment, PerspectiveCamera } from '@react-three/drei';
import { calculatePosition, calculateRotation } from '../utils/animations';
import { Group, Mesh } from 'three';

export function Scene() {
  const modelRef = useRef<Group>(null);
  const meshRef = useRef<Mesh>(null);
  const scroll = useScroll();

  useFrame((state) => {
    if (!modelRef.current || !meshRef.current) return;
    
    const scrollOffset = scroll.offset;
    const newPosition = calculatePosition(scrollOffset);
    const newRotation = calculateRotation(scrollOffset, state.clock.elapsedTime);
    
    modelRef.current.position.copy(newPosition);
    meshRef.current.rotation.copy(newRotation);
  });

  return (
    <>
      <PerspectiveCamera makeDefault position={[0, 0, 5]} />
      <Environment preset="sunset" />
      <ambientLight intensity={0.5} />
      <spotLight
        position={[10, 10, 10]}
        angle={0.15}
        penumbra={1}
        intensity={1}
        castShadow
      />
      
      <group ref={modelRef}>
        <mesh ref={meshRef} castShadow receiveShadow>
          <torusKnotGeometry args={[1, 0.3, 128, 16]} />
          <meshStandardMaterial 
            color="#4f46e5"
            metalness={0.5}
            roughness={0.2}
          />
        </mesh>
      </group>
    </>
  );
}
//Model.tsx
import { useGLTF } from '@react-three/drei';
import { useEffect } from 'react';
import { Mesh } from 'three';
import { GLTF } from 'three-stdlib';

interface ModelProps {
  url: string;
  scale?: number;
  position?: [number, number, number];
}

export function Model({ url, scale = 1, position = [0, 0, 0] }: ModelProps) {
  const { scene } = useGLTF(url) as GLTF;

  useEffect(() => {
    scene.traverse((child) => {
      if (child instanceof Mesh) {
        child.castShadow = true;
        child.receiveShadow = true;
      }
    });
  }, [scene]);

  return <primitive object={scene} scale={scale} position={position} />;
}

Fuild animation text is correct as it is front the model. But last text and background of text should be behind the model. Basically I don't want I want some DOM elements infront of model and some DOM behind the model. So far DOM is only appearing in front of model that is in <Scroll html> </Scroll> tags

Sandbox

Upvotes: 1

Views: 75

Answers (1)

Łukasz Daniel Mastalerz
Łukasz Daniel Mastalerz

Reputation: 2217

Probably occlude="blending" in Drei's Helpers is something what you looking for.

import React, { useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import { OrbitControls, Html } from "@react-three/drei";
import * as THREE from "three";

const Cube: React.FC = () => {
  const cubeRef = useRef<THREE.Mesh>(null);

  useFrame(() => {
    if (cubeRef.current) {
      cubeRef.current.rotation.x += 0.01;
      cubeRef.current.rotation.y += 0.01;
    }
  });

  return (
    <mesh ref={cubeRef} position={[0, 0, 0]} scale={[1.5, 1.5, 1.5]}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="#7bff42" />
    </mesh>
  );
};

const App: React.FC = () => {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        height: "100vh",
      }}
    >
      <div style={{ position: "relative", width: "400px", height: "400px" }}>
        <Canvas>
          <ambientLight intensity={0.5} />
          <directionalLight position={[10, 10, 10]} />
          <Cube />
          <OrbitControls />

          <Html
            position={[-0.5, 0.5, 0]}
            style={{
              marginTop: "10px",
              fontSize: "24px",
              fontWeight: "bold",
              color: "#ff0808",
              textAlign: "center",
            }}
          >
            I am Here
          </Html>
          <Html
            position={[-3.5, 0.5, 0]}
            occlude="blending"
            style={{
              fontSize: "24px",
              fontWeight: "bold",
              color: "white",
              textAlign: "center",
              backgroundImage:
                "url('https://images.unsplash.com/photo-1451187580459-43490279c0fa?q=80&w=2072')",
              backgroundSize: "cover",
              backgroundPosition: "center",
              padding: "20px",
              width: "300px",
            }}
          >
            Background Texture
          </Html>
        </Canvas>
      </div>
    </div>
  );
};

export default App;

SANDBOX

Upvotes: 0

Related Questions