Solving the challenge of magnifying images in React without the use of any

I am working on adapting this image magnifier code for React Typescript without relying on an external library. The original Vanilla Javascript Codepen can be found here. Instead of copying and pasting the CSS into a separate file, I want to incorporate it within my const styles or explore using a styled component to achieve the same outcome.

One question I have is how to avoid manual DOM manipulation with getElementById as I believe there could be a better way to handle this in React Typescript.

The structure involves a container for centering the element, followed by a magnifyWrapper that serves as the hover div triggering the display of a larger version of the image when hovered over.

Other elements include the main image and a ghost div for loading the enlarged image.

React Typescript Code

import React from 'react';

const styles = {

  container: {
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
    height: "100vh",
  },

  magnifyWrapper: {
    position: "relative",
    maxHeight: "50vh",
    image: {
      maxHeight: "inherit",
    },
    #largeImg: {
      background: "url("https://images.unsplash.com/photo-1542856204-00101eb6def4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=975&q=80")",
        noRepeat "#fff",
      width: "100px",
      height: "100px",
      boxShadow: "0 5px 10px -2px rgba(0, 0, 0, 0.3)",
      pointerEvents: "none",
      position: "absolute",
      border: "4px solid #efefef",
      zIndex: "99",
      borderRadius: "100%",
      display: "block",
      opacity: "0",
      transition: "opacity 0.2s",
    },
    &:hover,
    &:active: {
      #largeImg: {
        opacity: "1"
      }
    }
  }
};

interface Props {
  magnified: HTMLElement;
  original: HTMLElement;
  imgWidth: number;
  imgHeight: number;

}

function Magnifier(props: Props) {

  document.getElementById("zoom").addEventListener(
    "mousemove",
    function (e) {
      let original = document.getElementById("main-img"),
        magnified = document.getElementById("large-img"),
        style = magnified.style,
        x = e.pageX - this.offsetLeft,
        y = e.pageY - this.offsetTop,
        imgWidth = original.width,
        imgHeight = original.height,
        xperc = (x / imgWidth) * 100,
        yperc = (y / imgHeight) * 100;

      if (x > 0.01 * imgWidth) {
        xperc += 0.15 * xperc;
      }

      if (y >= 0.01 * imgHeight) {
        yperc += 0.15 * yperc;
      }

      style.backgroundPositionX = xperc - 9 + "%";
      style.backgroundPositionY = yperc - 9 + "%";

      style.left = x - 50 + "px";
      style.top = y - 50 + "px";
    },
    false
  );
      

  return (
    <div sx={styles.container} >
      <div id="zoom" sx={styles.magnifyWrapper}>
        <img 
          src="https://images.unsplash.com/photo-1542856204-00101eb6def4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=975&q=80" id="main-img" 
        />
        <div sx={styles.largeImg}></div>
      </div>
    </div>
  );
}

export { Magnifier };

Answer №1

New Approach

When working with React, it's essential to shift your mindset from imperative to declarative programming. Instead of directly manipulating the DOM with inline styles, you should focus on rendering elements based on the current state and props. To tackle this, consider the following:

  • Understanding the application's state at any given moment.
  • Representing the application state through a combination of component props and state.
  • Rendering DOM elements dynamically based on the application state.

Fresh Solution

Here is a revamped approach I devised:

import "./styles.css";
import { Box, Image } from "theme-ui";
import React, { useState } from "react";

interface MagnifierProps {
  imgSrc: string;
  imgWidth?: number;
  imgHeight?: number;
  magnifierRadius: number;
}

