Build a Simple Next.js Interactive Image Comparison App

Written by

in

Have you ever visited a website and been impressed by an interactive image comparison slider? Perhaps you’ve seen it on an e-commerce site showcasing before-and-after photos of a product, or maybe on a real estate website highlighting the difference between a property’s current and previous state. These engaging elements not only enhance user experience but also provide a visually compelling way to present information. In this tutorial, we’ll dive into how to build your own interactive image comparison app using Next.js, a powerful React framework for building modern web applications.

Why Build an Image Comparison App?

Image comparison apps serve a variety of purposes. They are great for:

  • Product Demonstrations: Showcasing product transformations or features.
  • Before-and-After Comparisons: Highlighting changes, improvements, or processes.
  • Interactive Storytelling: Engaging users with visual narratives.
  • Educational Content: Demonstrating differences or changes over time.

Building one offers a practical way to learn Next.js, React, and fundamental web development concepts. Furthermore, it allows you to create a visually appealing and functional component that can be easily integrated into various projects.

What We’ll Cover

In this guide, we’ll cover the following:

  • Setting up a Next.js project.
  • Creating the basic HTML structure.
  • Styling the component with CSS.
  • Implementing the interactive slider functionality using JavaScript.
  • Adding responsiveness for different screen sizes.
  • Deploying your app.

Prerequisites

Before you start, make sure you have the following:

  • Node.js and npm (or yarn) installed.
  • A basic understanding of HTML, CSS, and JavaScript.
  • A code editor (e.g., VS Code, Sublime Text).

Step-by-Step Guide

1. Setting Up the Next.js Project

First, let’s create a new Next.js project. Open your terminal and run the following command:

npx create-next-app image-comparison-app
cd image-comparison-app

This command creates a new Next.js project named “image-comparison-app” and navigates you into the project directory. You can choose a different project name if you prefer.

2. Project Structure and File Setup

Next.js projects have a specific file structure. Here’s how we’ll organize our files:

  • pages/index.js: This is the main page of our application. We will add our image comparison component here.
  • components/ImageComparison.js: This is where we will create the reusable image comparison component.
  • styles/ImageComparison.module.css: This file will contain the CSS styles for our component.
  • public/: This directory will store our images.

Create these files and directories within your project.

3. Creating the Image Comparison Component (ImageComparison.js)

Let’s start building the core component. Open components/ImageComparison.js and add the following code:

import React, { useState, useRef, useEffect } from 'react';
import styles from '../styles/ImageComparison.module.css';

const ImageComparison = ({ beforeImage, afterImage, altText, initialPosition = 50 }) => {
  const [position, setPosition] = useState(initialPosition);
  const sliderRef = useRef(null);
  const containerRef = useRef(null);
  const [isDragging, setIsDragging] = useState(false);

  useEffect(() => {
    const handleMouseMove = (e) => {
      if (!isDragging) return;
      const containerWidth = containerRef.current.offsetWidth;
      const newPosition = ((e.clientX - containerRef.current.offsetLeft) / containerWidth) * 100;
      const clampedPosition = Math.max(0, Math.min(100, newPosition));
      setPosition(clampedPosition);
    };

    const handleMouseUp = () => {
      setIsDragging(false);
    };

    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);

    return () => {
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
    };
  }, [isDragging]);

  const handleMouseDown = (e) => {
    e.preventDefault();
    setIsDragging(true);
  };

  const handleTouchMove = (e) => {
    e.preventDefault();
    if (!isDragging) return;
    const containerWidth = containerRef.current.offsetWidth;
    const newPosition = ((e.touches[0].clientX - containerRef.current.offsetLeft) / containerWidth) * 100;
    const clampedPosition = Math.max(0, Math.min(100, newPosition));
    setPosition(clampedPosition);
  };

  const handleTouchStart = (e) => {
    e.preventDefault();
    setIsDragging(true);
  };

  const handleTouchEnd = () => {
    setIsDragging(false);
  };


  return (
    <div>
      <div style="{{">
        <span></span>
      </div>
      <img src="{beforeImage}" alt="{altText}" />
      <div style="{{">
        <img src="{afterImage}" alt="{altText}" />
      </div>
      <div>
      </div>
    </div>
  );
};

export default ImageComparison;

This code defines our ImageComparison component. Let’s break it down:

  • Imports: We import useState, useRef, and useEffect from React. We also import our CSS module.
  • State: position keeps track of the slider’s position (percentage). isDragging tracks if the slider is being dragged or not.
  • Refs: sliderRef and containerRef are used to access the slider handle and the container element, respectively.
  • useEffect: This hook handles mouse and touch events for dragging the slider. It updates the position based on the mouse/touch movement. It also includes cleanup functions to remove the event listeners when the component unmounts.
  • Event Handlers: handleMouseDown, handleMouseMove, handleMouseUp, handleTouchStart, handleTouchMove, and handleTouchEnd manage the dragging behavior for both mouse and touch events. They calculate the new slider position based on the user’s interaction.
  • JSX: The JSX structure includes the container, the slider handle, the before image, and the after image container. The style attribute on the comparisonSlider and afterImageContainer dynamically updates their positions/widths based on the position state. We also include a touchArea div to capture touch events.

4. Styling the Component (ImageComparison.module.css)

