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)