Super

Posted on

Creating Stunning 3D Instagram Story Swipes with React: A Step-by-Step Tutorial

Introduction

Have you ever used Instagram? If so, chances are you've come across Instagram Stories - a feature that has become increasingly popular in recent years. As someone who enjoys using Instagram, I found the animations used in Instagram Stories to be particularly amazing, and I decided to try and recreate them myself. And guess what? I succeeded! In this tutorial, I'm going to show you how to create a 3D Instagram story swipe using React, a popular JavaScript library for building user interfaces. With my help, you'll be able to add a creative and unique touch to your Instagram Stories that will make them stand out from the crowd. Let's get started!

Setup

We won't use any library other than React for this demo ( we can write this in plain JavaScript too )

Understand CSS 3D

Before going into the code. I'll explain some CSS attributes that are important with 3D animations:

1. transform with transformZ and Y value: The `transform` property is used to apply transformations such as rotation, scaling, and translation to an element. When used in 3D, you can also use `transformZ` and `transformY` values to control the depth and height of the element respectively. For example, `transform: translateZ(-50px)` will move the element 50 pixels away from the viewer, creating a sense of depth.

2. transform-style with preserve-3d value: The `transform-style` property is used to specify whether child elements should be flattened or preserve their 3D position. When set to `preserve-3d`, child elements will maintain their 3D positioning relative to the parent element. This is essential for creating complex 3D animations and designs.

3. perspective: The `perspective` property is used to set the distance between the z=0 plane and the viewer in 3D space. This creates a sense of depth and perspective, making objects appear further away or closer to the viewer. For example, `perspective: 1000px` will set the viewer's perspective 1000 pixels away from the element, creating a greater sense of depth.

The Logic

States and constants

``````const [displayStories, setDisplayStories] = useState(data);
const [imagePosition, setImagePosition] = useState(
new Map(data.map((i) => [i.id, 0]))
);
const [cellSize, setCellSize] = useState(480);

const [currentStory, setCurrentStory] = useState(data[0].id);
const hold = useRef(0);
const radiusRef = useRef(240 / Math.tan(Math.PI / 4));

const carouselRef = useRef<HTMLDivElement | null>(null);
const currentStoryRef = useRef(data[0].id);

let isDown = false;
let current = 0;
let rotateYref = 0;
``````

This code block defines several state variables and references that are used to manage the 3D carousel effect.

`const [displayStories, setDisplayStories] = useState(data);` - This line defines a state variable called `displayStories` that holds an array of story objects. The `useState` hook is used to initialize the state with the `data` parameter passed in.

`const [imagePosition, setImagePosition] = useState(new Map(data.map((i) => [i.id, 0])));` - This line defines a state variable called `imagePosition` that holds a Map object where each key is a story ID and the value is the index of the currently displayed image for that story. The `useState` hook is used to initialize the state with a new Map object that is created from the `data` parameter passed in.

`const [cellSize, setCellSize] = useState(480);` - This line defines a state variable called `cellSize` that holds the size of each carousel cell. The `useState` hook is used to initialize the state with a value of 480.

`const [currentStory, setCurrentStory] = useState(data[0].id);` - This line defines a state variable called `currentStory` that holds the ID of the currently displayed story. The `useState` hook is used to initialize the state with the ID of the first story in the `data` array.

`const hold = useRef(0);` - This line defines a reference called `hold` that is used to store a timeout ID.

`const radiusRef = useRef(240 / Math.tan(Math.PI / 4));` - This line defines a reference called `radiusRef` that is used to store the radius of the carousel circle. The `useRef` hook is used to initialize the reference with a value calculated from the angle of view of the camera.

`const carouselRef = useRef<HTMLDivElement | null>(null);` - This line defines a reference called `carouselRef` that is used to reference the carousel `div`.

`const currentStoryRef = useRef(data[0].id);` - This line defines a reference called `currentStoryRef` that is used to reference the ID of the currently displayed story.