Now, let’s add some CSS to style our component. Open styles/ImageComparison.module.css and add the following CSS:

.comparisonContainer {
  position: relative;
  width: 100%;
  height: 400px;
  overflow: hidden; /* Important to clip the after image */
  border: 1px solid #ccc;
  border-radius: 5px;
}

.beforeImage {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none; /* Prevent image selection */
}

.afterImageContainer {
  position: absolute;
  top: 0;
  left: 0;
  width: 50%; /* Initial width */
  height: 100%;
  overflow: hidden; /* Clip the after image */
}

.afterImage {
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

.comparisonSlider {
  position: absolute;
  top: 0;
  left: 50%; /* Initial position */
  width: 2px;
  height: 100%;
  background-color: #fff;
  cursor: col-resize;
  z-index: 10; /* Ensure slider is on top */
}

.sliderHandle {
  position: absolute;
  top: 50%;
  left: -10px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: #0070f3;
  transform: translateY(-50%);
  cursor: col-resize;
}

.touchArea {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 5;
}

@media (max-width: 768px) {
  .comparisonContainer {
    height: 300px; /* Adjust height for smaller screens */
  }
}

This CSS provides the basic styling for our component. Key points include:

  • Container: The .comparisonContainer sets the overall dimensions and ensures the images are clipped correctly. overflow: hidden is crucial for the image comparison effect.
  • Images: The .beforeImage and .afterImage styles position the images and use object-fit: cover to ensure they fill the container.
  • Slider: The .comparisonSlider and .sliderHandle styles create the draggable slider.
  • Touch Area: The .touchArea div is used to capture touch events.
  • Responsiveness: A media query is included to adjust the height of the component on smaller screens.

5. Using the Component in the Main Page (pages/index.js)

Now, let’s integrate the ImageComparison component into our main page. Open pages/index.js and replace the default code with the following:

import ImageComparison from '../components/ImageComparison';

export default function Home() {
  return (
    <div>
      <h2>Interactive Image Comparison</h2>
      <p>Drag the slider to compare the images:</p>
      
    </div>
  );
}

In this code:

  • We import the ImageComparison component.
  • We render the component, passing in the paths to our before and after images and an alt text.

6. Adding Images

To see the component in action, you need to add two images to your public/ directory. Name them before.jpg and after.jpg, or adjust the image paths in pages/index.js accordingly.

7. Run the Application

Finally, run your Next.js application by running the following command in your terminal:

npm run dev

or

yarn dev

Open your browser and go to http://localhost:3000. You should see your interactive image comparison app!

Common Mistakes and How to Fix Them

  • Incorrect Image Paths: Double-check that your image paths in pages/index.js are correct. Make sure the images are in the public/ directory.
  • CSS Conflicts: If your component doesn’t look right, inspect your browser’s developer tools to check for CSS conflicts. Make sure your CSS module is correctly applied.
  • Event Listener Issues: Ensure your event listeners (mousemove, mouseup, etc.) are correctly added and removed to prevent memory leaks. The useEffect hook with a cleanup function is crucial for this.
  • Slider Not Draggable: Verify that the cursor property in the CSS is set to col-resize to indicate that the slider is draggable. Also, check that the event listeners are correctly attached.
  • Responsiveness Issues: Test your component on different screen sizes to ensure the layout is responsive. Use media queries in your CSS to adjust the styling for smaller screens.

Adding More Features (Optional)

You can enhance this project further with these features:

  • Add a loading indicator: Display a loading indicator while the images are loading.
  • Implement different slider styles: Customize the slider handle and track appearance.
  • Add a caption: Include a caption or description for each image.
  • Allow image swapping: Provide the ability to swap the before and after images.
  • Optimize image loading: Use Next.js’s Image component for optimized image loading and performance.

Key Takeaways

This tutorial provides a solid foundation for building an interactive image comparison app using Next.js. You’ve learned how to:

  • Set up a Next.js project.
  • Create a reusable component.
  • Handle user interactions (dragging).
  • Style the component with CSS.
  • Make your component responsive.

This is a great starting point for more complex projects. You can adapt this component for various use cases, such as product comparisons, before-and-after demonstrations, or educational content. By understanding the core concepts and techniques, you can build interactive and engaging web experiences.

FAQ

Q: How can I deploy this app?

A: You can deploy your Next.js app to platforms like Vercel (recommended), Netlify, or other hosting providers. Vercel has built-in support for Next.js and makes deployment very easy.

Q: How do I handle different image sizes?

A: You can use CSS to control image size. Consider using object-fit: cover or object-fit: contain in your CSS to make the images fit within the container. You might also want to use responsive images using the Image component in Next.js.

Q: How can I improve performance?

A: Optimize your images by compressing them and using appropriate formats (e.g., WebP). Consider lazy loading images and using Next.js’s built-in image optimization features.

Q: How can I add touch support?

A: The provided code already includes touch support using onTouchStart, onTouchMove, and onTouchEnd events. The touchArea div ensures that touch events are captured correctly.

Q: Can I use this component with images from a CMS?

A: Yes, you can. You’d fetch the image URLs from your CMS (Content Management System) and pass them as props to the ImageComparison component.

Building interactive components like this is a fundamental skill for modern web development. By mastering the techniques demonstrated here, you’ll be well-equipped to create engaging and user-friendly web applications. Now go forth and create something amazing!