Steps for building a link element that reveals an image when hovered over

I'm currently working on a react component that aims to replicate the functionality of Wikipedia's page preview feature minus the text. Upon hovering over the link, I want the image to appear either above or below the text.

The ImgCard component I've created includes an a link that displays the image when you hover over it. However, I feel like the component may be too complex, so any suggestions for simplification would be appreciated. The example below utilizes TailwindCSS.

Some issues encountered:

  • If there isn't enough space, the image doesn't render properly. For instance, hovering over the spongebob 5 link showcases this issue. It would be ideal if the component could intelligently determine when to display the image above the text in such cases.
  • The images consistently appear on the left side.

How can these challenges be best addressed?

const {useState} = React;

function ImgCard({ src, alt, text }) {
  const [isShown, setIsShown] = useState(false);
  return (
    <React.Fragment>
      <a
        href={src}
        className="text-blue-600 visited:text-purple-600"
        onMouseEnter={() => setIsShown(true)}
        onMouseLeave={() => setIsShown(false)}
      >
        {text}
      </a>
      {isShown && (
        <img
          className="max-w-xs rounded overflow-hidden shadow-lg absolute"
          src={src}
          alt={alt}
        />
      )}
    </React.Fragment>
  );
}

function Page(){
  return(
    <div className="max-w-xl mx-auto px-8">
    <h1 className="text-2xl text-gray-900 font-semibold">The Best Lorem Ever
    </h1>
      <div className="grid grid-cols-1 gap-4">
        <p>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit <ImgCard text="spongebob 1" src="https://i.imgur.com/TYHtyCe.png" />. Nam imperdiet magna quis consectetur gravida. Nam maximus consectetur rhoncus. Nulla facilisi. Ut convallis risus at odio euismod, <ImgCard text="spongebob 2" src="https://i.imgur.com/tnOglmb.png" /> sodales tincidunt augue bibendum. Sed hendrerit arcu ut ipsum mattis, id eleifend sem dictum. Etiam finibus elementum vulputate. Suspendisse nec sem ex.
        </p>
        <p>
 <ImgCard text="spongebob 3" src="https://i.imgur.com/m8oAgs8.jpg" /> in porttitor sapien, ac egestas libero. Suspendisse potenti. Aliquam sed tempus ligula. Praesent efficitur aliquam varius. Proin ex libero, hendrerit sit amet massa ac, mollis semper nulla. Vestibulum ultrices rhoncus metus, vel pellentesque erat iaculis in. Duis ut ligula in lacus volutpat mattis maximus nec nunc. Sed euismod tortor non mauris porta fringilla. Cras aliquam quis odio sit amet dapibus. In et eros venenatis, interdum dui at, accumsan mauris. Pro...
        </p>
        <p>
<ImgCard text="spongebob 4" src="https://i.imgur.com/vYCijFL.jpg"  /> Mauris vitae iaculis turpis, nec sodales diam. Duis euismod, velit tincidunt laoreet porta, sem nulla lobortis tellus, non fringilla ipsum mauris et massa. Pellentesque vel ante sem. Integer ut mauris aliquet dolor auctor porttitor ut eget sem. Vestibulum egestas tellus ut mi dignissim fringilla. Phasellus ut gravida quam, nec rutrum lectus. Fusce ut volutpat diam, vitae dignissim enim. Vivamus dapibus nunc eu neque porta, ut luct...
        </p>
        </div>
      </div>
  )
}

