-
Notifications
You must be signed in to change notification settings - Fork 185
Image-Search-app #351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Image-Search-app #351
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,185 @@ | ||||||
| # Image Search App | ||||||
|
|
||||||
| A modern, responsive image search application that demonstrates API integration, asynchronous JavaScript, and responsive UI design concepts. | ||||||
|
|
||||||
| ## 🚀 Features | ||||||
|
|
||||||
| - **Dynamic Image Search**: Search for images using keywords | ||||||
| - **Real-time Results**: Fetches and displays images instantly | ||||||
| - **Responsive Grid Layout**: Beautiful masonry-style grid that adapts to all screen sizes | ||||||
| - **Infinite Scroll**: Automatically loads more images as you scroll | ||||||
| - **Loading Indicators**: Visual feedback during API requests | ||||||
| - **Error Handling**: Comprehensive error handling for API failures and network issues | ||||||
| - **Full-size Image View**: Click any image to view it in full size with modal overlay | ||||||
| - **Modern UI**: Glass morphism design with smooth animations and transitions | ||||||
|
|
||||||
| ## 🛠 Technologies Demonstrated | ||||||
|
|
||||||
| - **Fetch API & Async/Await**: Modern JavaScript for API requests | ||||||
| - **DOM Manipulation**: Dynamic content creation and updates | ||||||
| - **CSS Grid & Flexbox**: Responsive layout design | ||||||
| - **CSS Animations**: Smooth transitions and loading states | ||||||
| - **Event Handling**: Search, scroll, and click interactions | ||||||
| - **Error Handling**: Try-catch blocks and user-friendly error messages | ||||||
| - **Responsive Design**: Mobile-first approach with media queries | ||||||
|
|
||||||
| ## 📁 Project Structure | ||||||
|
|
||||||
| ``` | ||||||
| Image-Search-App/ | ||||||
| ├── index.html # Main HTML structure | ||||||
| ├── styles.css # Responsive CSS with modern design | ||||||
| └── script.js # JavaScript logic and API integration | ||||||
| ``` | ||||||
|
|
||||||
| ## 🔧 Setup Instructions | ||||||
|
|
||||||
| 1. **Get Unsplash API Key**: | ||||||
| - Visit [Unsplash Developers](https://unsplash.com/developers) | ||||||
| - Create a free account and register your application | ||||||
| - Copy your Access Key | ||||||
|
|
||||||
| 2. **Configure API Key**: | ||||||
| - Open `script.js` | ||||||
| - Replace `YOUR_UNSPLASH_ACCESS_KEY` with your actual API key: | ||||||
| ```javascript | ||||||
| this.API_KEY = 'your_actual_api_key_here'; | ||||||
| ``` | ||||||
|
|
||||||
| 3. **Serve locally & open in browser**: | ||||||
| Modern browsers may block some fetch requests when opening files directly from disk. It's recommended to serve the folder with a simple static server and open http://localhost:8000. | ||||||
|
|
||||||
| Using Python 3 (recommended): | ||||||
| ```bash | ||||||
| cd examples/Image-Search-App | ||||||
| python3 -m http.server 8000 | ||||||
| # then open http://localhost:8000 | ||||||
| ``` | ||||||
|
|
||||||
| Or use VS Code Live Server or `npx http-server`. | ||||||
|
|
||||||
| ## 🎯 API Integration Details | ||||||
|
|
||||||
| The app uses the Unsplash Search Photos API: | ||||||
|
|
||||||
| ```javascript | ||||||
| // Example API call structure | ||||||
| const response = await fetch('https://api.unsplash.com/search/photos', { | ||||||
| headers: { | ||||||
| 'Authorization': `Client-ID ${API_KEY}` | ||||||
| } | ||||||
| }); | ||||||
| ``` | ||||||
|
|
||||||
| **Parameters used:** | ||||||
| - `query`: Search term (e.g., "nature", "technology") | ||||||
| - `page`: Page number for pagination | ||||||
| - `per_page`: Number of results (12 per page) | ||||||
| - `orientation`: "landscape" for consistent layout | ||||||
|
|
||||||
| ## 💡 Key Code Concepts | ||||||
|
|
||||||
| ### Async/Await Pattern | ||||||
| ```javascript | ||||||
| async performSearch(query) { | ||||||
| try { | ||||||
| const images = await this.fetchImages(query, page); | ||||||
| this.displayImages(images); | ||||||
| } catch (error) { | ||||||
| this.handleSearchError(error); | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ### Error Handling | ||||||
| ```javascript | ||||||
| if (!response.ok) { | ||||||
| throw new Error(`API Error: ${response.status} ${response.statusText}`); | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ### Infinite Scroll Implementation | ||||||
| ```javascript | ||||||
| shouldLoadMoreImages() { | ||||||
| const scrollTop = window.pageYOffset; | ||||||
| const windowHeight = window.innerHeight; | ||||||
| const documentHeight = document.documentElement.scrollHeight; | ||||||
|
|
||||||
| return scrollTop + windowHeight >= documentHeight - 1000; | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ### Responsive CSS Grid | ||||||
|
||||||
| ```css | ||||||
| .image-grid { | ||||||
| display: grid; | ||||||
| grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); | ||||||
| gap: 2rem; | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## 🎨 Responsive Design Features | ||||||
|
|
||||||
| - **Desktop**: 4-5 columns with larger images | ||||||
| - **Tablet**: 2-3 columns with medium images | ||||||
| - **Mobile**: Single column with optimized spacing | ||||||
| - **Touch-friendly**: Large buttons and touch targets | ||||||
| - **Performance**: Lazy loading and optimized images | ||||||
|
|
||||||
| ## 🚨 Error Scenarios Handled | ||||||
|
|
||||||
| 1. **Invalid API Key**: Clear message to configure API key | ||||||
| 2. **Network Errors**: Retry functionality | ||||||
| 3. **API Quota Exceeded**: Informative message about limits | ||||||
| 4. **No Results Found**: Helpful suggestions for better search terms | ||||||
| 5. **Empty Search Query**: Validation with user feedback | ||||||
|
|
||||||
| ## 🔍 Search Suggestions | ||||||
|
|
||||||
| Try these search terms to see the app in action: | ||||||
| - nature | ||||||
| - technology | ||||||
| - animals | ||||||
| - architecture | ||||||
| - food | ||||||
| - travel | ||||||
| - abstract | ||||||
| - minimal | ||||||
|
|
||||||
| ## 🌟 Bonus Features Implemented | ||||||
|
|
||||||
| ✅ **Infinite Scroll**: Loads more images automatically | ||||||
| ✅ **Loading Indicators**: Visual feedback during requests | ||||||
| ✅ **Full-size Image Modal**: Click any image for larger view | ||||||
| ✅ **Keyboard Navigation**: Enter to search, Escape to close modal | ||||||
| ✅ **Smooth Animations**: Fade-in effects and hover states | ||||||
| ✅ **Accessibility**: Proper focus states and ARIA attributes | ||||||
|
||||||
| ✅ **Accessibility**: Proper focus states and ARIA attributes | |
| ✅ **Accessibility**: Some focus states and ARIA attributes; further enhancements (e.g., search input label, modal focus trap) pending |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,84 @@ | ||||||||||||||||||||
| <!DOCTYPE html> | ||||||||||||||||||||
| <html lang="en"> | ||||||||||||||||||||
| <head> | ||||||||||||||||||||
| <meta charset="UTF-8"> | ||||||||||||||||||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||||||||||||||||
| <title>Image Search App</title> | ||||||||||||||||||||
| <link rel="stylesheet" href="style.css"> | ||||||||||||||||||||
| <link rel="preconnect" href="https://fonts.googleapis.com"> | ||||||||||||||||||||
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | ||||||||||||||||||||
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | ||||||||||||||||||||
| </head> | ||||||||||||||||||||
| <body> | ||||||||||||||||||||
| <div class="container"> | ||||||||||||||||||||
| <header class="header"> | ||||||||||||||||||||
| <h1 class="title">Image Search</h1> | ||||||||||||||||||||
| <p class="subtitle">Discover amazing images from around the world</p> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <div class="search-container"> | ||||||||||||||||||||
| <div class="search-wrapper"> | ||||||||||||||||||||
| <svg class="search-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||||||||||||||||||||
| <circle cx="11" cy="11" r="8"></circle> | ||||||||||||||||||||
| <path d="m21 21-4.35-4.35"></path> | ||||||||||||||||||||
| </svg> | ||||||||||||||||||||
| <input | ||||||||||||||||||||
| type="text" | ||||||||||||||||||||
| id="searchInput" | ||||||||||||||||||||
| placeholder="Search for images... (e.g., nature, technology, animals)" | ||||||||||||||||||||
| class="search-input" | ||||||||||||||||||||
| autocomplete="off" | ||||||||||||||||||||
| > | ||||||||||||||||||||
|
Comment on lines
+24
to
+30
|
||||||||||||||||||||
| <button id="searchButton" class="search-button"> | ||||||||||||||||||||
| Search | ||||||||||||||||||||
| </button> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
| </header> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <main class="main-content"> | ||||||||||||||||||||
| <div id="loadingIndicator" class="loading-indicator"> | ||||||||||||||||||||
| <div class="spinner"></div> | ||||||||||||||||||||
| <p>Searching for images...</p> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <div id="errorMessage" class="error-message" style="display: none;"> | ||||||||||||||||||||
| <div class="error-content"> | ||||||||||||||||||||
| <svg class="error-icon" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | ||||||||||||||||||||
| <circle cx="12" cy="12" r="10"></circle> | ||||||||||||||||||||
| <line x1="15" y1="9" x2="9" y2="15"></line> | ||||||||||||||||||||
| <line x1="9" y1="9" x2="15" y2="15"></line> | ||||||||||||||||||||
| </svg> | ||||||||||||||||||||
| <h3>Oops! Something went wrong</h3> | ||||||||||||||||||||
| <p id="errorText">Please check your internet connection and try again.</p> | ||||||||||||||||||||
| <button id="retryButton" class="retry-button">Try Again</button> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <div id="noResults" class="no-results" style="display: none;"> | ||||||||||||||||||||
| <div class="no-results-content"> | ||||||||||||||||||||
| <svg class="no-results-icon" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> | ||||||||||||||||||||
| <circle cx="11" cy="11" r="8"></circle> | ||||||||||||||||||||
| <path d="m21 21-4.35-4.35"></path> | ||||||||||||||||||||
| <path d="M11 8a3 3 0 1 1 0 6"></path> | ||||||||||||||||||||
| </svg> | ||||||||||||||||||||
| <h3>No images found</h3> | ||||||||||||||||||||
| <p>Try searching for something else or use different keywords.</p> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <div id="imageGrid" class="image-grid"> | ||||||||||||||||||||
| <!-- Images will be dynamically inserted here --> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
|
|
||||||||||||||||||||
| <div id="loadMoreContainer" class="load-more-container" style="display: none;"> | ||||||||||||||||||||
| <button id="loadMoreButton" class="load-more-button"> | ||||||||||||||||||||
| <div class="load-more-spinner"></div> | ||||||||||||||||||||
| Load More Images | ||||||||||||||||||||
| </button> | ||||||||||||||||||||
|
Comment on lines
+74
to
+77
|
||||||||||||||||||||
| <button id="loadMoreButton" class="load-more-button"> | |
| <div class="load-more-spinner"></div> | |
| Load More Images | |
| </button> | |
| <button id="loadMoreButton" class="load-more-button" aria-busy="false"> | |
| <div class="load-more-spinner" aria-hidden="true"></div> | |
| Load More Images | |
| </button> | |
| <span id="loadMoreStatus" class="visually-hidden" aria-live="polite"></span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filename listed as styles.css but the actual file added is style.css; update the README to match the real filename to avoid confusion.