Building an Unsplash Slide Show on NeoCities
Tags: javascript, unsplash
Published: 2023-07-07 18:53:04

[Unsplash](https://unsplash.com/) is a great website that offers free images in a variety of formats and subjects. When we first went into lockdown in 2020, my home setup was two screens - my laptop, for actual work, and an external screen which is where my video calls took place, so I could still reference my work screen at the same time. 

However, while a call wasn't taking place, my top screen just kind of sat there, blank. I wanted something to look at during thinking breaks. Basically I wanted a screen saver, but you can't get those going on just one screen.

Way back in the day, I had a number of personal pages on [GeoCities](https://en.wikipedia.org/wiki/GeoCities). This revolutionary site allowed you to write your own HTML and have it hosted for free! Turns out, there's a modern version of this, called [NeoCities](https://neocities.org/). It allows you to create simple sites with static files that are hosted for free.

I used [this post](https://codepen.io/brianhaferkamp/post/full-screen-unsplash-slideshow) to make use of the `source.unsplash.com` endpoint to show a random, full-page image on my browser which I made full-screen on the external monitor. (I use Firefox for my browsing, so I just stuck Chrome up there for the slide show). I also made use of [finicky](https://github.com/johnste/finicky) so that all Google Meet links went to Chrome, while all other links stayed with Firefox.

Problem! A couple of weeks ago, Unsplash removed the `source.unsplash.com` endpoint. Instead, you need to use their API directly. Thankfully, the API is still free for low levels of usage, so I was able to modify the site to work with it.

## The Design

Once you sign up, you'll get an API key. For these purposes, I didn't care too much about exposing it in a static site since I don't have any payment info on Unsplash and I'm not using it for anything else.

The overall design is as follows:
* We want a "target" image div which will hold the image.
* We will fetch 30 images (the max we can) from the API in the collection we're interested in.
* We will loop through those images and show a new one once per minute.
* Once we've exhausted the ones we have, we'll fetch more. This means we should in general only be making 2 API requests per hour.
* If I don't like the image showing, I want an easy way to skip to the next one. I want to add a "refresh" button that will do that for me.

I'm going to keep this super simple. The only dependency I have is jQuery which I'm using for animations. You could easily do this with CSS animations, but I'm not as used to those.

## The Implementation

First, here's the HTML:

```html
<head>
<style>
* {
  box-sizing: border-box;
}

body {  margin: 0; background-color: black;}
  
.trigger {  position: fixed; top: 2rem; right: 2rem;}
  a { font-size: 2rem; color: white; transition: color 200ms ease; }
  a:hover { color: rgba(white, 0.7) }
  
#target {background-size: cover; background-position: center; background-repeat: no-repeat; height: 100%; width: 100%; }
    
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css"></link>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<div class="trigger">
  <a style="cursor: pointer;" id="refresh"><i class="zmdi zmdi-refresh-alt"></i></a>
    </div>

    <div id="target"></div>
</body>
```

Now for the JavaScript.
```javascript
// control the auto refresh rate -> 1000 = 1 second
window.refreshRate = 60000;
// you can change the collections ID to switch these out. Or you can use any other query from the API if you want truly random images
window.url = "https://api.unsplash.com/photos/random?w=1600&h=900&collections=879001&count=30&client_id=YOUR_API_KEY&orientation=landscape"
// use this to cache our data
window.jsonData = null;

// fetch the data, put it into the JSON blob, and pre-load the images from the data
async function fetchData() {
  const response = await fetch(window.url); 
  window.jsonData = await response.json();
  window.jsonData.forEach((image) => {
    const img = new Image();
    img.url = image.urls.full;
  });
}

// display the next image
async function nextImage() {
  const image = window.jsonData?.pop()
  if (image) { // we have more in the array
    const imageURL = image.urls.full
    $('#target').fadeOut(function() {
      $('#target').css({'background-image' : `url(${imageURL})`
    }).fadeIn(1500)
      });
  }
  else { // we've exhausted the array, go get more
    await fetchData();
    await nextImage();
  }
}

window.intervalTimer = setInterval(nextImage, refreshRate);
setTimeout(nextImage, 0);

$('#refresh').click(async function() {
  clearInterval(window.intervalTimer);
  await nextImage();
  window.intervalTimer = setInterval(nextImage, refreshRate);
});

// Hide the images on load
$('#target').hide();
```

Et voila! Hope someone out there finds this helpful!