Add Interactivity to Your Profile Page
Learning Objectives
- Master the concepts in this lesson
- Apply knowledge through practice
- Build practical skills
- Prepare for next topics
Assignment Overview
In this assignment, you will enhance your personal profile page by adding JavaScript interactivity. You'll learn how to make your page dynamic, responsive to user actions, and more engaging. This exercise will help you apply fundamental JavaScript concepts to create a more interactive and user-friendly web experience.
George Polya's 4-Step Problem Solving Method
Step 1: Understand the Problem
We need to add interactivity to our existing profile page using JavaScript. This means we should:
- Identify areas where interactivity would enhance user experience
- Determine which JavaScript techniques to use (event handling, DOM manipulation, etc.)
- Ensure the interactivity is meaningful and not just decorative
- Make sure the page remains functional even if JavaScript is disabled
- Consider user experience and accessibility
The goal is to transform our static profile page into an interactive experience that engages visitors and showcases our JavaScript skills.
Step 2: Devise a Plan
Let's implement several interactive features for different sections of our profile page:
- Interactive Navigation
- Smooth scrolling to sections
- Highlight active navigation items
- Create a collapsible mobile menu
- Dynamic Header
- Add a typing effect for a welcome message
- Implement a theme toggler (light/dark mode)
- Skills Section Enhancement
- Animated skill bars
- Filterable skills categories
- Projects Showcase
- Image slider/carousel for projects
- Filter projects by category
- Modal windows for project details
- Contact Form Validation
- Real-time form validation
- Submit form with AJAX (simulated)
- Show success/error messages
Step 3: Execute the Plan
Let's implement these interactive features one by one:
Solution: Adding Interactivity to Your Profile Page
File Structure
To keep our code organized, we'll use the following file structure:
personal_profile/
├── index.html # Main HTML file
├── css/
│ ├── styles.css # Main CSS file
│ └── dark-theme.css # Dark theme styles (optional)
├── js/
│ ├── main.js # Main JavaScript file
│ ├── navigation.js # Navigation functionality
│ ├── skills.js # Skills section interactivity
│ ├── projects.js # Projects section interactivity
│ └── contact.js # Contact form validation
└── images/ # Image assets
Feature 1: Interactive Navigation
HTML Structure
First, ensure your navigation is properly structured:
JavaScript Implementation (navigation.js)
// Wait for DOM to load completely
document.addEventListener('DOMContentLoaded', function() {
// Mobile Navigation Toggle
const navToggle = document.getElementById('nav-toggle');
const navLinks = document.getElementById('nav-links');
if (navToggle) {
navToggle.addEventListener('click', function() {
// Toggle the 'nav-active' class on the nav links
navLinks.classList.toggle('nav-active');
// Toggle the 'toggle-active' class on the toggle button
navToggle.classList.toggle('toggle-active');
// Toggle aria-expanded attribute for accessibility
const expanded = navToggle.getAttribute('aria-expanded') === 'true' || false;
navToggle.setAttribute('aria-expanded', !expanded);
});
}
// Smooth Scrolling
const links = document.querySelectorAll('.nav-links a');
links.forEach(link => {
link.addEventListener('click', function(e) {
// Get the target section id from the href
const targetId = this.getAttribute('href');
// Only apply smooth scroll for internal links
if (targetId.startsWith('#')) {
e.preventDefault();
const targetSection = document.querySelector(targetId);
if (targetSection) {
// Close mobile menu if open
if (navLinks.classList.contains('nav-active')) {
navLinks.classList.remove('nav-active');
navToggle.classList.remove('toggle-active');
}
// Scroll to the target section smoothly
window.scrollTo({
top: targetSection.offsetTop - 70, // Adjust for header height
behavior: 'smooth'
});
// Update active link
updateActiveLink(this);
}
}
});
});
// Update active navigation link based on scroll position
window.addEventListener('scroll', function() {
// Debounce scroll event for better performance
if (!window.requestAnimationFrame) {
// For browsers that don't support requestAnimationFrame
setTimeout(highlightNavOnScroll, 300);
} else {
if (!this.ticking) {
this.ticking = true;
window.requestAnimationFrame(() => {
highlightNavOnScroll();
this.ticking = false;
});
}
}
});
function highlightNavOnScroll() {
// Get all sections
const sections = document.querySelectorAll('section[id]');
// Get current scroll position
const scrollPosition = window.scrollY + 100; // Adding offset
// Find the current section
sections.forEach(section => {
const sectionTop = section.offsetTop;
const sectionHeight = section.offsetHeight;
const sectionId = section.getAttribute('id');
if (scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight) {
// Find the corresponding navigation link
const activeLink = document.querySelector(`.nav-links a[href="#${sectionId}"]`);
if (activeLink) {
updateActiveLink(activeLink);
}
}
});
}
function updateActiveLink(activeLink) {
// Remove active class from all links
links.forEach(link => {
link.classList.remove('active');
});
// Add active class to current link
activeLink.classList.add('active');
}
});
CSS Styling for Navigation
/* Navigation Styles */
.main-nav {
position: fixed;
top: 0;
left: 0;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 5%;
background-color: #fff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
transition: all 0.3s ease;
}
.nav-brand a {
font-size: 1.5rem;
font-weight: bold;
text-decoration: none;
color: #333;
}
.nav-links {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.nav-links li {
margin-left: 30px;
}
.nav-links a {
text-decoration: none;
color: #555;
font-weight: 500;
position: relative;
transition: color 0.3s ease;
}
.nav-links a:hover,
.nav-links a.active {
color: #0066cc;
}
.nav-links a::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 0;
height: 2px;
background-color: #0066cc;
transition: width 0.3s ease;
}
.nav-links a:hover::after,
.nav-links a.active::after {
width: 100%;
}
.nav-toggle {
display: none;
background: none;
border: none;
cursor: pointer;
width: 30px;
height: 24px;
position: relative;
z-index: 1001;
}
.nav-toggle span {
display: block;
width: 100%;
height: 3px;
background-color: #333;
margin: 5px 0;
transition: all 0.3s ease;
}
.toggle-active span:nth-child(1) {
transform: rotate(45deg) translate(5px, 5px);
}
.toggle-active span:nth-child(2) {
opacity: 0;
}
.toggle-active span:nth-child(3) {
transform: rotate(-45deg) translate(7px, -7px);
}
/* Responsive Navigation */
@media (max-width: 768px) {
.nav-toggle {
display: block;
}
.nav-links {
position: absolute;
top: 70px;
right: -100%;
flex-direction: column;
width: 100%;
background-color: #fff;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
transition: right 0.3s ease;
padding: 20px 0;
}
.nav-active {
right: 0;
}
.nav-links li {
margin: 10px 0;
text-align: center;
}
}
Explanation:
This implementation adds three key interactive features to our navigation:
- Mobile-Responsive Menu: The hamburger toggle button shows/hides the navigation menu on mobile devices.
- Smooth Scrolling: When clicking navigation links, the page smoothly scrolls to the target section instead of jumping abruptly.
- Active Link Highlighting: The current section is highlighted in the navigation, both when clicked and when scrolling through the page.
The code uses event listeners to respond to user actions (clicks and scrolls) and updates the DOM accordingly. We've also added CSS transitions for smooth visual effects.
Feature 2: Dynamic Header with Typing Effect and Theme Toggle
HTML Structure
Dark Mode
Your Name
|
JavaScript Implementation (main.js)
// Wait for DOM to load completely
document.addEventListener('DOMContentLoaded', function() {
// Typing Effect
const typingText = document.getElementById('typing-text');
const phrases = [
'Web Developer',
'UI/UX Enthusiast',
'Problem Solver',
'Continuous Learner'
];
let phraseIndex = 0;
let charIndex = 0;
let isDeleting = false;
let typingSpeed = 100; // Base typing speed in ms
function typeEffect() {
const currentPhrase = phrases[phraseIndex];
if (isDeleting) {
// Remove a character
typingText.textContent = currentPhrase.substring(0, charIndex - 1);
charIndex--;
typingSpeed = 50; // Faster when deleting
} else {
// Add a character
typingText.textContent = currentPhrase.substring(0, charIndex + 1);
charIndex++;
typingSpeed = 100; // Slower when typing
}
// If phrase is complete, wait longer then start deleting
if (!isDeleting && charIndex === currentPhrase.length) {
isDeleting = true;
typingSpeed = 1000; // Pause at the end of phrase
}
// If deletion is complete, move to next phrase
else if (isDeleting && charIndex === 0) {
isDeleting = false;
phraseIndex = (phraseIndex + 1) % phrases.length;
typingSpeed = 500; // Pause before starting new phrase
}
// Continue the typing effect
setTimeout(typeEffect, typingSpeed);
}
// Start the typing effect if the element exists
if (typingText) {
setTimeout(typeEffect, 1000); // Start after 1 second
}
// Theme Toggler
const themeSwitch = document.getElementById('theme-switch');
const themeLabel = document.querySelector('.theme-label');
// Check if user has a preferred theme stored
const currentTheme = localStorage.getItem('theme');
if (currentTheme) {
document.documentElement.setAttribute('data-theme', currentTheme);
if (currentTheme === 'dark') {
themeSwitch.checked = true;
themeLabel.textContent = 'Light Mode';
}
}
// Handle theme switch
if (themeSwitch) {
themeSwitch.addEventListener('change', function(e) {
if (e.target.checked) {
document.documentElement.setAttribute('data-theme', 'dark');
localStorage.setItem('theme', 'dark');
themeLabel.textContent = 'Light Mode';
} else {
document.documentElement.setAttribute('data-theme', 'light');
localStorage.setItem('theme', 'light');
themeLabel.textContent = 'Dark Mode';
}
});
}
});
CSS Styling for Theme Toggle and Typing Effect
/* Root Variables for Theming */
:root {
--primary-color: #0066cc;
--secondary-color: #004080;
--text-color: #333;
--background-color: #fff;
--card-bg-color: #f9f9f9;
--border-color: #ddd;
--shadow-color: rgba(0, 0, 0, 0.1);
}
/* Dark Theme Variables */
[data-theme="dark"] {
--primary-color: #0099ff;
--secondary-color: #66c2ff;
--text-color: #f5f5f5;
--background-color: #121212;
--card-bg-color: #1e1e1e;
--border-color: #444;
--shadow-color: rgba(0, 0, 0, 0.3);
}
/* Apply theme variables */
body {
background-color: var(--background-color);
color: var(--text-color);
transition: all 0.3s ease;
}
/* Theme Switch Styles */
.theme-switch-wrapper {
position: absolute;
top: 20px;
right: 20px;
display: flex;
align-items: center;
}
.theme-switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
margin-right: 10px;
}
.theme-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
}
input:checked + .slider {
background-color: var(--primary-color);
}
input:focus + .slider {
box-shadow: 0 0 1px var(--primary-color);
}
input:checked + .slider:before {
transform: translateX(26px);
}
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.theme-label {
font-size: 0.9rem;
color: var(--text-color);
}
/* Typing Effect Styles */
.typing-container {
height: 2rem;
margin: 10px 0 20px;
text-align: center;
}
.typing-text {
font-size: 1.5rem;
color: var(--primary-color);
font-weight: 500;
}
.cursor {
font-weight: bold;
font-size: 1.5rem;
color: var(--primary-color);
animation: blink 1s infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
Explanation:
This implementation adds two dynamic features to our header:
- Typing Effect: Creates a typing animation that cycles through different phrases. This adds visual interest and can highlight different skills or roles.
- Theme Toggle: Allows users to switch between light and dark themes. The preference is saved in localStorage so it persists between visits.
The typing effect works by gradually adding and removing characters from the target element. We use CSS variables for theming, which makes it easy to switch between color schemes by changing a single data-attribute on the root element.
Feature 3: Animated Skills Bars with Filtering
HTML Structure
<!-- Skills Section -->
<section id="skills">
<div class="container">
<h2 class="section-title">Skills</h2>
<div class="skills-filter">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="frontend">Frontend</button>
<button class="filter-btn" data-filter="backend">Backend</button>
<button class="filter-btn" data-filter="other">Other</button>
</div>
<div class="skills-container">
<!-- Frontend Skills -->
<div class="skill-item" data-category="frontend">
<div class="skill-info">
<span class="skill-name">HTML5</span>
<span class="skill-percentage">90%</span>
</div>
<div class="skill-bar">
<div class="skill-progress" data-progress="90"></div>
</div>
</div>
<div class="skill-item" data-category="frontend">
<div class="skill-info">
<span class="skill-name">CSS3</span>
<span class="skill-percentage">85%</span>
</div>
<div class="skill-bar">
<div class="skill-progress" data-progress="85"></div>
</div>
</div>
<div class="skill-item" data-category="frontend">
<div class="skill-info">
<span class="skill-name">JavaScript</span>
<span class="skill-percentage">75%</span>
</div>
<div class="skill-bar">
<div class="skill-progress" data-progress="75"></div>
</div>
</div>
<!-- Backend Skills -->
<div class="skill-item" data-category="backend">
<div class="skill-info">
<span class="skill-name">PHP</span>
<span class="skill-percentage">70%</span>
</div>
<div class="skill-bar">
<div class="skill-progress" data-progress="70"></div>
</div>
</div>
<div class="skill-item" data-category="backend">
<div class="skill-info">
<span class="skill-name">MySQL</span>
<span class="skill-percentage">65%</span>
</div>
<div class="skill-bar">
<div class="skill-progress" data-progress="65"></div>
</div>
</div>
<!-- Other Skills -->
<div class="skill-item" data-category="other">
<div class="skill-info">
<span class="skill-name">Photoshop</span>
<span class="skill-percentage">60%</span>
</div>
<div class="skill-bar">
<div class="skill-progress" data-progress="60"></div>
</div>
</div>
<div class="skill-item" data-category="other">
<div class="skill-info">
<span class="skill-name">SEO</span>
<span class="skill-percentage">50%</span>
</div>
<div class="skill-bar">
<div class="skill-progress" data-progress="50"></div>
</div>
</div>
</div>
</div>
</section>
JavaScript Implementation (skills.js)
// Wait for DOM to load completely
document.addEventListener('DOMContentLoaded', function() {
// Animate skill bars when they come into view
const skillSection = document.getElementById('skills');
const skillBars = document.querySelectorAll('.skill-progress');
let animated = false;
// Check if skill bars should be animated
function checkSkillsView() {
if (isInViewport(skillSection) && !animated) {
animateSkillBars();
animated = true;
}
}
// Animate all skill bars
function animateSkillBars() {
skillBars.forEach(bar => {
const progress = bar.getAttribute('data-progress') + '%';
// Set a small delay for each bar to create a sequential effect
setTimeout(() => {
bar.style.width = progress;
}, 100 * Array.from(skillBars).indexOf(bar));
});
}
// Check if an element is in the viewport
function isInViewport(element) {
if (!element) return false;
const rect = element.getBoundingClientRect();
return (
rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.bottom >= 0
);
}
// Filter skills by category
const filterButtons = document.querySelectorAll('.filter-btn');
const skillItems = document.querySelectorAll('.skill-item');
filterButtons.forEach(button => {
button.addEventListener('click', function() {
// Remove active class from all buttons
filterButtons.forEach(btn => btn.classList.remove('active'));
// Add active class to clicked button
this.classList.add('active');
// Get the filter value
const filterValue = this.getAttribute('data-filter');
// Show/hide skill items based on filter
skillItems.forEach(item => {
if (filterValue === 'all' || item.getAttribute('data-category') === filterValue) {
item.style.display = 'block';
// Reset animation to make it run again
const progressBar = item.querySelector('.skill-progress');
progressBar.style.width = '0';
// Trigger reflow to restart animation
void progressBar.offsetWidth;
// Animate after a small delay
setTimeout(() => {
progressBar.style.width = progressBar.getAttribute('data-progress') + '%';
}, 100);
} else {
item.style.display = 'none';
}
});
});
});
// Check skills on scroll
window.addEventListener('scroll', checkSkillsView);
// Check once on page load (in case skills are already in view)
checkSkillsView();
});
CSS Styling for Skills Section
/* Skills Section Styles */
.skills-filter {
display: flex;
justify-content: center;
margin-bottom: 30px;
flex-wrap: wrap;
}
.filter-btn {
background: none;
border: 2px solid var(--primary-color);
color: var(--text-color);
padding: 8px 20px;
margin: 0 5px 10px;
border-radius: 25px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
}
.filter-btn:hover,
.filter-btn.active {
background-color: var(--primary-color);
color: #fff;
}
.skills-container {
max-width: 700px;
margin: 0 auto;
}
.skill-item {
margin-bottom: 25px;
overflow: hidden;
transition: all 0.3s ease;
}
.skill-info {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.skill-name {
font-weight: 500;
}
.skill-percentage {
color: var(--primary-color);
font-weight: 600;
}
.skill-bar {
height: 10px;
background-color: var(--card-bg-color);
border-radius: 5px;
overflow: hidden;
}
.skill-progress {
height: 100%;
width: 0; /* Initial width, will be animated with JS */
background-color: var(--primary-color);
border-radius: 5px;
transition: width 1s ease-in-out;
}
Explanation:
This implementation adds two interactive features to the skills section:
- Animated Skill Bars: When the skills section comes into view, the progress bars animate to their respective percentages, creating a dynamic visualization of your skill levels.
- Filterable Skills: Users can filter skills by category (Frontend, Backend, Other), making it easier to focus on specific skill sets.
The JavaScript detects when the skills section is visible in the viewport and triggers the animation. The filtering functionality uses data attributes to categorize skills and show/hide them based on the selected filter. When switching filters, the animations reset and play again for newly displayed skills.
Feature 4: Projects Showcase with Modal Windows
HTML Structure
<!-- Projects Section -->
<section id="projects">
<div class="container">
<h2 class="section-title">Projects</h2>
<div class="projects-filter">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="web">Web</button>
<button class="filter-btn" data-filter="app">App</button>
<button class="filter-btn" data-filter="design">Design</button>
</div>
<div class="projects-container">
<!-- Project 1 -->
<div class="project-card" data-category="web">
<img src="images/project1.jpg" alt="Project 1">
<div class="project-info">
<h3>Personal Blog</h3>
<p>A responsive blog built with HTML, CSS, and JavaScript.</p>
<button class="btn view-project" data-project="project1">View Details</button>
</div>
</div>
<!-- Project 2 -->
<div class="project-card" data-category="web app">
<img src="images/project2.jpg" alt="Project 2">
<div class="project-info">
<h3>Weather App</h3>
<p>A weather application using a third-party API.</p>
<button class="btn view-project" data-project="project2">View Details</button>
</div>
</div>
<!-- Project 3 -->
<div class="project-card" data-category="design">
<img src="images/project3.jpg" alt="Project 3">
<div class="project-info">
<h3>Logo Design</h3>
<p>A collection of logo designs for various clients.</p>
<button class="btn view-project" data-project="project3">View Details</button>
</div>
</div>
<!-- Add more project cards as needed -->
</div>
</div>
<!-- Project Modals -->
<div class="modal" id="modal-project1">
<div class="modal-content">
<span class="close-modal">×</span>
<div class="modal-header">
<h2>Personal Blog</h2>
</div>
<div class="modal-body">
<img src="images/project1-large.jpg" alt="Personal Blog">
<div class="project-details">
<h3>Project Overview</h3>
<p>A responsive blog website built with HTML, CSS, and JavaScript. Features include a responsive layout, dark mode toggle, and comment system.</p>
<h3>Technologies Used</h3>
<ul>
<li>HTML5</li>
<li>CSS3 (Flexbox/Grid)</li>
<li>JavaScript (ES6+)</li>
<li>LocalStorage API</li>
</ul>
<h3>Project Links</h3>
<div class="project-links">
<a href="#" class="btn" target="_blank">Live Demo</a>
<a href="#" class="btn" target="_blank">GitHub Repo</a>
</div>
</div>
</div>
</div>
</div>
<!-- Additional project modals (project2, project3, etc.) would go here -->
</section>
JavaScript Implementation (projects.js)
// Wait for DOM to load completely
document.addEventListener('DOMContentLoaded', function() {
// Project filtering
const projectFilterBtns = document.querySelectorAll('.projects-filter .filter-btn');
const projectCards = document.querySelectorAll('.project-card');
projectFilterBtns.forEach(button => {
button.addEventListener('click', function() {
// Remove active class from all buttons
projectFilterBtns.forEach(btn => btn.classList.remove('active'));
// Add active class to clicked button
this.classList.add('active');
// Get filter value
const filterValue = this.getAttribute('data-filter');
// Filter projects
projectCards.forEach(card => {
const categories = card.getAttribute('data-category').split(' ');
if (filterValue === 'all' || categories.includes(filterValue)) {
// Show with animation
card.style.display = 'block';
setTimeout(() => {
card.style.opacity = '1';
card.style.transform = 'scale(1)';
}, 10);
} else {
// Hide with animation
card.style.opacity = '0';
card.style.transform = 'scale(0.8)';
setTimeout(() => {
card.style.display = 'none';
}, 300);
}
});
});
});
// Project Modal Functionality
const modalBtns = document.querySelectorAll('.view-project');
const modals = document.querySelectorAll('.modal');
const closeButtons = document.querySelectorAll('.close-modal');
// Open modal when "View Details" is clicked
modalBtns.forEach(btn => {
btn.addEventListener('click', function() {
const projectId = this.getAttribute('data-project');
const modal = document.getElementById(`modal-${projectId}`);
if (modal) {
// Display the modal
modal.style.display = 'block';
// Prevent scrolling on the body
document.body.style.overflow = 'hidden';
// Add animation classes
setTimeout(() => {
modal.classList.add('show-modal');
}, 10);
}
});
});
// Close modal when "×" is clicked
closeButtons.forEach(button => {
button.addEventListener('click', closeCurrentModal);
});
// Close modal when clicking outside the modal content
modals.forEach(modal => {
modal.addEventListener('click', function(e) {
// Close only if the click is on the modal background (not the content)
if (e.target === modal) {
closeCurrentModal();
}
});
});
// Close modal when ESC key is pressed
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeCurrentModal();
}
});
// Function to close the currently open modal
function closeCurrentModal() {
const openModal = document.querySelector('.modal.show-modal');
if (openModal) {
// Remove animation class
openModal.classList.remove('show-modal');
// Hide after animation completes
setTimeout(() => {
openModal.style.display = 'none';
// Re-enable scrolling
document.body.style.overflow = '';
}, 300);
}
}
});
CSS Styling for Projects and Modals
/* Projects Section Styles */
.projects-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 25px;
margin-top: 30px;
}
.project-card {
background-color: var(--card-bg-color);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 5px 15px var(--shadow-color);
transition: transform 0.3s ease, opacity 0.3s ease, box-shadow 0.3s ease;
}
.project-card:hover {
transform: translateY(-10px);
box-shadow: 0 15px 20px var(--shadow-color);
}
.project-card img {
width: 100%;
height: 200px;
object-fit: cover;
transition: transform 0.5s ease;
}
.project-card:hover img {
transform: scale(1.05);
}
.project-info {
padding: 20px;
}
.project-info h3 {
margin-top: 0;
margin-bottom: 10px;
color: var(--text-color);
}
.project-info p {
color: var(--text-color);
margin-bottom: 15px;
}
.btn {
display: inline-block;
background-color: var(--primary-color);
color: #fff;
padding: 8px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
font-weight: 500;
transition: background-color 0.3s ease;
}
.btn:hover {
background-color: var(--secondary-color);
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1001;
overflow-y: auto;
opacity: 0;
transition: opacity 0.3s ease;
}
.modal.show-modal {
opacity: 1;
}
.modal-content {
background-color: var(--background-color);
margin: 5vh auto;
width: 90%;
max-width: 900px;
border-radius: 8px;
box-shadow: 0 5px 30px rgba(0, 0, 0, 0.3);
position: relative;
transform: translateY(-50px);
transition: transform 0.3s ease;
}
.modal.show-modal .modal-content {
transform: translateY(0);
}
.close-modal {
position: absolute;
top: 10px;
right: 20px;
font-size: 30px;
color: var(--text-color);
cursor: pointer;
transition: color 0.3s ease;
}
.close-modal:hover {
color: var(--primary-color);
}
.modal-header {
padding: 20px;
border-bottom: 1px solid var(--border-color);
}
.modal-header h2 {
margin: 0;
color: var(--primary-color);
}
.modal-body {
padding: 20px;
}
.modal-body img {
width: 100%;
border-radius: 8px;
margin-bottom: 20px;
}
.project-details h3 {
color: var(--text-color);
margin-top: 25px;
margin-bottom: 10px;
}
.project-links {
display: flex;
gap: 15px;
margin-top: 20px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.projects-container {
grid-template-columns: 1fr;
}
}
Explanation:
This implementation adds two interactive features to the projects section:
- Project Filtering: Users can filter projects by category (Web, App, Design) to find specific types of projects. The filtering includes smooth animations for showing and hiding projects.
- Modal Windows: When users click "View Details" on a project, a modal window opens with more comprehensive information about the project, including a larger image, detailed description, technologies used, and project links.
The modal system includes multiple ways to close the modal (close button, clicking outside the modal, pressing ESC key) for better user experience. All animations use CSS transitions with JavaScript triggering the appropriate classes, creating smooth and natural-feeling interactions.
Feature 5: Contact Form Validation with Interactive Feedback
HTML Structure
<!-- Contact Section -->
<section id="contact">
<div class="container">
<h2 class="section-title">Contact Me</h2>
<div class="contact-container">
<div class="contact-info">
<h3>Get In Touch</h3>
<p>Feel free to reach out if you have any questions or want to work together on a project.</p>
<div class="contact-detail">
<i class="contact-icon email-icon"></i>
<div>
<h4>Email</h4>
<a href="mailto:your.email@example.com">your.email@example.com</a>
</div>
</div>
<div class="contact-detail">
<i class="contact-icon location-icon"></i>
<div>
<h4>Location</h4>
<p>San Francisco, California</p>
</div>
</div>
<div class="social-links">
<a href="#" class="social-link" aria-label="GitHub"><i class="github-icon"></i></a>
<a href="#" class="social-link" aria-label="LinkedIn"><i class="linkedin-icon"></i></a>
<a href="#" class="social-link" aria-label="Twitter"><i class="twitter-icon"></i></a>
</div>
</div>
<div class="contact-form-container">
<form id="contact-form" class="contact-form">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" required>
<span class="error-message" id="name-error"></span>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<span class="error-message" id="email-error"></span>
</div>
<div class="form-group">
<label for="subject">Subject</label>
<input type="text" id="subject" name="subject" required>
<span class="error-message" id="subject-error"></span>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
<span class="error-message" id="message-error"></span>
</div>
<button type="submit" class="btn submit-btn">Send Message</button>
</form>
<div id="form-result" class="form-result"></div>
</div>
</div>
</div>
</section>
JavaScript Implementation (contact.js)
// Wait for DOM to load completely
document.addEventListener('DOMContentLoaded', function() {
const contactForm = document.getElementById('contact-form');
const nameInput = document.getElementById('name');
const emailInput = document.getElementById('email');
const subjectInput = document.getElementById('subject');
const messageInput = document.getElementById('message');
const formResult = document.getElementById('form-result');
// Error message elements
const nameError = document.getElementById('name-error');
const emailError = document.getElementById('email-error');
const subjectError = document.getElementById('subject-error');
const messageError = document.getElementById('message-error');
// Validation functions
const validators = {
name: function(value) {
if (value.trim() === '') {
return 'Name is required';
}
if (value.trim().length < 2) {
return 'Name must be at least 2 characters';
}
return '';
},
email: function(value) {
if (value.trim() === '') {
return 'Email is required';
}
// Basic email validation regex
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
return 'Please enter a valid email address';
}
return '';
},
subject: function(value) {
if (value.trim() === '') {
return 'Subject is required';
}
if (value.trim().length < 5) {
return 'Subject must be at least 5 characters';
}
return '';
},
message: function(value) {
if (value.trim() === '') {
return 'Message is required';
}
if (value.trim().length < 10) {
return 'Message must be at least 10 characters';
}
return '';
}
};
// Live validation on input
nameInput.addEventListener('input', function() {
const error = validators.name(this.value);
showError(nameError, error);
validateField(this, error);
});
emailInput.addEventListener('input', function() {
const error = validators.email(this.value);
showError(emailError, error);
validateField(this, error);
});
subjectInput.addEventListener('input', function() {
const error = validators.subject(this.value);
showError(subjectError, error);
validateField(this, error);
});
messageInput.addEventListener('input', function() {
const error = validators.message(this.value);
showError(messageError, error);
validateField(this, error);
});
// Display error message
function showError(errorElement, errorMessage) {
errorElement.textContent = errorMessage;
errorElement.style.display = errorMessage ? 'block' : 'none';
}
// Add/remove valid/invalid classes
function validateField(inputElement, error) {
if (error) {
inputElement.classList.add('invalid');
inputElement.classList.remove('valid');
} else if (inputElement.value.trim() !== '') {
inputElement.classList.add('valid');
inputElement.classList.remove('invalid');
} else {
inputElement.classList.remove('valid');
inputElement.classList.remove('invalid');
}
}
// Form submission handler
if (contactForm) {
contactForm.addEventListener('submit', function(e) {
e.preventDefault();
// Validate all fields
const nameVal = validators.name(nameInput.value);
const emailVal = validators.email(emailInput.value);
const subjectVal = validators.subject(subjectInput.value);
const messageVal = validators.message(messageInput.value);
// Show error messages
showError(nameError, nameVal);
showError(emailError, emailVal);
showError(subjectError, subjectVal);
showError(messageError, messageVal);
// Update field styling
validateField(nameInput, nameVal);
validateField(emailInput, emailVal);
validateField(subjectInput, subjectVal);
validateField(messageInput, messageVal);
// If any validation errors, stop submission
if (nameVal || emailVal || subjectVal || messageVal) {
return;
}
// Collect form data
const formData = {
name: nameInput.value,
email: emailInput.value,
subject: subjectInput.value,
message: messageInput.value
};
// Show loading state
const submitButton = contactForm.querySelector('.submit-btn');
const originalButtonText = submitButton.textContent;
submitButton.textContent = 'Sending...';
submitButton.disabled = true;
// Simulate form submission (in a real app, this would be an AJAX call)
setTimeout(function() {
// Reset form
contactForm.reset();
// Remove validation styling
[nameInput, emailInput, subjectInput, messageInput].forEach(input => {
input.classList.remove('valid');
});
// Show success message
formResult.innerHTML = `
`;
formResult.style.display = 'block';
// Reset button
submitButton.textContent = originalButtonText;
submitButton.disabled = false;
// Hide success message after 5 seconds
setTimeout(function() {
formResult.style.display = 'none';
}, 5000);
}, 1500); // Simulate network delay
});
}
});
CSS Styling for Contact Form
/* Contact Section Styles */
.contact-container {
display: grid;
grid-template-columns: 1fr;
gap: 30px;
}
@media (min-width: 768px) {
.contact-container {
grid-template-columns: 1fr 2fr;
}
}
.contact-info, .contact-form-container {
background-color: var(--card-bg-color);
padding: 30px;
border-radius: 8px;
box-shadow: 0 5px 15px var(--shadow-color);
}
.contact-detail {
display: flex;
align-items: flex-start;
margin-bottom: 20px;
}
.contact-icon {
width: 40px;
height: 40px;
background-color: var(--primary-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
flex-shrink: 0;
}
.email-icon:before {
content: "✉";
color: white;
font-size: 20px;
}
.location-icon:before {
content: "📍";
color: white;
font-size: 20px;
}
.contact-detail h4 {
margin: 0 0 5px 0;
color: var(--text-color);
}
.contact-detail a, .contact-detail p {
margin: 0;
color: var(--text-color);
}
.social-links {
display: flex;
gap: 15px;
margin-top: 25px;
}
.social-link {
width: 40px;
height: 40px;
background-color: var(--card-bg-color);
border: 2px solid var(--primary-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--primary-color);
transition: all 0.3s ease;
}
.social-link:hover {
background-color: var(--primary-color);
color: white;
}
.github-icon:before { content: "GH"; }
.linkedin-icon:before { content: "IN"; }
.twitter-icon:before { content: "TW"; }
.contact-form {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
@media (min-width: 768px) {
.contact-form {
grid-template-columns: 1fr 1fr;
}
.form-group:nth-child(4),
.submit-btn {
grid-column: span 2;
}
}
.form-group {
position: relative;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: var(--text-color);
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 12px 15px;
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--background-color);
color: var(--text-color);
font-size: 16px;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.form-group input:focus,
.form-group textarea:focus {
border-color: var(--primary-color);
outline: none;
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.2);
}
/* Validation styles */
.form-group input.valid,
.form-group textarea.valid {
border-color: #28a745;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%2328a745' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 20px;
padding-right: 40px;
}
.form-group input.invalid,
.form-group textarea.invalid {
border-color: #dc3545;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23dc3545' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 20px;
padding-right: 40px;
}
.error-message {
color: #dc3545;
font-size: 0.875rem;
margin-top: 5px;
display: none;
}
.submit-btn {
padding: 12px 25px;
font-size: 16px;
font-weight: 500;
}
.form-result {
display: none;
padding: 20px;
margin-top: 20px;
border-radius: 4px;
}
.success-message {
background-color: rgba(40, 167, 69, 0.1);
border: 1px solid #28a745;
border-radius: 4px;
padding: 15px;
color: #28a745;
}
.error-message-box {
background-color: rgba(220, 53, 69, 0.1);
border: 1px solid #dc3545;
border-radius: 4px;
padding: 15px;
color: #dc3545;
}
Explanation:
This implementation adds interactive form validation to the contact section:
- Real-Time Validation: As users type in the form fields, they receive immediate feedback about the validity of their input. This includes visual indicators (green checkmark for valid input, red warning icon for invalid input) and error messages.
- Form Submission Handling: When the form is submitted, all fields are validated again. If there are any errors, submission is prevented and error messages are displayed. If all fields are valid, a success message is shown.
- Simulated Form Submission: The code includes a simulated form submission process with loading state and success feedback. In a real implementation, this would be replaced with an AJAX call to a server endpoint.
The validation logic checks for empty fields, minimum length requirements, and proper email format. The CSS styling provides clear visual feedback for each state (default, focus, valid, invalid), enhancing usability and guiding users through the form completion process.
Step 4: Look Back and Review
We've successfully added several interactive features to our profile page:
- Interactive Navigation: Mobile-responsive menu, smooth scrolling, and active link highlighting
- Dynamic Header: Typing effect for roles/skills and a theme toggle for light/dark mode
- Animated Skills Bars: Visual representation of skill levels with filtering capabilities
- Projects Showcase: Filterable project cards with detailed modal windows
- Interactive Contact Form: Real-time validation with helpful feedback
These features transform our static profile page into an engaging, interactive experience that showcases not only our content but also our JavaScript skills. The interactivity improves user experience by making information more accessible and providing visual feedback for user actions.
JavaScript Best Practices for Interactivity
1. Progressive Enhancement
Design your page to work without JavaScript first, then enhance it with JavaScript:
- Make sure all content is accessible even if JavaScript is disabled
- Use semantic HTML that works on its own
- Add JavaScript as an enhancement, not a requirement
/* Example: Form should still submit without JS */
<form action="send-email.php" method="post" id="contact-form">
<!-- Form fields -->
<button type="submit">Send Message</button>
</form>
// JavaScript enhances with validation and AJAX submission
document.getElementById('contact-form').addEventListener('submit', function(e) {
// Only prevent default submission if JS is enabled
e.preventDefault();
// Validate and submit via AJAX
});
2. Event Delegation
Instead of attaching events to multiple elements, attach a single event listener to a parent element:
- More efficient for multiple similar elements (like gallery items)
- Automatically works for dynamically added elements
- Reduces memory usage and improves performance
/* Poor practice: Adding listeners to each button */
document.querySelectorAll('.project-card .view-project').forEach(btn => {
btn.addEventListener('click', handleClick);
});
/* Better practice: Event delegation */
document.querySelector('.projects-container').addEventListener('click', function(e) {
if (e.target.classList.contains('view-project')) {
handleClick(e);
}
});
3. Debouncing and Throttling
Limit how often a function can execute, especially for expensive operations:
- Debouncing: Wait until user stops action (typing, scrolling) before executing
- Throttling: Execute at most once per specified time period
// Debounce function
function debounce(func, wait) {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
// Usage: Debounced search input
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function() {
// Search operation - runs 300ms after user stops typing
performSearch(this.value);
}, 300));
4. Use Modern JavaScript Features
Leverage ES6+ features for cleaner, more maintainable code:
- Arrow functions for shorter syntax and lexical
this - Template literals for easier string concatenation
- Destructuring for cleaner variable assignment
- Spread/rest operators for array and object manipulation
// ES5 approach
var person = {
name: 'John',
greet: function() {
var self = this;
setTimeout(function() {
console.log('Hello, ' + self.name);
}, 1000);
}
};
// Modern approach
const person = {
name: 'John',
greet() {
setTimeout(() => {
console.log(`Hello, ${this.name}`);
}, 1000);
}
};
5. Optimize DOM Manipulation
DOM manipulation is expensive, so minimize it:
- Batch DOM updates when possible
- Use document fragments for multiple insertions
- Cache DOM references instead of repeatedly querying
- Use CSS classes for style changes instead of inline styles
// Poor performance (multiple DOM manipulations)
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
container.appendChild(div); // DOM updated 100 times
}
// Better performance (single DOM manipulation)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
container.appendChild(fragment); // DOM updated once
Common Interactive Elements for Profile Pages
Additional Interactive Elements to Consider
1. Portfolio Filtering with Isotope.js
Enhance your projects section with animated filtering using the Isotope library:
// HTML Structure
<div class="filter-buttons">
<button data-filter="*" class="active">All</button>
<button data-filter=".web">Web</button>
<button data-filter=".app">App</button>
</div>
<div class="portfolio-container">
<div class="portfolio-item web">...</div>
<div class="portfolio-item app">...</div>
<div class="portfolio-item web app">...</div>
</div>
// JavaScript with Isotope.js
const iso = new Isotope('.portfolio-container', {
itemSelector: '.portfolio-item',
layoutMode: 'fitRows'
});
document.querySelectorAll('.filter-buttons button').forEach(button => {
button.addEventListener('click', function() {
// Update active button
document.querySelector('.filter-buttons .active').classList.remove('active');
this.classList.add('active');
// Filter items
const filterValue = this.getAttribute('data-filter');
iso.arrange({ filter: filterValue });
});
});
2. Animated Scroll Reveal
Make content appear with animations as users scroll down the page:
// HTML Structure
<div class="reveal fade-in">Content that appears with fade-in</div>
<div class="reveal slide-up">Content that slides up</div>
// JavaScript
const revealElements = document.querySelectorAll('.reveal');
function checkReveal() {
revealElements.forEach(element => {
const elementTop = element.getBoundingClientRect().top;
const elementVisible = 150; // Amount of element visible before animation
if (elementTop < window.innerHeight - elementVisible) {
element.classList.add('active');
}
});
}
window.addEventListener('scroll', checkReveal);
checkReveal(); // Check on page load
// CSS
.reveal {
opacity: 0;
transition: all 1s ease;
}
.reveal.active {
opacity: 1;
}
.fade-in.active {
opacity: 1;
}
.slide-up {
transform: translateY(50px);
}
.slide-up.active {
transform: translateY(0);
}
3. Interactive Timeline
Create an interactive timeline for your education or work experience:
// HTML Structure
<div class="timeline">
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-date">2020-Present</div>
<div class="timeline-content">
<h3>Web Developer at Company</h3>
<p>Description of role and responsibilities...</p>
</div>
</div>
<!-- More timeline items -->
</div>
// JavaScript
document.querySelectorAll('.timeline-item').forEach(item => {
item.addEventListener('mouseenter', function() {
this.classList.add('active');
});
item.addEventListener('mouseleave', function() {
this.classList.remove('active');
});
});
// CSS
.timeline {
position: relative;
max-width: 800px;
margin: 0 auto;
}
.timeline::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 20px;
width: 2px;
background: #ddd;
}
.timeline-item {
position: relative;
margin-bottom: 30px;
padding-left: 50px;
transition: transform 0.3s ease;
}
.timeline-dot {
position: absolute;
left: 16px;
width: 10px;
height: 10px;
background: #0066cc;
border-radius: 50%;
transition: transform 0.3s ease, background-color 0.3s ease;
}
.timeline-item.active .timeline-dot {
transform: scale(1.5);
background-color: #004080;
}
.timeline-item.active {
transform: translateX(10px);
}
4. Skill Radar Chart
Visualize your skills using a radar/spider chart with Chart.js:
// HTML Structure
<div class="chart-container">
<canvas id="skillsChart"></canvas>
</div>
// JavaScript with Chart.js
const ctx = document.getElementById('skillsChart').getContext('2d');
const skillsChart = new Chart(ctx, {
type: 'radar',
data: {
labels: ['HTML', 'CSS', 'JavaScript', 'React', 'Node.js', 'PHP'],
datasets: [{
label: 'Skills',
data: [90, 85, 70, 65, 60, 75],
backgroundColor: 'rgba(0, 102, 204, 0.2)',
borderColor: '#0066cc',
pointBackgroundColor: '#0066cc',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: '#0066cc'
}]
},
options: {
scale: {
ticks: {
beginAtZero: true,
max: 100
}
},
animation: {
duration: 1500
}
}
});
Helpful JavaScript Libraries for Profile Interactivity
Top JavaScript Libraries for Profile Pages
Animation Libraries
- GSAP (GreenSock Animation Platform): Professional-grade animation library for complex animations and interactions.
- AOS (Animate On Scroll): Simple library to animate elements as they scroll into view.
- Anime.js: Lightweight animation library with a simple API for creating complex animations.
UI Component Libraries
- Swiper: Modern mobile touch slider with hardware-accelerated transitions.
- Lightbox2: Overlay images and content on the current page for gallery functionality.
- Isotope: Filter and sort layouts with animation effects.
Form Enhancement Libraries
- Validate.js: Lightweight form validation library with no dependencies.
- FormValidation: Comprehensive validation for Bootstrap, Foundation, and other frameworks.
- Cleave.js: Format input text as the user types (phone numbers, dates, credit cards).
Data Visualization Libraries
- Chart.js: Simple yet flexible JavaScript charting library for designers and developers.
- D3.js: Powerful data visualization library for creating complex custom visualizations.
- Particles.js: Create particle effects for backgrounds and interactive elements.
Utility Libraries
- Lodash: Utility library for common programming tasks.
- Day.js: Lightweight alternative to Moment.js for parsing, manipulating, and displaying dates.
- Clipboard.js: Add copy-to-clipboard functionality for sharing contact details.
Note: While libraries can save time and add powerful functionality, be mindful of performance. Only include libraries you actually need, and consider their file size impact on page load times. For simple profile pages, vanilla JavaScript is often sufficient.
Testing and Accessibility Considerations
Testing Your Interactive Features
- Cross-Browser Testing: Test in multiple browsers (Chrome, Firefox, Safari, Edge) to ensure consistent behavior.
- Responsive Testing: Test on different screen sizes, from mobile to desktop.
- Performance Testing: Check loading times and animation smoothness, especially on mobile devices.
- Progressive Enhancement: Verify that your page works without JavaScript (or with JavaScript disabled).
- Error Handling: Test edge cases and make sure your code handles errors gracefully.
Useful Testing Tools:
- Browser DevTools: Built-in developer tools for debugging JavaScript and testing responsive design.
- Lighthouse: Analyze performance, accessibility, and more (built into Chrome DevTools).
- BrowserStack/Sauce Labs: Test on real devices and browsers you don't have access to.
- WebPageTest: Analyze page load performance in different locations and network conditions.
Accessibility for Interactive Elements
Make sure your interactive features are accessible to all users, including those with disabilities:
Keyboard Navigation
- Ensure all interactive elements can be reached and activated using the keyboard (Tab, Enter, Space)
- Maintain a visible focus indicator for keyboard users
- Implement proper tab order for logical navigation
// Make non-standard interactive elements keyboard accessible
document.querySelectorAll('.custom-button').forEach(button => {
// Add role and tabindex
button.setAttribute('role', 'button');
button.setAttribute('tabindex', '0');
// Handle both click and keypress events
button.addEventListener('click', handleAction);
button.addEventListener('keypress', function(e) {
// Activate on Enter or Space
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleAction(e);
}
});
});
Screen Reader Support
- Use appropriate ARIA attributes to convey state and purpose
- Provide descriptive labels for interactive elements
- Announce dynamic content changes
// Example: Modal dialog with ARIA attributes
<div id="project-modal" class="modal" role="dialog" aria-labelledby="modal-title" aria-hidden="true">
<div class="modal-content">
<button class="close-modal" aria-label="Close modal">×</button>
<h2 id="modal-title">Project Title</h2>
<!-- Modal content -->
</div>
</div>
// JavaScript to handle modal accessibility
function openModal(modal) {
modal.setAttribute('aria-hidden', 'false');
// Trap focus inside modal
// Set focus on first focusable element in modal
}
function closeModal(modal) {
modal.setAttribute('aria-hidden', 'true');
// Restore focus to element that opened the modal
}
Animation and Motion Considerations
- Respect user preferences for reduced motion
- Provide alternatives for content that relies on animation
- Avoid animations that could trigger seizures or discomfort
// Respect prefers-reduced-motion setting
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
if (prefersReducedMotion.matches) {
// Use simplified or no animations
document.documentElement.classList.add('reduced-motion');
} else {
// Use standard animations
initializeAnimations();
}
// In CSS
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.001ms !important;
transition-duration: 0.001ms !important;
}
}
Further Learning Resources
- MDN JavaScript Guide - Comprehensive resource for JavaScript fundamentals
- CSS-Tricks: jQuery Learning Center - jQuery tutorials for easier DOM manipulation
- Smashing Magazine: JavaScript - Articles on modern JavaScript techniques
- Codrops - Creative JavaScript and CSS tutorials for web design
- Awesome JavaScript - Curated list of JavaScript libraries and resources
- ARIA Authoring Practices Guide - Accessibility guidelines for interactive elements
Homework Assignment
Primary Task: Add Interactivity to Your Profile Page
Enhance your personal profile page by adding at least five interactive features using JavaScript:
Required Features (Choose at least 3):
- Interactive Navigation: Implement smooth scrolling, highlight active sections, and/or create a mobile-responsive menu.
- Theme Toggle: Create a light/dark mode switch that persists user preferences.
- Animated Skills Section: Add progress bars or charts that animate when scrolled into view.
- Project Gallery: Create a filterable project showcase with categories.
- Form Validation: Add real-time validation to your contact form with helpful feedback.
Additional Features (Choose at least 2):
- Typing Effect: Create a typing animation for your tagline or introduction.
- Image Lightbox: Implement a lightbox for project images or gallery.
- Testimonials Carousel: Add a slider for testimonials or reviews.
- Animated Counters: Add animated counters for statistics (years of experience, projects completed, etc.).
- Interactive Timeline: Create an interactive timeline for your education or work history.
- Scroll Animations: Add animations that trigger as elements come into view.
- Custom Cursor: Create a custom cursor effect that reacts to different elements.
Requirements:
- Use vanilla JavaScript (no jQuery) for at least 3 features to demonstrate core JavaScript skills.
- You may use JavaScript libraries for more complex features if desired.
- Ensure all interactions are smooth and professional-looking.
- Make sure your page remains responsive and works across different browsers.
- Consider accessibility for all interactive elements.
- Add appropriate comments in your JavaScript code to explain your implementation.
Submission Guidelines
- Code Files: Submit your HTML, CSS, and JavaScript files organized in a clear folder structure.
- Documentation: Include a README.md file explaining the interactive features you implemented and any libraries used.
- Testing: Test your page in multiple browsers and screen sizes before submission.
- Deployment (Optional): Deploy your page to GitHub Pages or another hosting service for easy viewing.
Grading Criteria
- Functionality (40%): All interactive features work correctly and enhance user experience.
- Code Quality (25%): JavaScript is well-structured, efficient, and properly commented.
- Integration (15%): Interactive elements integrate seamlessly with the page design.
- Responsiveness (10%): Interactive features work well across different screen sizes.
- Creativity (10%): Unique or creative implementation of interactive elements.
Bonus Challenges (Optional)
- Parallax Effects: Implement parallax scrolling effects for a more immersive experience.
- Advanced Animations: Create complex animations using GSAP or similar libraries.
- Local Storage: Use localStorage to remember user preferences or form data.
- Interactive Resume: Create an interactive, filterable version of your resume/CV.
- Easter Eggs: Add hidden interactive features that users can discover.