Reputation: 994
I'm trying to create a react component that behaves much like wikipedia's page preview but without the text. When hovering over the link, I'd like the image to display either above or below the text.
I've made a ImgCard
component that displays an a
link and, while hovering, displays the image. I get this sense that I might be over-engineering the component itself, so if that's the case, please feel free to simplify it. In the example below, I'm using TailwindCSS.
Issues so far:
spongebob 5
link. In this case, it would be nice if it was smart enough to know that it needs to appear above the text.What's the best way to handle this?
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. Proin nec pulvinar leo. Duis in turpis vel mi cursus venenatis. Sed dapibus elit leo, sit amet ornare nisi commodo
</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 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")
);
<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">
Upvotes: 1
Views: 243
Reputation: 1442
first of all, thanks for the challenge as I'm not that familiar with react. However I reckon I have a proper solution.
As I have copied your code I won't explain that part. Basically I have added a function that checks onMouseOver
whether the <a>
is in the top or bottom of the screen. Then it sets the state of position
accordingly and then the ImgCard
is rendered accordingly as well.
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 with = 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">
Hope this helps! If not, please comment
I've now added the code so that the image will always be centered above or below the text inside of the <a>
-tags. It's method is best described by the comments I have added in the code:
"Spongebob 1" has a length of
~100px
I made a calculated guess that
1rem
=15px
thus100px
=6.67rem
The image width is
max-w-xs
or20rem
The image is centered when it is
(20 - 6.6) / 2
=6.665
It is
-6.665rem
because the images are normally aligned to the left, thus you have to make them even more left, if that makes sense.
Hope this helps!
Upvotes: 1
Reputation: 503
Perfect that you found the typo. The best approach to solve your second problem is to create a container with a relative positioning to the Link
element. I made a codesandbox to supply an example. You might want to improve your code, I am not really familiar with TailwindCSS
. https://codesandbox.io/s/tooltip-image-popup-lvcei
Take in consideration that the image might overflow the screen borders. To solve this you can calculate the distance between Screen Border
and Link
. Based on the Image width and the calculated space you change the Image className to left-0
or right-0
Upvotes: 1