`const [radius, setRadius] = useState(240 / Math.tan(Math.PI / 4));` - This line defines a state variable called `radius` that holds the radius of the carousel circle. The `useState` hook is used to initialize the state with a value calculated from the angle of view of the camera.

`let isDown = false;` - This line defines a boolean variable called `isDown` that is used to determine if the mouse is currently pressed down.

`let current = 0;` - This line defines a numeric variable called `current` that is used to keep track of the current rotation of the carousel.

`let rotateYref = 0;` - This line defines a numeric variable called `rotateYref` that is used to keep track of the current Y-rotation of the carousel.

The Events

There are some events that we need to listen when the component started render:

``````useEffect(() => {
const carousel = document.getElementById("carousel") || ({} as Element);

if (window.screen.width < 480) {
setCellSize(window.screen.width);
setRadius(window.screen.width / 2 / Math.tan(Math.PI / 4));
}

if (carousel) {
carousel.addEventListener("mousedown", start as (e: Event) => void);
carousel.addEventListener("touchstart", start as (e: Event) => void);

carousel.addEventListener("mousemove", move as (e: Event) => void);
carousel.addEventListener("touchmove", move as (e: Event) => void);

}
}, []);
``````

The event listeners for "mousedown", "touchstart", "mousemove", "touchmove", "mouseleave", "mouseup", and "touchend" are added to the carousel element. These event listeners call the `start`, `move`, and `end` functions which are defined elsewhere in the code. These functions are used to handle the user's interactions with the carousel, such as clicking, dragging, and releasing.

The functions

There are 5 main functions that I want to cover in this demo:

• `prevStory` : a function to go to the previous story
• `nextStory` : a function to go to the next story
• `start` : the function that would be triggered when we start click or touch the component
• `move` : the function that would be triggered everytime we move the mouse or our finger
• `end` : the function that would be trigger when we stop our animation

Let's go to the first 2 functions:

`````` const prevStory = (currentStoryIndex: number) => {
setCurrentStory(data[currentStoryIndex - 1].id);
rotateYref = hold.current + 90;
if (carouselRef.current) {
carouselRef.current.style.transform = `translateZ(-\${
}px) rotateY(\${hold.current + 90}deg)`;
}

hold.current = hold.current + 90;
};

const nextStory = (currentStoryIndex: number) => {
setCurrentStory(data[currentStoryIndex + 1].id);
rotateYref = hold.current - 90;
if (carouselRef.current) {
carouselRef.current.style.transform = `translateZ(-\${
}px) rotateY(\${hold.current - 90}deg)`;
}
hold.current = hold.current - 90;
};
``````

These two functions, `prevStory` and `nextStory`, are used to navigate to the previous and next story in the carousel, respectively. The functions take in a `currentStoryIndex` parameter, which represents the index of the currently displayed story in the `data` array.

When `prevStory` is called, it sets the `currentStory` state variable to the ID of the previous story in the `data` array. It also updates the `rotateYref` variable to the current angle of rotation plus 90 degrees, and updates the `carouselRef` element's style to reflect the new angle of rotation. Finally, it updates the `hold` reference to reflect the new angle of rotation.

When `nextStory` is called, it sets the `currentStory` state variable to the ID of the next story in the `data` array. It also updates the `rotateYref` variable to the current angle of rotation minus 90 degrees, and updates the `carouselRef` element's style to reflect the new angle of rotation. Finally, it updates the `hold` reference to reflect the new angle of rotation.

Animation functions

`````` const end = () => {
isDown = false;
if (carouselRef.current) {
carouselRef.current.style.transition = "transform 0.25s";
}

const currentStoryIndex = getCurrentIndex(currentStoryRef);
if (rotateYref > hold.current && !isFirst(currentStoryIndex)) {
prevStory(currentStoryIndex);
return;
}
if (rotateYref < hold.current && !isLast(currentStoryIndex, data.length)) {
nextStory(currentStoryIndex);
return;
}
if (carouselRef.current) {
}
};

