Next.js 3D Photo Effect

Eugene Musebe

Introduction

This article shows how the Nextjs framework can be implemented to produce a 3D illusion photo from an image.

Codesandbox

Check the sandbox demo on Codesandbox.

You can also get the project Github repo using Github.

Prerequisites

Entry-level javascript and React/Nextjs knowledge.

Setting Up the Sample Project

In your respective folder, create a new nextjs app using npx create-next-app 3dimage in your terminal. Head to your project root directory cd 3dimage

Nextjs has its own serverside rendering backend which we will use for our media files upload. Set up Cloudinary for our backend.

Create your Cloudinary account using this Link. Log in to a dashboard containing the environment variable keys necessary for the Cloudinary integration in our project.

Include Cloudinary in your project dependencies: npm install cloudinary.

Create a new file named .env.local and paste the following guide to fill your environment variables. You locate your variables from the Cloudinary dashboard.

1CLOUDINARY_CLOUD_NAME =
2
3CLOUDINARY_API_KEY =
4
5CLOUDINARY_API_SECRET =

Restart your project: npm run dev.

In the pages/api directory, create a new file named upload.js. Start by configuring the environment keys and libraries.

1var cloudinary = require("cloudinary").v2;
2
3cloudinary.config({
4 cloud_name: process.env.CLOUDINARY_NAME,
5 api_key: process.env.CLOUDINARY_API_KEY,
6 api_secret: process.env.CLOUDINARY_API_SECRET,
7});

Create a handler function to execute the POST request. The function will receive media file data and post it to the Cloudinary website. It then captures the media file's Cloudinary link and sends it back as a response.

1export default async function handler(req, res) {
2 if (req.method === "POST") {
3 let url = ""
4 try {
5 let fileStr = req.body.data;
6 const uploadedResponse = await cloudinary.uploader.upload_large(
7 fileStr,
8 {
9 resource_type: "video",
10 chunk_size: 6000000,
11 }
12 );
13 url = uploadedResponse.url
14 } catch (error) {
15 res.status(500).json({ error: "Something wrong" });
16 }
17
18 res.status(200).json({data: url});
19 }
20}

The code above concludes our backend.

We'll use styled-components to create a card that can rotate 180 degrees. Include it in the dependencies:

npm install styled-components We use the following code to create our card component. We first show the front and hide the back card. The back will listen to the flip state hook to rotate 180 degrees to reveal the back.

1"pages/index"
2
3import styled from "styled-components";
4import { useState } from "react";
5
6const Container = styled("div")(() => ({
7 display: "flex",
8 flexDirection: "row",
9 justifyContent: "center"
10}));
11
12const Card = styled.div`
13 position: relative;
14 flex-basis: 100%;
15 max-width: 220px;
16`;
17
18const CardTemplate = styled("div")(() => ({
19 width: "100%",
20 backfaceVisibility: "hidden",
21 height: "400px",
22 borderRadius: "6px",
23 transformStyle: "preserve-3d",
24 transition: "transform 1s cubic-bezier(0.8, 0.3, 0.3, 1)"
25}));
26
27const CardFront = styled(CardTemplate)(({ flip }) => ({
28 backgroundImage: "url('images/cardimage.jpg')",
29 backgroundSize: "cover",
30 backgroundPosition: "center",
31 transform: flip ? "rotateY(-180deg)" : "rotateY(0deg)"
32}));
33
34const CardBack = styled(CardTemplate)(({ flip }) => ({
35 position: "absolute",
36 top: 0,
37 left: 0,
38 background: "linear-gradient(0deg, #00adb5 20%,#596a72 100%)",
39 transform: flip ? "rotateY(0deg)" : "rotateY(180deg)"
40}));
41
42const CardContent = styled("div")(() => ({
43 top: "50%",
44 position: "absolute",
45 left: 0,
46 width: "100%",
47 backfaceVisibility: "hidden",
48 transform: "translateZ(70px) scale(0.90)"
49}));
50
51const BGFade = styled("div")(() => ({
52 position: "absolute",
53 right: 0,
54 bottom: 0,
55 left: 0,
56 height: "200px",
57 background: "linear-gradient(to bottom, rgba(0,0,0,0) 20%,rgba(0,0,0,.8) 90%)"
58}));
59
60export default function Home() {
61 const [flip, setFlip] = useState(false);
62
63 return (
64 <div className="App">
65
66 </div>
67 )
68}

Finally, paste the following in your return statement:

1return (
2 <div className="App">
3 <h1>Nextjs 3d Photo Effect</h1>
4 <Container>
5 <Card
6 onMouseEnter={() => setFlip(true)}
7 onMouseLeave={() => setFlip(false)}
8 >
9 <CardFront flip={flip}>
10 <CardContent>
11 <h1>Front</h1>
12 </CardContent>
13 <BGFade />
14 </CardFront>
15 <CardBack flip={flip}>
16 <CardContent>
17 <h1>Back</h1>
18 </CardContent>
19 </CardBack>
20 </Card>
21 </Container>
22 </div>
23 )

That's it! Your UI should look like the below:

Use the following code to command your upload button. The code will be uploaded to the backend for Cloudinary upload.

1const uploadHandler = async () => {
2 console.log(cardRef.current)
3 await html2canvas(cardRef.current).then(function (canvas) {
4 try {
5 fetch('/api/upload', {
6 method: 'POST',
7 body: JSON.stringify({ data: canvas.toDataURL() }),
8 headers: { 'Content-Type': 'application/json' },
9 })
10 .then((response) => response.json())
11 .then((data) => {
12 console.log(data.data);
13 setSampleSelected(true)
14 });
15 } catch (error) {
16 console.error(error);
17 }
18 })
19 }

That accomplishes the project. Enjoy selecting pictures for your card. An example is in the front card.

Eugene Musebe

Software Developer

I’m a full-stack software developer, content creator, and tech community builder based in Nairobi, Kenya. I am addicted to learning new technologies and loves working with like-minded people.