Complete Progressive Image Loading Project (#9)

This commit is contained in:
Đặng Phúc Hòa
2024-07-05 13:58:11 +07:00
committed by GitHub
parent 82bc6c3c22
commit 7b2c44a560
8 changed files with 491 additions and 8 deletions

View File

@@ -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!

View File

@@ -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;

View File

@@ -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;

View File

@@ -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'
};

View File

@@ -0,0 +1,2 @@
export const $$ = (selector, parent = document) => parent.querySelectorAll(selector);
export const $ = (selector, parent = document) => parent.querySelector(selector);

View File

@@ -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>

View File

@@ -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();
});

View File

@@ -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);
}
}