Reproducing the View-Master Effect in Javascript
kimrodrig
Posted on October 7, 2022
I was working on a React project this week—a platform for sharing and finding sublets—based on the standard thumbnail view. As opposed to, for instance, list view (useless for apartments!) or map view. The apartment search is like dating: you want to see it before you bother to go and see it.
Each listing was rendered as an individual component. It was easy enough to integrate a thumbnail photo here; all that was needed was to call the first image in the the image array within the listing object, as saved in the db.json file I had drafted. The object looked like this:
{
"id": 3,
"title": "Sun-dappled 1 bedroom",
"description": "Clean and open 1 bed 1 bath apartment right in the middle of downtown LA. Come take a look today and make it your own!",
"location": "Los Angeles, California",
"price": 2170,
"bedrooms": 1,
"baths": 1,
"availability": "10/11",
"email": "donaldotreply@gmail.com",
"image": [
"./images/image3.jpg",
"./images/image9.jpg",
"./images/image16.jpg"
]
}
So the html component would be as simple as <img src={listing.image[0]} alt={listing.title} />
But how to get that effect where you can preview the images by flipping through them while remaining in thumbnail view? As if looking through a View-Master reel?
I decided to create two buttons, right and left, that would mount the image, using .png icons I sniffed out out online. Manually positioning them, I proceeded to create a hover effect in the .css file, so they would by default be hidden from view, and only emerge to politely greet the visiting cursor:
The code was remarkably simple:
css:
.button-container {
visibility: hidden;
}
.image-container:hover .button-container {
visibility: visible;
}
js:
<div className="image-container">
<img src={listing.image[0]} alt={listing.title} />
<div className="button-container">
<button className="right-btn"><span className='right-icon'></span></button>
<button className="left-btn"><span className='left-icon'></span></button>
</div>
</div>
But what about the actual functionality? I've always been in favor of a two-state solution. So up top I established a state for image, naturally defaulted to the first, and one for the index of the image to call, naturally defaulted to zero.
const [imageToDisplay, setImageToDisplay] = useState(listing.image[0])
const [imageToDisplayIndex, setImageToDisplayIndex] = useState(0)
Then I updated the returned html component to incorporate the state variable and requisite onClick functions:
<div className="image-container">
<img src={imageToDisplay} alt={listing.title} />
<div className="button-container">
<button onClick={handleRightClick} className="right-btn"><span className='right-icon'></span></button>
<button onClick={handleLeftClick} className="left-btn"><span className='left-icon'></span></button>
</div>
</div>
There remained only the aforementioned handle functions, which would also need to update our state variables. I composed a simple algorithm for the right click. If the length of the array is still greater than 1 + the index (or so-called "imageToDisplayIndex"), increase the index by one and update the image. Because we aren't yet at the end of our array. (We need to add 1 due to the fact that indexing begins at 0.) Otherwise, which is to say we've reached our final image, reset the counter and make our image the first one in the array.
(We also need to call stopPropagation(), since the underlying component is clickable, otherwise a click on the right-button will simply function as a click on the whole listing card.)
For the left click, the logic is the same, just reversed. If we are anywhere ahead of our first image, just decrease the index by one. However, if we're at our first image, set the index and the image to the last one in the array (in other words, the array's length - 1).
function handleRightClick(e){
e.stopPropagation()
if (listing.image.length > (imageToDisplayIndex+1)){
setImageToDisplay(listing.image[imageToDisplayIndex+1])
setImageToDisplayIndex(imageToDisplayIndex+1)
}
else{
setImageToDisplayIndex(0)
setImageToDisplay(listing.image[0])
}
}
function handleLeftClick(e){
e.stopPropagation()
if (imageToDisplayIndex > 0){
setImageToDisplay(listing.image[imageToDisplayIndex-1])
setImageToDisplayIndex(imageToDisplayIndex-1)
}
else{
setImageToDisplay(listing.image[listing.image.length-1])
setImageToDisplayIndex(listing.image.length-1)
}
}
And our result looks like this! Of course these are shamelessly stock photos; clearly they do not belong to the same property.
I'm sure there is a more efficient way to write this by availing oneself of some library somewhere. But if you're going to get into the real estate game, shouldn't you get some practice Doing It Yourself?
Posted on October 7, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024