Introduction
Have you ever wanted to create your own image search engine? In this project, I built a simple yet powerful image search engine using HTML, CSS, JavaScript, and the Unsplash API. It allows users to search for high-quality images and load more results seamlessly. Let’s dive into the details!
Features
- Search millions of images from Unsplash
- Fast & responsive UI
- Show More button to load extra results
- Works on both desktop & mobile
- Lightweight and easy to implement
Live Demo
You can check out the live version of this project here:
🔗 Live Demo
How I Built It
1. Setting Up the HTML Structure
First, I created a simple layout with an input field for search, a results section, and a Show More button.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Build a Stunning Image Search Engine Using Unsplash API in JavaScript! - Muhammad Kashif Pathan</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<div class="container">
<div class="hero">
<h1>Find the Perfect Image for Your Project</h1>
<p>Search millions of high-quality images powered by Unsplash. Just type your keyword and explore
stunning visuals in seconds!</p>
<form>
<input type="text" placeholder="Search for beautiful images...">
<img src="search-icon.svg" alt="search-icon">
</form>
<p class="discover">Discover breathtaking images for free. Start searching now!</p>
</div>
<div class="search-result">
<div id="loader" class="loader"></div>
</div>
<div class="show-more">
<button class="show-more-btn">Show More</button>
</div>
</div>
</main>
<footer>
<p>Powered by Unsplash API | Developed by Muhammad Kashif Pathan</p>
</footer>
<script src="script.js"></script>
</body>
</html>
2. Adding Styling with CSS
For a clean and modern look, I styled the page using CSS. I also designed a dark theme-friendly scrollbar.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
}
body {
color: white;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
background-color: rgb(9, 9, 9);
}
main {
width: 100%;
height: calc(100vh - 52px);
overflow: hidden auto;
text-align: center;
background-image: linear-gradient(rgba(0, 0, 0, 0.986), rgba(0, 0, 0, 0.936)),
url("bg-img.jpeg");
background-size: cover;
}
main::-webkit-scrollbar {
width: 0;
}
.container .hero {
background-image: linear-gradient(rgba(0, 0, 0, 0.968), rgba(0, 0, 0, 0.635)),
url("coding.jpg");
background-size: cover;
padding: 20px;
background-position: center;
background-size: cover;
}
.hero h1 {
margin-bottom: 20px;
background-color: rgba(10, 198, 60, 0.1);
border: 2px solid rgb(10, 198, 60);
border-radius: 50px;
padding: 10px 20px;
font-size: 16px;
display: inline-flex;
}
.hero p:nth-child(2) {
margin: 20px auto;
}
.hero form {
display: flex;
border-radius: 5px;
width: 100%;
justify-content: center;
}
.hero form input {
border: none;
outline: none;
border-radius: 10px 15px 15px 10px;
padding: 10px;
background-color: black;
color: white;
max-width: 500px;
width: 100%;
position: relative;
left: 20px;
font-size: 16px;
}
.hero form img {
padding: 10px;
cursor: pointer;
border-radius: 50%;
background-color: rgb(10, 198, 60);
border: 5px solid rgb(24, 24, 24);
position: relative;
left: -20px;
transition: all 0.8s linear;
}
.hero form img:hover {
border: 5px solid rgb(10, 198, 60);
}
.hero .discover {
margin: 20px auto;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
}
.show-more button {
border: none;
padding: 10px 20px;
cursor: pointer;
border-radius: 0 5px 5px 0;
background-color: rgb(10, 198, 60);
color: white;
font-weight: 700;
transition: background-color 0.3s ease;
}
.hero form button:hover,
.show-more button:hover {
background-color: rgb(5, 158, 28);
}
.search-result {
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-top: 20px;
padding: 20px;
}
.search-result a {
width: calc(33.333% - 20px);
max-width: 300px;
background-color: transparent;
border-radius: 5px;
overflow: hidden;
transition: all 0.3s ease;
}
.search-result a img {
width: 100%;
height: 200px;
object-fit: cover;
object-position: center;
display: block;
}
.search-result a:hover {
transform: scale(1.05);
}
.hide {
display: none;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #1e1e1e;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #555;
border-radius: 10px;
transition: all 0.3s ease;
}
::-webkit-scrollbar-thumb:hover {
background: #888;
}
::-webkit-scrollbar-thumb:active {
background: #aaa;
}
* {
scrollbar-width: thin;
scrollbar-color: #555 #1e1e1e;
}
.loader {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
border: 6px solid rgba(255, 255, 255, 0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
z-index: 1000;
background-color: transparent;
display: none;
}
.loader {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
border: 6px solid rgba(255, 255, 255, 0.3);
border-top-color: #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
display: none;
}
@keyframes spin {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.show-more {
display: flex;
justify-content: center;
}
.show-more-btn {
margin-top: 20px;
padding: 10px 20px;
border-radius: 5px;
font-size: 14px;
font-weight: 700;
display: none;
}
footer p {
font-size: 10px;
color: gray;
font-weight: 700;
padding: 20px;
height: 52px;
}
@media (max-width: 768px) {
.search-result a {
width: calc(50% - 10px);
}
}
@media (max-width: 550px) {
main {
padding: 20px 0;
}
.container .hero {
padding: 20px 10px;
}
.hero, .hero h1 {
font-size: 12px;
}
.search-result {
padding: 10px;
}
.search-result a {
min-width: 100%;
}
.search-result a img {
height: 250px;
}
}
3. Fetching Images from Unsplash API
The main functionality is powered by JavaScript and the Unsplash API. Here’s how I handled image fetching and display:
const accessKey = "YOUR_UNSPLASH_ACCESS_KEY";
const form = document.querySelector("form");
const searchInp = document.querySelector("input");
const searchResult = document.querySelector(".search-result");
const showMoreBtn = document.querySelector(".show-more-btn");
const loader = document.getElementById("loader");
const discoverText = document.querySelector(".discover");
let keyword = "";
let page = 1;
// ✅ Function to fetch images
async function searchImages() {
keyword = searchInp.value.trim();
if (!keyword) return;
// Show loader and clear previous results for a new search
if (page === 1) {
searchResult.innerHTML = "";
discoverText.textContent = "";
discoverText.style.display = "block";
}
loader.style.display = "block";
const url = `https://api.unsplash.com/search/photos?page=${page}&query=${keyword}&client_id=${accessKey}&per_page=12`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch images");
const data = await response.json();
const results = data.results;
if (results.length === 0) {
discoverText.textContent = `No images found for "${keyword}". Try a different keyword.`;
showMoreBtn.style.display = "none";
loader.style.display = "none";
return;
}
// Append new images
results.forEach((result) => {
const image = document.createElement("img");
image.src = result.urls.small;
image.alt = result.alt_description || "Search result image";
const imageLink = document.createElement("a");
imageLink.href = result.links.html;
imageLink.target = "_blank";
imageLink.appendChild(image);
searchResult.appendChild(imageLink);
});
discoverText.style.display = "none";
showMoreBtn.style.display = results.length > 0 ? "block" : "none";
} catch (error) {
console.error("Error fetching images:", error);
searchResult.innerHTML = `<p class="error-message">Something went wrong. Please try again later.</p>`;
} finally {
// Hide loader after fetching is complete
setTimeout(() => {
loader.style.display = "none";
}, 500);
}
}
form.addEventListener("submit", (e) => {
e.preventDefault();
page = 1;
searchImages();
});
showMoreBtn.addEventListener("click", () => {
page++;
searchImages();
});
Here’s how the project looks:
🔥 Final Thoughts
This project is a great way to practice working with APIs, handling asynchronous JavaScript, and creating a user-friendly UI. If you want to expand it, you can:
- Add pagination for infinite scrolling
- Implement a light/dark mode switch
- Allow users to download images directly
💡 What do you think?
I’d love to hear your feedback! If you liked this project, feel free to like, comment, and follow me for more awesome content. 🚀
🔗 GitHub Repository: GitHub
👨💻 Developed by Muhammad Kashif Pathan
🔥 Ready to build your own image search engine? Let’s discuss in the comments below!
Top comments (0)