const start = (e: MouseEvent | TouchEvent) => {
isDown = true;
current = "pageX" in e ? e.pageX : e.touches[0].pageX;
};

const move = (e: MouseEvent | TouchEvent) => {
if (!isDown || !carouselRef.current) return;
e.preventDefault();
carouselRef.current.style.transition = "none";
const dist = "pageX" in e ? e.pageX : e.touches[0].pageX;
const threshHold = Math.abs(dist - current);
const wrap = 3.6666666;
if (dist >= current) {
rotateYref = hold.current + threshHold / wrap;
carouselRef.current.style.transform = `translateZ(-\${
}px) rotateY(\${hold.current + threshHold / wrap}deg)`;
} else {
rotateYref = hold.current - threshHold / wrap;

carouselRef.current.style.transform = `translateZ(-\${
}px) rotateY(\${hold.current - threshHold / wrap}deg)`;
}
}
``````

These three functions are used to handle the user's interactions with the carousel element, such as clicking, dragging, and releasing.

`end` - This function is called when the user releases the mouse or touch on the carousel element. It sets the `isDown` variable to `false` to indicate that the mouse is no longer pressed. It also adds a transition effect to the carousel element, sets the `currentStoryIndex` to the current index of the displayed story, and checks if the carousel should rotate to the previous or next story, based on the `rotateYref` and `hold.current` variables. Finally, it updates the `carouselRef` element's style to reflect the new angle of rotation.

`start` - This function is called when the user clicks or touches the carousel element. It sets the `isDown`to `true` to indicate that the mouse is pressed, and sets the `current` to the current X position of the mouse or touch.

`move` - This function is called when the user moves the mouse or touch on the carousel element. It checks if the mouse is currently pressed down, and if so, it prevents the default behavior of the event. It also updates the `rotateYref` and `carouselRef` element's style to reflect the new angle of rotation based on the distance moved by the mouse or touch.

The User Interface

We will put all the code above into a React Hook, then we can use it in the component like this:

``````const {
displayStories,
imagePosition,
cellSize,
carouselRef,
} = useCarousel();
``````

The most important part right now is to put the radius into each element like this:

``````<div
key={story.id}
className="image-full absolute"
style={{
transform: `rotateY(\${
index * 90
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 60,
}}
>
``````

We will rotate the Y axis with index multiply with 90 degree. So we will have a cube.
With the wrapper, we need to set `transform-style: preserve-3d;` to it. And this will be the result:

Conclusion

To sum up, developing a 3D Instagram story carousel with React can significantly enhance the visual appeal of your Instagram Stories. By employing 3D CSS transformations and React, a revolving image carousel can be created with the appearance of depth and perspective. In this guide, we examined the essential features of 3D CSS, such as `transform`, `transform-style`, and `perspective`. Additionally, we discussed the React component code that constructs a 3D Instagram Stories carousel and explained the interplay of various functions and state variables in delivering an interactive, captivating user experience. With a touch of inventiveness and understanding of 3D CSS and React, you can develop your very own striking 3D Instagram story carousel that will astonish your audience and make your Stories more prominent.

Source Code: https://github.com/superdev163/3d-instagram
Live Demo: https://instagram-3d-poc.web.app/

Nishchit

Very interactive tutorial. The demo looks like a professional work. Thanks for sharing.

Super

Thanks. I put a lot of effort in the demo

Comment deleted

Emmanuel Katto

Amazing, thanks for sharing!

Anastasiia Ogneva

Thanks! Very interesting

Thanks. Great content!!

Comment deleted

_solaris

Nice, I've always wondered how these were made.

Comment deleted

a_moah__

Great content

Super

Thanks !

Ashutosh Mishra

Amazing! Would love to see other interesting projects from you!

Paul Salamone

very cool, one constructive idea: show us the demo earlier in the article :)

Comment deleted

Comment deleted