function Magnifier({
  imgSrc,
  imgHeight,
  imgWidth,
  magnifierRadius
}: MagnifierProps) {
  // State to store magnifier position and visibility.
  const [magnifierState, setMagnifierState] = useState({
    top: 0,
    left: 0,
    offsetX: 0,
    offsetY: 0
  });
  
  const [isVisible, setIsVisible] = useState(false);

  return (
    <Box
      sx={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100vh"
      }}
    >
      <Box sx={{ position: "relative" }}>
        <Image
          src={imgSrc}
          width={imgWidth}
          height={imgHeight}
          // Additional styling for image.
          sx={{
            maxHeight: "50vh",
            maxWidth: "50vh",
            height: "auto",
            width: "auto"
          }}
          onMouseMove={(e) => {
            setIsVisible(true);
            const smallImage = e.currentTarget;
            const x = e.nativeEvent.offsetX;
            const y = e.nativeEvent.offsetY;
            
            // Calculate magnifier position relative to large image.
            setMagnifierState({
              top: y - magnifierRadius,
              left: x - magnifierRadius,
              offsetX: (x / smallImage.width) * smallImage.naturalWidth - magnifierRadius,
              offsetY: (y / smallImage.height) * smallImage.naturalHeight - magnifierRadius
            });
          }}
          onMouseLeave={() => setIsVisible(false)}
        />
        <Box
          sx={{
            boxShadow: "0 5px 10px -2px rgba(0, 0, 0, 0.3)",
            pointerEvents: "none",
            position: "absolute",
            border: "4px solid #efefef",
            zIndex: 99,
            display: "block",
            transition: "opacity 0.2s",
            background: `url("${imgSrc}") no-repeat #fff`,
            width: 2 * magnifierRadius,
            height: 2 * magnifierRadius,
            borderRadius: magnifierRadius,
            top: magnifierState.top + "px",
            left: magnifierState.left + "px",
            backgroundPositionX: -1 * magnifierState.offsetX,
            backgroundPositionY: -1 * magnifierState.offsetY,
            opacity: isVisible ? 1 : 0
          }}
        />
      </Box>
    </Box>
  );
}

export default function App() {
  return (
    <Magnifier
      imgSrc="https://images.unsplash.com/photo-1542856204-00101eb6def4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=975&q=80"
      imgWidth={975}
      imgHeight={1300}
      magnifierRadius={50}
    />
  );
}

Check out the CodeSandbox demo here

Breakdown

Component Props

I introduced a Magnifier component that can be easily integrated into your App. The important details like image source, dimensions, and zoom circle radius are now customizable props. This allows for flexibility in changing the displayed image through the imgSrc.

Optional props like imgWidth and imgHeight control the image size, while magnifierRadius dictates the zoom circle's dimensions.

Dynamic Positioning

The magnifier's position updates dynamically as the mouse moves over the image. By capturing mouse coordinates within the image, we adjust the magnified view appropriately using the magnifierState.

Visibility Management

Instead of relying on CSS selectors, I utilized React state to toggle the magnifier's visibility based on mouse actions. This simplifies the logic and ensures smooth interactivity.

Responsive Styling

Styling properties for the zoom circle are determined by the current state, eliminating the need for direct DOM manipulation. React handles rendering based on the component's dynamic state. For instance, opacity toggles between 0 and 1 depending on the isVisible flag.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for adjusting the color of a row when hovering

Can I modify the row color on hover in material-table using props? Similar to this example. Appreciate your help. ...

Navbar Growth Effect

I'm attempting to create an expanding effect in my navbar using Angular. When I click on "show information," the content should slide up. The issue is that the top part of my footer does not follow the effect. I have tried something like: <foot ...

Display the title tag when hovering over an image

Hey there! I'm looking to display the title tag of an image directly inside the image itself. Here's an example to illustrate what I mean: http://jsfiddle.net/51csqs0b/ .image { position:relative; width:200px; height:200px; } .ima ...

Releasing Typescript 2.3 Modules on NPM for Integration with Angular 4

Although there are instructions available in Writing NPM modules in Typescript, they are outdated and there are numerous conflicting answers that may not be suitable for Angular. Additionally, Jason Aden has delivered an informative presentation on youtu ...

How can I use jQuery to dynamically add and remove input values?

I am having trouble with saving and deleting values in a table row. I have an input field and an add button. When I click on the add button, I want the input value to be saved in a table row with a delete button next to it. I need to be able to delete indi ...

The Properties of a Firestore Document are not being correctly retrieved by the DocumentReference in Typescript

