Complete Progressive Image Loading Project ✅ (#9)
This commit is contained in:
@@ -2,20 +2,80 @@
|
||||
|
||||
## Description
|
||||
|
||||
A brief description of the progressive image loading project.
|
||||
Progressive Image Loading is a feature that enhances website performance and user experience by loading images progressively. This approach significantly improves perceived load time, reduces initial page weight, and provides a smoother visual experience, especially for image-heavy websites or those accessed on slower network connections.
|
||||
|
||||
## Features
|
||||
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Feature 3
|
||||
- **Low-Quality Image Placeholder (LQIP)**: Initially loads a small, blurred version of the image.
|
||||
- **Smooth Transition**: Implements a smooth fade-in effect when transitioning from LQIP to full-resolution image.
|
||||
- **Easy Integration**: Simple to integrate into existing web projects with minimal setup.
|
||||
- **Dark Mode**: Supports switching between light and dark themes for improved readability in different lighting conditions.
|
||||
- **Theme Customization**: Allows users to personalize the color scheme of the website.
|
||||
- **Responsive Design**: Adapts to different screen sizes and resolutions for optimal viewing on various devices.
|
||||
|
||||
## Technologies Used
|
||||
|
||||
- JavaScript
|
||||
- HTML
|
||||
- CSS
|
||||
- **JavaScript**: Implements timer logic and interactivity.
|
||||
- **HTML**: Structures the application's layout.
|
||||
- **CSS**: Styles the application's appearance.
|
||||
|
||||
## Setup
|
||||
|
||||
Instructions to set up and run the project.
|
||||
Follow these instructions to set up and run the Progressive Image Loading project:
|
||||
|
||||
1. **Clone the Repository**:
|
||||
```bash
|
||||
git clone https://github.com/phuchoa2001/ULTIMATE-JAVASCRIPT-PROJECT.git
|
||||
cd Performance and Optimization Projects/8-progressive_image_loading
|
||||
```
|
||||
|
||||
2. **Open the Project**:
|
||||
Open the `index.html` file in your preferred web browser to start the application.
|
||||
|
||||
3. **Open `index.html` in your web browser**:
|
||||
You can open the `index.html` file directly in your web browser by double-clicking on it or by using a live server extension in your code editor (like Live Server for VSCode).
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions to the Progressive Image Loading project are welcome. Follow these steps to contribute:
|
||||
|
||||
1. **Fork the Repository**:
|
||||
Click the "Fork" button on the repository's GitHub page to create a copy of the repository under your GitHub account.
|
||||
|
||||
2. **Clone Your Fork**:
|
||||
```bash
|
||||
git clone https://github.com/your-username/countdown-timer.git
|
||||
cd Performance and Optimization Projects/8-progressive_image_loading
|
||||
```
|
||||
|
||||
3. **Create a Branch**:
|
||||
Create a new branch for your feature or bug fix.
|
||||
```bash
|
||||
git checkout -b feature-name
|
||||
```
|
||||
|
||||
4. **Make Changes**:
|
||||
Make your changes to the codebase. Ensure your code follows the project's style guidelines.
|
||||
|
||||
5. **Commit Changes**:
|
||||
Commit your changes with a descriptive commit message.
|
||||
```bash
|
||||
git commit -m "Description of the changes"
|
||||
```
|
||||
|
||||
6. **Push Changes**:
|
||||
Push your changes to your forked repository.
|
||||
```bash
|
||||
git push origin feature-name
|
||||
```
|
||||
|
||||
7. **Create a Pull Request**:
|
||||
Open a pull request from your fork's branch to the main repository's `main` branch. Provide a clear description of your changes and the purpose of the pull request.
|
||||
|
||||
## Get in Touch
|
||||
|
||||
If you have any questions or need further assistance, feel free to open an issue on GitHub or contact us directly. Your contributions and feedback are highly appreciated!
|
||||
|
||||
---
|
||||
|
||||
Thank you for your interest in the Progressive Image Loading project. Together, we can build a more robust and feature-rich application. Happy coding!
|
||||
@@ -0,0 +1,46 @@
|
||||
import { ELEMENTS, ATTRIBUTES, CLASSES } from './constants.js';
|
||||
|
||||
import { $$, $ } from './utils.js';
|
||||
|
||||
class ProgressiveImageLoader {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initProgressiveImages();
|
||||
}
|
||||
|
||||
initProgressiveImages() {
|
||||
const progressiveImages = $$(ELEMENTS.PROGRESSIVE_IMAGE);
|
||||
progressiveImages.forEach(container => this.loadHighResolutionImage(container));
|
||||
}
|
||||
|
||||
loadHighResolutionImage(container) {
|
||||
const image = new Image();
|
||||
const previewImage = $(ELEMENTS.LOADING_IMAGE, container);
|
||||
const newImage = $(ELEMENTS.OVERLAY, container);
|
||||
|
||||
if (previewImage && previewImage.hasAttribute(ATTRIBUTES.DATA_IMAGE)) {
|
||||
image.onload = () => this.updateImageDisplay(container, newImage, image.src);
|
||||
image.onerror = () => console.error('Failed to load high-resolution image');
|
||||
const imageSrc = previewImage.getAttribute(ATTRIBUTES.DATA_IMAGE);
|
||||
image.src = imageSrc;
|
||||
} else {
|
||||
console.warn('Preview image or data-image attribute not found');
|
||||
}
|
||||
}
|
||||
|
||||
updateImageDisplay(container, element, imageSrc) {
|
||||
element.style.backgroundImage = `url(${imageSrc})`;
|
||||
|
||||
element.addEventListener('transitionend', () => {
|
||||
container.classList.add(CLASSES.LOADED);
|
||||
console.log('Image loading complete');
|
||||
}, { once: true }); // Ensure event listener is only called once
|
||||
|
||||
element.style.opacity = '1';
|
||||
}
|
||||
}
|
||||
|
||||
export default ProgressiveImageLoader;
|
||||
@@ -0,0 +1,61 @@
|
||||
import { ELEMENTS, STORAGE_KEYS, CLASSES } from './constants.js';
|
||||
|
||||
import { $ } from './utils.js';
|
||||
|
||||
class ThemeManager {
|
||||
constructor() {
|
||||
this.root = $(ELEMENTS.ROOT);
|
||||
this.lightThemeBtn = $(ELEMENTS.THEME_SWITCHER.LIGHT);
|
||||
this.darkThemeBtn = $(ELEMENTS.THEME_SWITCHER.DARK);
|
||||
this.primaryColorPicker = $(ELEMENTS.COLOR_PICKER.PRIMARY);
|
||||
this.secondaryColorPicker = $(ELEMENTS.COLOR_PICKER.SECONDARY);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupEventListeners();
|
||||
this.loadSavedTheme();
|
||||
this.loadSavedColors();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
this.lightThemeBtn.addEventListener('click', () => this.setTheme('light'));
|
||||
this.darkThemeBtn.addEventListener('click', () => this.setTheme('dark'));
|
||||
this.primaryColorPicker.addEventListener('input', (e) => this.setColor(STORAGE_KEYS.PRIMARY_COLOR, e.target.value));
|
||||
this.secondaryColorPicker.addEventListener('input', (e) => this.setColor(STORAGE_KEYS.SECONDARY_COLOR, e.target.value));
|
||||
}
|
||||
|
||||
loadSavedTheme() {
|
||||
const savedTheme = localStorage.getItem(STORAGE_KEYS.THEME);
|
||||
if (savedTheme) {
|
||||
this.setTheme(savedTheme);
|
||||
}
|
||||
}
|
||||
|
||||
loadSavedColors() {
|
||||
const savedPrimaryColor = localStorage.getItem(STORAGE_KEYS.PRIMARY_COLOR);
|
||||
const savedSecondaryColor = localStorage.getItem(STORAGE_KEYS.SECONDARY_COLOR);
|
||||
|
||||
if (savedPrimaryColor) {
|
||||
this.setColor(STORAGE_KEYS.PRIMARY_COLOR, savedPrimaryColor);
|
||||
this.primaryColorPicker.value = savedPrimaryColor;
|
||||
}
|
||||
if (savedSecondaryColor) {
|
||||
this.setColor(STORAGE_KEYS.SECONDARY_COLOR, savedSecondaryColor);
|
||||
this.secondaryColorPicker.value = savedSecondaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
setTheme(theme) {
|
||||
$(ELEMENTS.BODY).classList.toggle(CLASSES.DARK_THEME, theme === 'dark');
|
||||
localStorage.setItem(STORAGE_KEYS.THEME, theme);
|
||||
}
|
||||
|
||||
setColor(property, value) {
|
||||
this.root.style.setProperty(property, value);
|
||||
localStorage.setItem(property, value);
|
||||
}
|
||||
}
|
||||
|
||||
export default ThemeManager;
|
||||
@@ -0,0 +1,30 @@
|
||||
export const ELEMENTS = {
|
||||
ROOT: ':root',
|
||||
BODY: 'body',
|
||||
THEME_SWITCHER: {
|
||||
LIGHT: '.theme-switcher__btn--light',
|
||||
DARK: '.theme-switcher__btn--dark'
|
||||
},
|
||||
COLOR_PICKER: {
|
||||
PRIMARY: '#primaryColorPicker',
|
||||
SECONDARY: '#secondaryColorPicker'
|
||||
},
|
||||
PROGRESSIVE_IMAGE: '.progressive-image',
|
||||
LOADING_IMAGE: '.progressive-image__loading',
|
||||
OVERLAY: '.progressive-image__overlay'
|
||||
};
|
||||
|
||||
export const ATTRIBUTES = {
|
||||
DATA_IMAGE: 'data-image'
|
||||
};
|
||||
|
||||
export const CLASSES = {
|
||||
DARK_THEME: 'dark-theme',
|
||||
LOADED: 'progressive-image--loaded'
|
||||
};
|
||||
|
||||
export const STORAGE_KEYS = {
|
||||
THEME: 'theme',
|
||||
PRIMARY_COLOR: '--primary-color',
|
||||
SECONDARY_COLOR: '--secondary-color'
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export const $$ = (selector, parent = document) => parent.querySelectorAll(selector);
|
||||
export const $ = (selector, parent = document) => parent.querySelector(selector);
|
||||
@@ -0,0 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Progressive Image Loading with BEM</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
|
||||
<body class="page">
|
||||
<div class="page__container">
|
||||
<header class="header">
|
||||
<h1 class="header__title">Progressive Image Loading</h1>
|
||||
<p class="header__subtitle">Enable throttling in your dev tools to better see the effect</p>
|
||||
<div class="theme-switcher">
|
||||
<button class="theme-switcher__btn theme-switcher__btn--light">Light Theme</button>
|
||||
<button class="theme-switcher__btn theme-switcher__btn--dark">Dark Theme</button>
|
||||
</div>
|
||||
<div class="color-picker">
|
||||
<input type="color" class="color-picker__input" id="primaryColorPicker" value="#3498db">
|
||||
<input type="color" class="color-picker__input" id="secondaryColorPicker" value="#2ecc71">
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="gallery">
|
||||
<div class="gallery__item">
|
||||
<div class="progressive-image">
|
||||
<div class="progressive-image__loading"
|
||||
style="background-image: url('https://res.cloudinary.com/sourcetoad/image/upload/v1483582294/frog-sm_sg9llg.jpg')"
|
||||
data-image="https://res.cloudinary.com/sourcetoad/image/upload/v1483582295/frog-lg_ikekce.jpg">
|
||||
</div>
|
||||
<div class="progressive-image__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery__item">
|
||||
<div class="progressive-image">
|
||||
<div class="progressive-image__loading"
|
||||
style="background-image: url('https://res.cloudinary.com/sourcetoad/image/upload/v1483582294/frog-2-sm_zjphps.jpg')"
|
||||
data-image="https://res.cloudinary.com/sourcetoad/image/upload/v1483582295/frog-2-lg_vahxhg.jpg">
|
||||
</div>
|
||||
<div class="progressive-image__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery__item">
|
||||
<div class="progressive-image">
|
||||
<div class="progressive-image__loading"
|
||||
style="background-image: url('https://res.cloudinary.com/sourcetoad/image/upload/v1483582294/frog-3-sm_xsjmqo.jpg')"
|
||||
data-image="https://res.cloudinary.com/sourcetoad/image/upload/v1483582295/frog-3-lg_cgu28a.jpg">
|
||||
</div>
|
||||
<div class="progressive-image__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery__item">
|
||||
<div class="progressive-image">
|
||||
<div class="progressive-image__loading"
|
||||
style="background-image: url('https://images.unsplash.com/photo-1579380656108-f98e4df8ea62?w=200')"
|
||||
data-image="https://images.unsplash.com/photo-1579380656108-f98e4df8ea62?w=1000">
|
||||
</div>
|
||||
<div class="progressive-image__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery__item">
|
||||
<div class="progressive-image">
|
||||
<div class="progressive-image__loading"
|
||||
style="background-image: url('https://images.unsplash.com/photo-1551189014-fe516aed0e9e?w=200')"
|
||||
data-image="https://images.unsplash.com/photo-1551189014-fe516aed0e9e?w=1000">
|
||||
</div>
|
||||
<div class="progressive-image__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery__item">
|
||||
<div class="progressive-image">
|
||||
<div class="progressive-image__loading"
|
||||
style="background-image: url('https://images.unsplash.com/photo-1598537179958-687e6cc625fb?w=200')"
|
||||
data-image="https://images.unsplash.com/photo-1598537179958-687e6cc625fb?w=1000">
|
||||
</div>
|
||||
<div class="progressive-image__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery__item">
|
||||
<div class="progressive-image">
|
||||
<div class="progressive-image__loading"
|
||||
style="background-image: url('https://images.unsplash.com/photo-1598537179958-687e6cc625fb?w=200')"
|
||||
data-image="https://images.unsplash.com/photo-1598537179958-687e6cc625fb?w=1000">
|
||||
</div>
|
||||
<div class="progressive-image__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery__item">
|
||||
<div class="progressive-image">
|
||||
<div class="progressive-image__loading"
|
||||
style="background-image: url('https://images.unsplash.com/photo-1504450874802-0ba2bcd9b5ae?w=200')"
|
||||
data-image="https://images.unsplash.com/photo-1504450874802-0ba2bcd9b5ae?w=1000">
|
||||
</div>
|
||||
<div class="progressive-image__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery__item">
|
||||
<div class="progressive-image">
|
||||
<div class="progressive-image__loading"
|
||||
style="background-image: url('https://images.unsplash.com/photo-1528158222524-d4d912d2e208?w=200')"
|
||||
data-image="https://images.unsplash.com/photo-1528158222524-d4d912d2e208?w=1000">
|
||||
</div>
|
||||
<div class="progressive-image__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery__item">
|
||||
<div class="progressive-image">
|
||||
<div class="progressive-image__loading"
|
||||
style="background-image: url('https://images.unsplash.com/photo-1551189014-fe516aed0e9e?w=200')"
|
||||
data-image="https://images.unsplash.com/photo-1551189014-fe516aed0e9e?w=1000">
|
||||
</div>
|
||||
<div class="progressive-image__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="script.js" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
import ThemeManager from './assets/js/ThemeManager.js';
|
||||
import ProgressiveImageLoader from './assets/js/ProgressiveImageLoader.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ThemeManager();
|
||||
new ProgressiveImageLoader();
|
||||
});
|
||||
@@ -0,0 +1,155 @@
|
||||
:root {
|
||||
--primary-color: #3498db;
|
||||
--secondary-color: #2ecc71;
|
||||
--background-color: #ffffff;
|
||||
--text-color: #333333;
|
||||
--header-bg: #f0f0f0;
|
||||
|
||||
--fs-small: 0.875rem;
|
||||
--fs-medium: 1rem;
|
||||
--fs-large: 1.25rem;
|
||||
--fs-xlarge: 1.5rem;
|
||||
|
||||
--space-small: 0.5rem;
|
||||
--space-medium: 1rem;
|
||||
--space-large: 1.5rem;
|
||||
|
||||
--font-primary: 'Arial', sans-serif;
|
||||
|
||||
--border-radius: 4px;
|
||||
--transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.dark-theme {
|
||||
--background-color: #333333;
|
||||
--text-color: #ffffff;
|
||||
--header-bg: #222222;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-primary);
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
transition: background-color var(--transition-duration), color var(--transition-duration);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page__container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-large);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-large);
|
||||
background-color: var(--header-bg);
|
||||
padding: var(--space-medium);
|
||||
}
|
||||
|
||||
.header__title {
|
||||
font-size: var(--fs-xlarge);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.header__subtitle {
|
||||
font-size: var(--fs-medium);
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.theme-switcher {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--space-medium);
|
||||
margin-top: var(--space-large);
|
||||
}
|
||||
|
||||
.theme-switcher__btn {
|
||||
padding: var(--space-small) var(--space-medium);
|
||||
font-size: var(--fs-small);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-duration);
|
||||
}
|
||||
|
||||
.theme-switcher__btn--light {
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.theme-switcher__btn--dark {
|
||||
background-color: var(--text-color);
|
||||
color: var(--background-color);
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--space-medium);
|
||||
margin-top: var(--space-large);
|
||||
}
|
||||
|
||||
.color-picker__input {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: var(--space-large);
|
||||
}
|
||||
|
||||
.gallery__item {
|
||||
overflow: hidden;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.progressive-image {
|
||||
position: relative;
|
||||
padding-bottom: 75%; /* 4:3 Aspect Ratio */
|
||||
}
|
||||
|
||||
.progressive-image__loading,
|
||||
.progressive-image__overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.progressive-image__loading {
|
||||
filter: blur(10px);
|
||||
}
|
||||
|
||||
.progressive-image__overlay {
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-duration);
|
||||
}
|
||||
|
||||
.progressive-image--loaded .progressive-image__overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.gallery {
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
}
|
||||
|
||||
.header__title {
|
||||
font-size: var(--fs-large);
|
||||
}
|
||||
|
||||
.header__subtitle {
|
||||
font-size: var(--fs-small);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user