5.4: Adding Media & Interactive Elements
Enhance your website with optimized images, videos, and interactive features to create an engaging user experience. Learn image optimization, responsive media, and how to add interactivity using forms and JavaScript.
1. Image Optimization Basics
Images are often the largest files on web pages. Optimize them for fast loading.
Image Format Selection
JPG (JPEG) - Best for photos:
✓ Photographs and complex images
✓ Millions of colors
✓ Good compression
✗ Lossy compression (quality loss)
✗ No transparency
✗ Not good for text/logos
File sizes: 50-500 KB (optimized photos)
PNG - Best for graphics with transparency:
✓ Logos, icons, illustrations
✓ Lossless compression
✓ Transparency support
✓ Sharp text and edges
✗ Larger file sizes
✗ Not ideal for photos
File sizes: 10-200 KB (icons/logos)
WebP - Modern format (best overall):
✓ Better compression than JPG/PNG
✓ Supports transparency
✓ Lossy and lossless options
✓ 25-35% smaller files
✗ Older browsers don't support it
File sizes: 30-70% smaller than JPG/PNG
SVG - Best for icons and simple graphics:
✓ Infinitely scalable (vector)
✓ Tiny file sizes
✓ CSS styleable
✓ Animatable
✗ Not good for photos
✗ Complex for detailed illustrations
File sizes: 1-20 KB (icons)
Image Optimization Tools
Online tools:
TinyPNG - PNG/JPG compression (https://tinypng.com)
Squoosh - Modern image converter (https://squoosh.app)
ImageOptim - Mac app for optimization
JPEG-Optimizer - Batch JPG optimization
Command-line tools:
# Convert to WebP (requires cwebp)
cwebp input.jpg -q 80 -o output.webp
# Optimize JPG
jpegoptim --max=85 image.jpg
# Optimize PNG
optipng -o5 image.png
Recommended Image Sizes
Portfolio images:
Hero background: 1920 × 1080 px (max 300 KB)
Project thumbnail: 800 × 600 px (max 150 KB)
Project detail: 1200 × 900 px (max 200 KB)
Profile photo: 400 × 400 px (max 50 KB)
Logo: SVG or 200 × 100 px (max 20 KB)
Icons: SVG or 32 × 32 px (max 5 KB)
2. Responsive Images
Serve different image sizes for different devices.
Using srcset for Resolution Switching
Basic responsive image:
<img
src="project-800.jpg"
srcset="
project-400.jpg 400w,
project-800.jpg 800w,
project-1200.jpg 1200w
"
sizes="(max-width: 768px) 100vw, 50vw"
alt="E-commerce platform screenshot"
>
Explanation:
src- Fallback for old browserssrcset- List of image sources with widths (400w = 400px wide)sizes- Tells browser how wide image will be displayed- Mobile (≤768px): 100% viewport width
- Desktop: 50% viewport width
- Browser chooses best image automatically
Using picture Element for Art Direction
Different crops for different screens:
<picture>
<!-- Mobile: Square crop -->
<source
media="(max-width: 768px)"
srcset="hero-mobile-400.jpg 400w,
hero-mobile-800.jpg 800w"
>
<!-- Tablet: 4:3 crop -->
<source
media="(max-width: 1024px)"
srcset="hero-tablet-800.jpg 800w,
hero-tablet-1200.jpg 1200w"
>
<!-- Desktop: Wide 16:9 crop -->
<source
srcset="hero-desktop-1200.jpg 1200w,
hero-desktop-1920.jpg 1920w"
>
<!-- Fallback -->
<img src="hero-desktop-1200.jpg" alt="Portfolio hero image">
</picture>
Modern Format with Fallback
Serve WebP to modern browsers, JPG to others:
<picture>
<!-- Modern browsers get WebP -->
<source
type="image/webp"
srcset="project-400.webp 400w,
project-800.webp 800w,
project-1200.webp 1200w"
>
<!-- Older browsers get JPG -->
<source
type="image/jpeg"
srcset="project-400.jpg 400w,
project-800.jpg 800w,
project-1200.jpg 1200w"
>
<!-- Fallback -->
<img src="project-800.jpg" alt="Project screenshot">
</picture>
3. Image Galleries with Grid/Flexbox
Simple Grid Gallery
HTML structure:
<section class="gallery">
<h2>Project Screenshots</h2>
<div class="gallery-grid">
<figure class="gallery-item">
<img src="images/screenshot-1.jpg" alt="Homepage design">
<figcaption>Homepage</figcaption>
</figure>
<figure class="gallery-item">
<img src="images/screenshot-2.jpg" alt="Product page">
<figcaption>Product Page</figcaption>
</figure>
<figure class="gallery-item">
<img src="images/screenshot-3.jpg" alt="Checkout flow">
<figcaption>Checkout</figcaption>
</figure>
<figure class="gallery-item">
<img src="images/screenshot-4.jpg" alt="Admin dashboard">
<figcaption>Admin Dashboard</figcaption>
</figure>
<figure class="gallery-item">
<img src="images/screenshot-5.jpg" alt="Mobile responsive">
<figcaption>Mobile View</figcaption>
</figure>
<figure class="gallery-item">
<img src="images/screenshot-6.jpg" alt="User profile">
<figcaption>User Profile</figcaption>
</figure>
</div>
</section>
CSS Grid layout:
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
padding: 2rem 0;
}
.gallery-item {
margin: 0;
position: relative;
overflow: hidden;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.gallery-item:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
.gallery-item img {
width: 100%;
height: 250px;
object-fit: cover;
display: block;
}
.gallery-item figcaption {
padding: 1rem;
background-color: white;
text-align: center;
font-size: 0.875rem;
color: #64748b;
}
Masonry-Style Gallery (Pinterest-like)
CSS for masonry layout:
.masonry-gallery {
column-count: 3;
column-gap: 1rem;
}
@media (max-width: 1024px) {
.masonry-gallery {
column-count: 2;
}
}
@media (max-width: 640px) {
.masonry-gallery {
column-count: 1;
}
}
.masonry-item {
break-inside: avoid;
margin-bottom: 1rem;
}
.masonry-item img {
width: 100%;
height: auto;
display: block;
border-radius: 8px;
}
4. Modal Image Viewer (Lightbox)
Create a full-screen image viewer for gallery images.
HTML Structure
<!-- Gallery item with click handler -->
<figure class="gallery-item" data-image="images/project-1-full.jpg">
<img src="images/project-1-thumb.jpg" alt="Project screenshot">
<figcaption>Click to enlarge</figcaption>
</figure>
<!-- Modal (initially hidden) -->
<div class="modal" id="image-modal">
<button class="modal-close" aria-label="Close modal">×</button>
<img src="" alt="" id="modal-image">
<div class="modal-caption" id="modal-caption"></div>
</div>
CSS for Modal
/* Modal overlay */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
z-index: 1000;
justify-content: center;
align-items: center;
padding: 2rem;
}
.modal.active {
display: flex;
}
/* Modal image */
#modal-image {
max-width: 90%;
max-height: 90%;
object-fit: contain;
animation: zoomIn 0.3s;
}
@keyframes zoomIn {
from {
transform: scale(0.8);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
/* Close button */
.modal-close {
position: absolute;
top: 1rem;
right: 1rem;
background: none;
border: none;
color: white;
font-size: 3rem;
cursor: pointer;
line-height: 1;
padding: 0.5rem;
transition: transform 0.2s;
}
.modal-close:hover {
transform: scale(1.1);
}
/* Caption */
.modal-caption {
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
color: white;
background-color: rgba(0, 0, 0, 0.7);
padding: 1rem 2rem;
border-radius: 4px;
max-width: 80%;
text-align: center;
}
JavaScript for Modal Functionality
// Get modal elements
const modal = document.getElementById('image-modal');
const modalImage = document.getElementById('modal-image');
const modalCaption = document.getElementById('modal-caption');
const closeBtn = document.querySelector('.modal-close');
// Get all gallery items
const galleryItems = document.querySelectorAll('.gallery-item');
// Add click event to each gallery item
galleryItems.forEach(item => {
item.addEventListener('click', () => {
const imageSrc = item.dataset.image;
const imageAlt = item.querySelector('img').alt;
modalImage.src = imageSrc;
modalImage.alt = imageAlt;
modalCaption.textContent = imageAlt;
modal.classList.add('active');
document.body.style.overflow = 'hidden'; // Prevent scrolling
});
});
// Close modal
function closeModal() {
modal.classList.remove('active');
document.body.style.overflow = ''; // Restore scrolling
}
closeBtn.addEventListener('click', closeModal);
// Close on outside click
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeModal();
}
});
// Close on Escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.classList.contains('active')) {
closeModal();
}
});
5. Embedding Video
HTML5 Video Element
Basic video with controls:
<video
controls
width="800"
poster="video-thumbnail.jpg"
>
<source src="videos/demo.mp4" type="video/mp4">
<source src="videos/demo.webm" type="video/webm">
<p>
Your browser doesn't support HTML5 video.
<a href="videos/demo.mp4">Download the video</a> instead.
</p>
</video>
Video with more options:
<video
controls
autoplay
muted
loop
preload="metadata"
poster="poster.jpg"
width="100%"
style="max-width: 800px;"
>
<source src="demo.mp4" type="video/mp4">
<source src="demo.webm" type="video/webm">
<!-- Subtitles/captions -->
<track
kind="subtitles"
src="subtitles-en.vtt"
srclang="en"
label="English"
>
<!-- Fallback -->
<p>Video not supported. <a href="demo.mp4">Download video</a></p>
</video>
Attributes explained:
controls- Show play/pause/volume controlsautoplay- Start playing automatically (requiresmuted)muted- No sound (required for autoplay)loop- Repeat videopreload- "none", "metadata", or "auto"poster- Thumbnail image before play
Embedding YouTube Videos
Responsive YouTube embed:
<!-- Container for responsive aspect ratio -->
<div class="video-wrapper">
<iframe
src="https://www.youtube.com/embed/VIDEO_ID"
title="Project demo video"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
CSS for responsive 16:9 video:
.video-wrapper {
position: relative;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
height: 0;
overflow: hidden;
max-width: 100%;
}
.video-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
Background Video (Hero Sections)
HTML for background video:
<section class="hero-video">
<video autoplay muted loop playsinline class="hero-video-bg">
<source src="videos/background.mp4" type="video/mp4">
<source src="videos/background.webm" type="video/webm">
</video>
<div class="hero-content">
<h1>Welcome to My Portfolio</h1>
<p>Creating exceptional digital experiences</p>
</div>
</section>
CSS for background video:
.hero-video {
position: relative;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.hero-video-bg {
position: absolute;
top: 50%;
left: 50%;
min-width: 100%;
min-height: 100%;
width: auto;
height: auto;
transform: translate(-50%, -50%);
z-index: -1;
object-fit: cover;
}
.hero-content {
position: relative;
z-index: 1;
text-align: center;
color: white;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
}
6. Audio Elements
HTML5 Audio Player
Basic audio player:
<audio controls>
<source src="audio/podcast.mp3" type="audio/mpeg">
<source src="audio/podcast.ogg" type="audio/ogg">
<p>Your browser doesn't support audio playback.</p>
</audio>
Custom styled audio player (simple):
<div class="audio-player">
<audio id="audio" src="audio/track.mp3"></audio>
<button id="play-pause" class="btn-play">
<span class="play-icon">▶</span>
<span class="pause-icon" hidden>⏸</span>
</button>
<div class="progress-bar">
<div class="progress" id="progress"></div>
</div>
<span id="time">0:00 / 0:00</span>
</div>
7. Icon Systems
SVG Icons (Inline)
Inline SVG for styling flexibility:
<!-- GitHub icon -->
<a href="https://github.com/username" class="social-link">
<svg class="icon" width="24" height="24" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
GitHub
</a>
CSS for SVG icons:
.icon {
width: 24px;
height: 24px;
fill: currentColor; /* Inherits text color */
vertical-align: middle;
transition: fill 0.3s;
}
.social-link:hover .icon {
fill: #6366f1;
}
Icon Fonts (Font Awesome)
Using Font Awesome CDN:
<!-- In <head> -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
>
<!-- In HTML -->
<a href="https://github.com/username">
<i class="fab fa-github"></i> GitHub
</a>
<a href="https://linkedin.com/in/username">
<i class="fab fa-linkedin"></i> LinkedIn
</a>
<a href="mailto:your@email.com">
<i class="fas fa-envelope"></i> Email
</a>
8. JavaScript Interactions
Smooth Scroll to Anchors
JavaScript for smooth scrolling:
// Select all anchor links
const anchorLinks = document.querySelectorAll('a[href^="#"]');
anchorLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href');
const targetElement = document.querySelector(targetId);
if (targetElement) {
targetElement.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
// Update URL without jumping
history.pushState(null, null, targetId);
}
});
});
Or use CSS-only approach:
html {
scroll-behavior: smooth;
}
Image Slider (Carousel)
HTML structure:
<div class="slider">
<button class="slider-btn slider-prev" aria-label="Previous slide">
‹
</button>
<div class="slider-track">
<div class="slide active">
<img src="images/slide-1.jpg" alt="Slide 1">
</div>
<div class="slide">
<img src="images/slide-2.jpg" alt="Slide 2">
</div>
<div class="slide">
<img src="images/slide-3.jpg" alt="Slide 3">
</div>
</div>
<button class="slider-btn slider-next" aria-label="Next slide">
›
</button>
<div class="slider-dots">
<button class="dot active" data-slide="0"></button>
<button class="dot" data-slide="1"></button>
<button class="dot" data-slide="2"></button>
</div>
</div>
JavaScript for slider:
const slides = document.querySelectorAll('.slide');
const dots = document.querySelectorAll('.dot');
const prevBtn = document.querySelector('.slider-prev');
const nextBtn = document.querySelector('.slider-next');
let currentSlide = 0;
function showSlide(index) {
// Hide all slides
slides.forEach(slide => slide.classList.remove('active'));
dots.forEach(dot => dot.classList.remove('active'));
// Wrap around if needed
if (index >= slides.length) {
currentSlide = 0;
} else if (index < 0) {
currentSlide = slides.length - 1;
} else {
currentSlide = index;
}
// Show current slide
slides[currentSlide].classList.add('active');
dots[currentSlide].classList.add('active');
}
// Next/Previous buttons
nextBtn.addEventListener('click', () => showSlide(currentSlide + 1));
prevBtn.addEventListener('click', () => showSlide(currentSlide - 1));
// Dot navigation
dots.forEach(dot => {
dot.addEventListener('click', () => {
showSlide(parseInt(dot.dataset.slide));
});
});
// Auto-advance (optional)
setInterval(() => {
showSlide(currentSlide + 1);
}, 5000); // Change slide every 5 seconds
9. Performance: Lazy Loading
Load images only when they enter the viewport.
Native Lazy Loading
Simple approach (modern browsers):
<img
src="project-1.jpg"
alt="Project screenshot"
loading="lazy"
>
For multiple images:
<!-- Eager load (above fold) -->
<img src="hero.jpg" alt="Hero" loading="eager">
<!-- Lazy load (below fold) -->
<img src="project-1.jpg" alt="Project 1" loading="lazy">
<img src="project-2.jpg" alt="Project 2" loading="lazy">
<img src="project-3.jpg" alt="Project 3" loading="lazy">
Intersection Observer (Advanced)
HTML with placeholder:
<img
class="lazy"
src="placeholder.jpg"
data-src="actual-image.jpg"
alt="Project screenshot"
>
JavaScript for lazy loading:
const lazyImages = document.querySelectorAll('img.lazy');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
img.classList.add('loaded');
observer.unobserve(img);
}
});
});
lazyImages.forEach(image => {
imageObserver.observe(image);
});
10. Practical Exercises
Exercise 4.1: Optimize Images
Optimize all portfolio images:
- Convert photos to WebP format
- Create 3 sizes (400px, 800px, 1200px)
- Compress to under 200 KB each
- Test file sizes before/after
Exercise 4.2: Implement Responsive Images
Add responsive images to project pages:
- Use
srcsetfor project thumbnails - Use
<picture>for hero images - Provide WebP with JPG fallback
- Test on different devices/browsers
Exercise 4.3: Build Image Gallery
Create a project screenshot gallery:
- Grid layout with 6+ images
- Hover effects on gallery items
- Modal viewer for full-size images
- Keyboard navigation (arrow keys, Escape)
Exercise 4.4: Add Video Demo
Embed a project demonstration video:
- Create/embed a demo video
- Make it responsive (16:9 ratio)
- Add poster image
- Ensure controls work on mobile
Exercise 4.5: Implement Lazy Loading
Add lazy loading to improve performance:
- Use
loading="lazy"on all images below fold - Test with browser DevTools Network tab
- Verify images load only when scrolling
- Measure performance improvement
11. Knowledge Check
Question 1: When should you use WebP vs JPG?
Show answer
Use WebP as primary format (25-35% smaller) with JPG fallback for older browsers. WebP supports both lossy/lossless compression and transparency, making it versatile.Question 2: What's the purpose of the srcset attribute?
Show answer
`srcset` provides multiple image sources at different resolutions, allowing browsers to choose the best image based on screen size and pixel density, improving performance and quality.Question 3: Why should autoplay videos be muted?
Show answer
Most browsers block autoplay with sound to prevent annoying users. Videos can only autoplay if muted. Also better for accessibility and user experience.Question 4: What's the benefit of lazy loading images?
Show answer
Lazy loading defers loading of off-screen images until user scrolls to them, reducing initial page load time, bandwidth usage, and improving performance metrics.Question 5: What's the difference between <img> and <picture>?
Show answer
`12. Common Media Mistakes
Unoptimized Images
Bad (huge file):
<img src="photo-from-camera-5MB.jpg" alt="Project">
<!-- 5 MB file! Page loads slowly -->
Good (optimized):
<img
src="photo-optimized-150kb.webp"
alt="Project screenshot"
loading="lazy"
>
<!-- 150 KB, WebP format, lazy loaded -->
Missing Alt Text
Bad:
<img src="project.jpg"> ✗ No alt text
<img src="project.jpg" alt=""> ✗ Empty (unless decorative)
<img src="project.jpg" alt="image"> ✗ Not descriptive
Good:
<img src="project.jpg" alt="E-commerce homepage with product grid">
Non-Responsive Videos
Bad:
<iframe width="800" height="450" src="..."></iframe>
<!-- Fixed size, breaks on mobile -->
Good:
<div class="video-wrapper">
<iframe src="..."></iframe>
</div>
<!-- Responsive 16:9 wrapper -->
13. Media Checklist
Images:
□ All images optimized (under 200 KB for photos)
□ WebP format with fallbacks
□ Responsive images with srcset
□ Descriptive alt text on all images
□ Lazy loading on below-fold images
□ SVG for logos and icons
Video:
□ Multiple formats (MP4 + WebM)
□ Poster images for videos
□ Responsive video containers
□ Autoplay only if muted
□ Controls visible and accessible
Performance:
□ Images compressed and optimized
□ Lazy loading implemented
□ Critical images loaded first
□ No unnecessarily large files
□ Fast loading on slow connections
Accessibility:
□ All images have alt text
□ Videos have captions/transcripts
□ Keyboard navigation works
□ Focus states visible
□ ARIA labels where needed
14. Key Takeaways
- Optimize images - Compress and use modern formats (WebP)
- Responsive images - Use srcset and picture elements
- Choose correct format - WebP/JPG for photos, SVG for icons
- Lazy load - Use
loading="lazy"for below-fold images - Alt text required - Descriptive text for all images
- Responsive video - Use wrapper div for 16:9 aspect ratio
- Autoplay must be muted - Browsers block autoplay with sound
- Test performance - Check file sizes and load times
- Accessibility matters - Keyboard navigation, captions, alt text
- Progressive enhancement - Fallbacks for older browsers
15. Further Resources
Image Optimization:
- Squoosh - Image compression tool
- TinyPNG - PNG/JPG optimizer
- WebP Converter
Responsive Images:
Video:
Performance:
Next Steps
Great work! You've learned how to add optimized media and interactive elements to create an engaging, performant portfolio.
In Lesson 5: Testing & Debugging, you'll learn how to test across browsers and devices, validate your code, debug issues, and ensure your site is production-ready.