I am currently working on a Firebase Cloud Function that is responsible for creating a new Firestore document and then sending back the unique ID of the document to a Flutter application. My functions are written in Typescript. Below is the code snippet t ...

Whenever a new entry is made into the textfield, the onChange feature triggers a reset on the content within the textfield

I'm experiencing an issue while creating a SignUp authentication page with Firebase. Every time I try to input text in the text field, it gets reset automatically. I have been unable to identify the root cause of this problem. It seems to be related t ...

Splitting a list of accordions into two columns with CSS and React - a step-by-step guide!

I am looking for a way to showcase a list of Accordions in a two-column layout. Each Accordion consists of both a Summary and Details section, with the Details being visible only when the Summary is clicked. Unfortunately, I won't delve into the imple ...

Tips for adjusting the size of icons in Ionic Framework v4 and Angular 7

The library ngx-skycons offers a variety of icons for use in projects. If you're interested, check out the demo here. I'm currently incorporating this icon library into an Ionic project that utilizes Angular. While the icons work perfectly, I&ap ...

"My NodeJS code is returning a string instead of JSON data when using the MongoDB populate

My current project involves managing a product with multiple providers, each having its own price. The challenge I am facing is that when using populate() to retrieve provider information by ID, it returns the data as a string instead of JSON format. Below ...

Tips for modifying the value of an MUI DatePicker in Jest (x-date-pickers)

When it comes to { DatePicker } from '@mui/x-date-pickers': I'm facing a challenge in changing the value using Jest. Let me show you my custom wrapper for DatePicker called DatePickerX: import React, { useState } from 'react'; im ...

Positioning the modal toward the bottom across all screen sizes

In my current project, I am utilizing react-bootstrap modal. My goal is to position the modal at the bottom of all screen resolutions. While I have successfully achieved this by adjusting the CSS bottom property to -74% for 1080x1920 resolutions, the same ...

Angular 2 interprets my JSON object as a function

My webapp retrieves data via Json and places it in objects for display. I have successfully implemented this process twice using services and classes. However, when I recently copied and pasted the old code with some modifications to redirect to the correc ...

What is the best way to prevent a component from getting stuck in an infinite

Currently, I am working on fetching data from a SQLite database within my component. Users have the ability to insert new data into the same table of that database. I want to be able to display the newly inserted element immediately after it has been added ...

What can I do to improve the quality of the resolution?

Check out this demonstration on my website. I'm utilizing the jQuery cycle plugin to create a slider and ensuring that the photos maintain their full height and width. However, I've noticed that when I zoom in on the page, the picture doesn&apo ...

Top Margin: Supported by Chrome and Safari

After troubleshooting why the margin-top is not working on Safari, but works fine on Chrome, I discovered a discrepancy. Chrome Upon hovering over an item in the image, the cursor changes as expected. This feature operates smoothly in Chrome. https://i. ...

What is the best way to test chained function calls using sinon?

Here is the code I am currently testing: obj.getTimeSent().getTime(); In this snippet, obj.getTimeSent() returns a Date object, followed by calling the getTime() method on that Date. My attempt to stub this functionality looked like this: const timeStu ...

I'm attempting to retrieve mlab data in the console using node.js, but unfortunately encountering an error

I came across this helpful YouTube tutorial:https://www.youtube.com/watch?v=PFP0oXNNveg&t=460s. I followed the steps outlined in the video and made necessary code adjustments based on current updates found through a Google search (to resolve errors enc ...

Make the element switch from hidden to visible when hovering over it using CSS

I had a challenge to only show some rating text on my website when I hovered over it. Being new to coding with CSS and JavaScript, I was experimenting with overriding styles using the Stylebot extension in Chrome. I encountered an issue while trying to mo ...

I am unable to modify the interface in stylex within the context of next.js and react

My CSS code is not consistently displaying in the user interface. Sometimes it works fine, and other times all the styles disappear. Here is a snippet from the console: // designs.stylex.js import * as stylex from '@stylexjs/stylex'; export ...