logologo

Three-D

☕Sign in

    Recent Post:

    Categories:

    nextjsjavascriptthreejshonoreactjs
    featured post image

    snippets

    featured post image

    What is Currying?

    featured post image

    What is an IIFE?

    Creating a 3D Interactive Pricing Demo with Three.js and Next.js

    August 16, 2024

    626

    image

    Think you know it all?

    Take quiz!

    code

    const PricingDemo = () => {
      const [value, setValue] = React.useState(3427);
    
      return (
        <ThreeDWraper title="PRICING" link="/blog/6f683bd5-e52e-40d9-8d9c-8f0629901bf9">
          <div className="flex flex-col gap-4 w-full">
            {/* On Click */}
            <div className="flex flex-row flex-wrap gap-4">
              <p>On Click</p>
              {[200, 400, 2600, 3100, 2639, 1243, 5437, 3242].map((val) => (
                <Button key={val} onClick={() => setValue(val)}>
                  {val}
                </Button>
              ))}
            </div>
    
            {/* On Hover */}
            <div className="hidden md:flex flex-row flex-wrap gap-4">
              <p>On Hover</p>
              {[200, 400, 2600, 3100, 2639, 1243, 5437, 3242].map((val) => (
                <Button key={val} onMouseEnter={() => setValue(val)} onMouseLeave={() => setValue(3457)}>
                  {val}
                </Button>
              ))}
            </div>
          </div>
        </ThreeDWraper>
      );
    };

    Key Points:

    Introduction

    In this blog, I will walk you through how I created an interactive 3D Pricing Demo using Three.js, @react-three/fiber, and @react-three/drei. This project dynamically updates the 3D text rendered on a canvas based on user interactions such as button clicks and hover events.

    We'll break down the setup, components, and rendering process step by step to help you create a similar 3D experience for your own projects.

    Project Setup

    To get started, install the necessary dependencies for the project:

    code

    npm install three @react-three/fiber @react-three/drei maath

    We also rely on Three.js's powerful utilities for rendering and the maath library for easing animations.

    Creating the Main Components

    1. Counter Component

    The Counter component renders a series of 3D numbers that update based on the value passed to them. The numbers rotate on the y axis with smooth animation using maath for easing.

    code

    import { Text, useMask } from '@react-three/drei';
    import * as THREE from 'three';
    import { easing } from 'maath';
    import { useRef } from 'react';
    import { useFrame } from '@react-three/fiber';
    
    const Counter = ({ index, value, speed = 0.1 }: { index: number; value: number; speed?: number }) => {
      const ref = useRef<THREE.Group | null>(null);
      const stencil = useMask(1);
    
      useFrame((state, delta) => {
        if (ref.current) {
          easing.damp(ref.current.position, 'y', value * -2, speed, delta);
        }
      });
    
      return (
        <group position-x={index * 1.1} ref={ref}>
          {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((number) => (
            <Text key={number} position={[-2.2, number * 2, 2]} fontSize={2}>
              {number}
              <meshBasicMaterial {...stencil} />
            </Text>
          ))}
        </group>
      );
    };
    

    Key Points:

    useRef is used to store references to the group of numbers.

    useFrame allows the numbers to move and rotate smoothly in sync with the animation frame, creating a dynamic effect.

    2. Price Component

    The Price component manages the display of a number based on user interactions. It uses the Counter component to display each digit of the price in 3D.

    code

    const Price = ({ value, currency = '₹', ...props }: { value: number; currency?: string; [key: string]: any }) => {
      return (
        <group {...props}>
          {[...`✨✨✨${value}`.slice(-4)].map((num, index) => (
            <Counter index={index} value={num === '✨' ? -1 : parseInt(num)} key={index} speed={0.1 * (4 - index)} />
          ))}
          <Text anchorY="bottom" position={[2 * 1.1, -0.25, 2]} fontSize={1}>
            {currency}
          </Text>
        </group>
      );
    };

    Here, the price is split into individual digits, and each digit is passed to a separate Counter component. The speed of the number’s animation decreases for each successive digit, creating a staggered animation effect.

    Interactive Button Component

    The next step is to add buttons that allow the user to change the displayed price by clicking or hovering.

    Clicking or hovering on a button changes the value, which is passed to the Price component, triggering a visual update.

    The price reverts to a default value (3457) when the user moves the mouse away from the button.

    Rendering the Scene

    Finally, we render the 3D scene using the Canvas component from @react-three/fiber:

    code

    <Canvas className="max-h-[calc(100%-60%)]">
      <Price value={value} />
    </Canvas>

    This is encapsulated within a responsive layout that ensures the 3D canvas fits well within the page.

    Conclusion

    By combining Three.js, @react-three/fiber, and @react-three/drei, we’ve created an interactive 3D pricing demo that updates dynamically based on user interactions. This can be further extended to suit a wide range of interactive 3D applications, from product showcases to creative pricing displays.

    I hope you found this tutorial helpful! Feel free to experiment with this concept and create your own 3D experiences.