// Render it
ReactDOM.render(
  <Page />,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">

Answer №1

Great job on catching the typo! To address your second issue, consider creating a container with relative positioning in relation to the Link element. I've provided an example in a codesandbox for reference. It may be beneficial to refine your code as I am not very experienced with TailwindCSS. You can view the example here: https://codesandbox.io/s/tooltip-image-popup-lvcei Keep in mind that the image could potentially overflow the screen boundaries. A solution is to calculate the distance between the Screen Border and the Link, then adjust the Image's className to either "left-0" or "right-0" based on the image width and available space.

Answer №2

Thank you for the challenge, especially since I'm not very experienced with react. But I believe I've come up with a suitable solution.

I have implemented a function that checks onMouseOver to determine whether the <a> element is positioned at the top or bottom of the screen. Based on this, it adjusts the state of position, which in turn affects how the ImgCard component is rendered.

const {useState, useRef, useEffect} = React;

function ImgCard({ src, alt, text }) {
  const [isShown, setIsShown] = useState(false);
  //create an identifier for each element
  const inputRef = useRef();
  //set the default state for the position of an image
  const [position, setPosition] = useState("image-below");
  const [disOffCenter, setDisOffCenter] = useState(6.67);

  //check the position of the <a> tag relative to the current viewport
  const positionImg = function() {
    //"Spongebob 1" has a length of ~100px
    //I made a calculated guess that 1rem = 15px thus 100px = 6.67 rem
    //The image width = max-w-xs or 20 rem
    //The image is centered when it is (20 - 6.6) / 2 = -6.665
    let textWidth = inputRef.current.getBoundingClientRect().width / 15;
    let distanceOffCenter = (20 - textWidth) / 2;
    setDisOffCenter(distanceOffCenter);
    
    if((window.innerHeight / 2) > inputRef.current.getBoundingClientRect().y) {
       //if the <a> element is on the lower half of the screen: show the image above it
       setPosition("image-above");
    } else {
      //if the <a> element is on the top half of the screen: show the image below it
      setPosition("image-below");
    }
  
  }
  
  return (
    <React.Fragment>
      <a
        href={src}
        className="text-blue-600 visited:text-purple-600 relative"
        onMouseEnter={() => {setIsShown(true); positionImg();}}
        onMouseLeave={() => setIsShown(false)}
        ref={inputRef}
      >
        {text},
        {isShown && (
        <img
          className={"max-w-xs rounded overflow-hidden shadow-lg absolute " + position}
          style={{"margin-left" : "-" + disOffCenter + "rem"}}
          src={src}
          alt={alt}
        />
      )}
      </a>
      
    </React.Fragment>
  );
}

function Page(){
  return(
    <div className="max-w-xl mx-auto px-8">
    <h1 className="text-2xl text-gray-900 font-semibold">The Best Lorem Ever
    </h1>
      <div className="grid grid-cols-1 gap-4">
        <p>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit <ImgCard text="spongebob 1" src="https://i.imgur.com/TYHtyCe.png" />. Nam imperdiet magna quis consectetur gravida. Nam maximus consectetur rhoncus. Nulla facilisi. Ut convallis risus at odio euismod, <ImgCard text="spongebob 2" src="https://i.imgur.com/tnOglmb.png" /> sodales tincidunt augue bibendum. Sed hendrerit arcu ut ipsum mattis, id eleifend sem dictum. Etiam finibus elementum vulputate. Suspendisse nec sem ex.
        </p>
        <p>
 <ImgCard text="spongebob 3" src="https://i.imgur.com/m8oAgs8.jpg" /> in porttitor sapien, ac egestas libero. Suspendisse potenti. Aliquam sed tempus ligula. Praesent efficitur aliquam varius. Proin ex libero, hendrerit sit amet massa ac, mollis semper nulla. Vestibulum ultrices rhoncus metus, vel pellentesque erat iaculis in. Duis ut ligula in lacus volutpat mattis maximus nec nunc. Sed euismod tortor non mauris porta fringilla. Cras aliquam quis odio sit amet dapibus. In et eros venenatis, interdum dui at, accumsan mauris. Proin nec pulvinar leo. Duis in turpis vel mi cursus venenatis. Sed dapibus elit leo, sit amet ornare nisi commodo
        </p>
        <p>Lorem ipsum dolor sit amet, 
<ImgCard text="spon" src="https://i.imgur.com/vYCijFL.jpg"  /> - small text to test variable text widths. Mauris vitae iaculis turpis, nec sodales diam. Duis euismod, velit tincidunt laoreet porta, sem nulla lobortis tellus, non fringilla ipsum mauris et massa. Pellentesque vel ante sem. Integer ut mauris aliquet dolor auctor porttitor ut eget sem. Vestibulum egestas tellus ut mi dignissim fringilla. Phasellus ut gravida quam, nec rutrum lectus. Fusce ut volutpat diam, vitae dignissim enim. Vivamus dapibus nunc eu neque porta, ut luctus est venenatis. Suspendisse eu neque eget lorem feugiat pharetra auctor auctor augue. Mauris malesuada id leo sit amet maximus. Morbi egestas placerat arcu at posuere. Nam hendrerit dignissim odio, quis gravida magna pulvinar sed. Ut tincidunt elit semper eleifend rhoncus. Aliquam erat volutpat. Vestibulum malesuada luctus rutrum. <ImgCard text="spongebob 5" src="https://i.imgur.com/LthzuJn.jpg"  />
        </p>
        </div>
      </div>
  )
}

// Render it
ReactDOM.render(
  <Page />,
  document.getElementById("react")
);
/*The image is 20px above the element*/
.image-above {
  top: 1.5rem;
}

/*The image is 20px below the element*/
.image-below {
  bottom: 1.5rem;
}

.image-above, .image-below {
  z-index: 20;
}
  
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">

I trust that this solution meets your requirements. Feel free to offer any feedback or suggestions!

Edit

I have updated the code so that the image remains centered either above or below the text within the <a> tags. The logic behind this adjustment can be found in the added comments within the code:

"Spongebob 1" has a length of ~100px

A rough estimation suggests that 1rem equals 15px, making 100px equal to 6.67rem

The image width is set as max-w-xs or 20rem

The image is centered at (20 - 6.6) / 2 resulting in 6.665

The offset is -6.665rem because images typically align to the left, requiring additional left alignment.

I hope this explanation clarifies any doubts! Let me know if you need further assistance.

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

Challenges with Isotope brickwork design and Bootstrap lightbox

I am currently experimenting with using isotope's masonry version to showcase a collection of thumbnail images, paired with the bootstrap lightbox plugin to enlarge these thumbnails. The images are retrieved from my Flickr XML feed using PHP to extra ...

Link embedded in prism formatting, encased in code snippet

In the HTML template, I have the following code snippet: <pre> <code class="language-markup"> {% filter force_escape %} <Item> <MarkUp><a href="http://google.com">Google</a></MarkUp> <No ...

The functionality of an AJAX poll is not functioning properly due to issues with the PHP

I am embarking on my first website and project, hoping to see it through to completion. In my room, I have a modest site set up on a Raspberry Pi 2. Users visit this site to cast their votes of 'yes' or 'no'. However, there seems to be ...

Creating a smooth transition effect for a welcoming message using PHP upon logging in

In the given code snippet for setting a cookie on a website, the logic is such that it checks if the cookie with user's full name already exists. If it does, it immediately displays a welcome message to the user and redirects them to the home page aft ...

Tips for customizing an image within the antd upload component

Using the upload feature within an antd form component has presented me with a challenge. <Form name="posting" onFinish={onSubmitForm} scrollToFirstError encType='multipart/form-data' > <Form.Item name=&qu ...

Position one image directly on top of another without any transparency

I am looking to create a layout where one image is displayed above another image. When the user hovers over the below image, the opacity of that image should change while the above image remains fully visible. However, currently both images have reduced o ...

A secondary operation is triggered in the simulated keyboard when the enter or space key is pressed, even when it is not warranted

In my HTML, I have created a simulated keyboard with buttons that correspond to characters. When you click on these buttons, the character is added to a text element on the screen. Additionally, you can use your physical keyboard to input characters in the ...

Instructions for developing an offset plugin for an HTML5 video player

I'm currently working on developing an offset plugin that allows playing a specific portion of a video in an HTML5 video player. While there is an existing plugin for video.js available at videojs-offset plugin, I am now experimenting to adapt this p ...

What is preventing NgClass from applying the CSS styles?

I'm currently facing an issue in Angular2 where I am trying to apply different styles using ngClass within an 'NgFor' loop, but for some reason, it's not working as expected. Apologies for any language errors. <div class='line ...

Issue encountered while utilizing redux-persist v6 in conjunction with redux-persist-transform-immutable v5.0.0

Initially, I came across this specific installation issue: npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="92f1d2a2bc ...

Guidelines for submitting on button click within MaterialUI stepper

I am currently developing a Register Form utilizing MaterialUI stepper. Each step in the form corresponds to a specific component: const steps = ['Basic information', 'Profile', 'Review registration'] At the final step of the ...

Inconsistency in Firebase data updates

Hey there, my code snippet below is responsible for capturing latitude and longitude values through a drag-and-drop marker. Although the latitude and longitude are continuously updated in the console when I log them, the same doesn't seem to happen wh ...

What is the best way to preserve the allocated space in the DOM while utilizing CSS display:none?

I have explored the functionalities of display:none and visibility:hidden. However, I need to utilize display:none for a specific reason while still preserving the element's allocated space in the DOM to prevent any impact on adjacent elements. Is thi ...

The state is filled with uncertainties, followed by the next in line - the

Whenever I perform a fetch request for the details of an instrument, the response initially returns the state with undefined status before providing the object. This causes the props to first read the undefined information, leading to page errors. class D ...

Using jQuery's sortable functionality to rearrange rows in a table can cause conflicts with Angular's orderBy feature

In the past, my angular app used a table with orderBy to sort rows based on a specific column. By clicking on a table header, the orderBy arguments would change and the list would be sorted according to the values in that column. Now, I am experimenting w ...

React UseEffect does not trigger update upon deletion of data from array

I've hit a roadblock and need some assistance. I'm working on a MERN stack application that interacts with the Github API. Users can search for Github users, save them to their profile on the app, and automatically start following them on Github. ...

Tips for preventing the "Too many re-renders" error in React and avoiding infinite loops

While utilizing react typescript, redux toolkit, and material UI together, I encountered an error when calling the API: Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. at renderWithHooks () at mountIndeterminat ...

Best method for displaying a div using ASP.NET server controls

There is a concealed div containing ASP.NET server buttons. When I display the content of this div as a modal window on the page using JavaScript by copying innerHTML, the buttons do not trigger server events. Can anyone offer a solution to this issue? ...

Invoke the getSignedUrl function on the backend using Node.js, and then send the result to

Short version: How can I call a getSignedUrl function in my Node backend from the React frontend and then return the result to the frontend? In my server.js file on the backend, the code below generates a signed URL from gcloud. However, I am unsure how t ...

typescript React-Redux challenge: Boosting Your mapDispatchToProps Skills

I'm having trouble determining the type of my mapDispatchToProps function in the SignInComponent. See the code snippet below: Here is my authAction.ts file: import firebase from 'firebase/app' import { Dispatch } from 'react'; ty ...