Skip to content

Commit a80fa77

Browse files
committed
Refactor video loading and playback functionality; implement lazy loading for videos and enhance hover effects for desktop. Update styles for loading indicators in main.css.
1 parent 5e7a895 commit a80fa77

File tree

4 files changed

+275
-42
lines changed

4 files changed

+275
-42
lines changed

css/main.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,33 @@
66
light: #f8f8f6, #efeff2
77
*/
88

9+
/* Video Loading States */
10+
.video-loading-overlay {
11+
position: absolute;
12+
top: 0;
13+
left: 0;
14+
right: 0;
15+
bottom: 0;
16+
background: rgba(17, 17, 17, 0.8);
17+
display: none;
18+
align-items: center;
19+
justify-content: center;
20+
z-index: 2;
21+
}
22+
23+
.loading-spinner {
24+
width: 40px;
25+
height: 40px;
26+
border: 3px solid rgba(248, 248, 246, 0.3);
27+
border-radius: 50%;
28+
border-top-color: #f8f8f6;
29+
animation: spin 1s ease-in-out infinite;
30+
}
31+
32+
@keyframes spin {
33+
to { transform: rotate(360deg); }
34+
}
35+
936
/* Base Layout */
1037
body {
1138
margin: 0;

index.html

Lines changed: 11 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,14 @@
3030
<link href="https://fonts.googleapis.com/css?family=Roboto+Slab" rel="stylesheet">
3131
<!-- Jquery Min Library -->
3232
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" defer></script>
33-
<!-- <script src="./js/main.js" defer></script> -->
34-
35-
<script>
36-
document.addEventListener('DOMContentLoaded', function() {
37-
const video = document.querySelector('.video-background');
38-
if (video) {
39-
video.playbackRate = 0.25;
40-
}
41-
});
42-
</script>
33+
<!-- Lazy Video Loader -->
34+
<script src="./js/lazy-video-loader.js" defer></script>
35+
<!-- Crumple Hover Effects -->
36+
<script src="./js/crumple-hover.js" defer></script>
4337
</head>
4438
<body>
45-
<video autoplay muted loop playsinline class="video-background">
46-
<source src="./assets/videos/super 8.mp4" type="video/mp4">
39+
<video muted loop playsinline class="video-background" preload="none" poster="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Crect fill='%23111' width='1' height='1'/%3E%3C/svg%3E">
40+
<source data-src="./assets/videos/super 8.mp4" type="video/mp4">
4741
</video>
4842
<div class="content-wrapper">
4943
<section class="hero">
@@ -77,46 +71,21 @@ <h1 class="header">
7771

7872
<div class="crumple" aria-hidden="">
7973
<video class="crumple-video crumple-video--writings" muted loop playsinline preload="none">
80-
<source src="./assets/videos/PXL_20220605_192330465.mp4" type="video/mp4">
74+
<source data-src="./assets/videos/PXL_20220605_192330465.mp4" type="video/mp4">
8175
</video>
8276
<video class="crumple-video crumple-video--alhambra" muted loop playsinline preload="none">
83-
<source src="./assets/videos/IMG_4943.MOV" type="video/mp4">
77+
<source data-src="./assets/videos/IMG_4943.MOV" type="video/mp4">
8478
</video>
8579
<video class="crumple-video crumple-video--playlists" muted loop playsinline preload="none">
86-
<source src="./assets/videos/20180226_180744.mp4" type="video/mp4">
80+
<source data-src="./assets/videos/20180226_180744.mp4" type="video/mp4">
8781
</video>
8882
<video class="crumple-video crumple-video--instagram" muted loop playsinline preload="none">
89-
<source src="./assets/videos/IMG_1624.MOV" type="video/mp4">
83+
<source data-src="./assets/videos/IMG_1624.MOV" type="video/mp4">
9084
</video>
9185
<video class="crumple-video crumple-video--github" muted loop playsinline preload="none">
92-
<source src="./assets/videos/IMG_7555.MOV" type="video/mp4">
86+
<source data-src="./assets/videos/IMG_7555.MOV" type="video/mp4">
9387
</video>
9488
</div>
9589

96-
<script>
97-
// Only enable video crumples on devices with hover capability (desktop)
98-
// On mobile/touch devices, fall back to color-based crumples
99-
if (window.matchMedia('(hover: hover)').matches) {
100-
document.body.classList.add('has-hover');
101-
102-
// Play videos on hover to save bandwidth
103-
const footerLinks = document.querySelectorAll('.footer a');
104-
const videos = document.querySelectorAll('.crumple-video');
105-
106-
footerLinks.forEach((link, index) => {
107-
const videoClass = link.className.replace('footer__link--', 'crumple-video--');
108-
const video = document.querySelector('.' + videoClass);
109-
110-
if (video) {
111-
link.addEventListener('mouseenter', () => {
112-
video.play().catch(e => console.log('Video play failed:', e));
113-
});
114-
}
115-
});
116-
} else {
117-
// Mobile: Use color-based crumples instead
118-
document.body.classList.add('no-hover');
119-
}
120-
</script>
12190
</body>
12291
</html>

js/crumple-hover.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Crumple Hover Effects
3+
* Handles video hover effects for footer links on desktop devices
4+
*/
5+
6+
// Detect mobile devices more comprehensively
7+
function isMobileDevice() {
8+
const userAgent = navigator.userAgent.toLowerCase();
9+
const isMobileUA = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
10+
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
11+
const hasHover = window.matchMedia('(hover: hover)').matches;
12+
const isSmallScreen = window.innerWidth <= 768;
13+
14+
return isMobileUA || (isTouchDevice && !hasHover && isSmallScreen);
15+
}
16+
17+
// Initialize crumple hover effects
18+
function initCrumpleHover() {
19+
// Only enable video crumples on desktop devices
20+
// On mobile devices, fall back to color-based crumples and skip video loading
21+
if (window.matchMedia('(hover: hover)').matches && !isMobileDevice()) {
22+
document.body.classList.add('has-hover');
23+
24+
// Play videos on hover - only on desktop
25+
const footerLinks = document.querySelectorAll('.footer a');
26+
27+
footerLinks.forEach((link) => {
28+
const videoClass = link.className.replace('footer__link--', 'crumple-video--');
29+
const video = document.querySelector('.' + videoClass);
30+
31+
if (video) {
32+
link.addEventListener('mouseenter', async () => {
33+
try {
34+
// Check if video source needs to be loaded
35+
const source = video.querySelector('source[data-src]');
36+
if (source && source.dataset.src) {
37+
source.src = source.dataset.src;
38+
source.removeAttribute('data-src');
39+
video.load();
40+
41+
// Wait for video to be ready
42+
await new Promise((resolve) => {
43+
if (video.readyState >= 3) {
44+
resolve();
45+
} else {
46+
video.addEventListener('canplaythrough', resolve, { once: true });
47+
}
48+
});
49+
}
50+
51+
await video.play();
52+
} catch (e) {
53+
console.log('Video play failed:', e);
54+
}
55+
});
56+
}
57+
});
58+
} else {
59+
// Mobile: Use color-based crumples instead, no video loading
60+
document.body.classList.add('no-hover');
61+
}
62+
}
63+
64+
// Initialize when DOM is ready
65+
if (document.readyState === 'loading') {
66+
document.addEventListener('DOMContentLoaded', initCrumpleHover);
67+
} else {
68+
initCrumpleHover();
69+
}

js/lazy-video-loader.js

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* Lazy Video Loader
3+
* Loads videos after page load to prevent freezing
4+
*/
5+
6+
class LazyVideoLoader {
7+
constructor() {
8+
this.loadedVideos = new Set();
9+
this.isMobile = this.detectMobile();
10+
this.init();
11+
}
12+
13+
detectMobile() {
14+
// Check for mobile devices using multiple methods
15+
const userAgent = navigator.userAgent.toLowerCase();
16+
const isMobileUA = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
17+
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
18+
const hasHover = window.matchMedia('(hover: hover)').matches;
19+
const isSmallScreen = window.innerWidth <= 768;
20+
21+
const isMobile = isMobileUA || (isTouchDevice && !hasHover && isSmallScreen);
22+
23+
// Log detection results for debugging
24+
console.log('Mobile Detection:', {
25+
userAgent: isMobileUA,
26+
touchDevice: isTouchDevice,
27+
hasHover: hasHover,
28+
smallScreen: isSmallScreen,
29+
screenWidth: window.innerWidth,
30+
finalResult: isMobile
31+
});
32+
33+
return isMobile;
34+
}
35+
36+
init() {
37+
// Wait for page to fully load before starting video loading
38+
if (document.readyState === 'loading') {
39+
document.addEventListener('DOMContentLoaded', () => {
40+
setTimeout(() => this.loadHeroVideo(), 500);
41+
});
42+
} else {
43+
setTimeout(() => this.loadHeroVideo(), 500);
44+
}
45+
}
46+
47+
async loadHeroVideo() {
48+
const heroVideo = document.querySelector('.video-background');
49+
if (!heroVideo || this.loadedVideos.has('hero')) return;
50+
51+
try {
52+
// Show loading state
53+
this.showVideoLoading(heroVideo);
54+
55+
// Load the video source
56+
await this.loadVideoSource(heroVideo);
57+
58+
// Set playback rate and play
59+
heroVideo.playbackRate = 0.25;
60+
await heroVideo.play();
61+
62+
this.loadedVideos.add('hero');
63+
this.hideVideoLoading(heroVideo);
64+
65+
// Only preload crumple videos on desktop devices
66+
if (!this.isMobile) {
67+
setTimeout(() => this.preloadCrumpleVideos(), 1000);
68+
} else {
69+
console.log('Mobile device detected - skipping crumple video preloading to save bandwidth');
70+
}
71+
72+
} catch (error) {
73+
console.warn('Hero video failed to load:', error);
74+
this.hideVideoLoading(heroVideo);
75+
}
76+
}
77+
78+
async preloadCrumpleVideos() {
79+
const crumpleVideos = document.querySelectorAll('.crumple-video');
80+
81+
// Load videos one by one to avoid overwhelming the browser
82+
for (let i = 0; i < crumpleVideos.length; i++) {
83+
const video = crumpleVideos[i];
84+
if (!this.loadedVideos.has(video.className)) {
85+
try {
86+
await this.loadVideoSource(video);
87+
this.loadedVideos.add(video.className);
88+
89+
// Small delay between loading each video
90+
await new Promise(resolve => setTimeout(resolve, 200));
91+
} catch (error) {
92+
console.warn(`Crumple video ${i} failed to preload:`, error);
93+
}
94+
}
95+
}
96+
}
97+
98+
loadVideoSource(video) {
99+
return new Promise((resolve, reject) => {
100+
// If video already has a source and is loaded, resolve immediately
101+
if (video.readyState >= 3) {
102+
resolve();
103+
return;
104+
}
105+
106+
// Check if we need to move data-src to src
107+
const source = video.querySelector('source[data-src]');
108+
if (source && source.dataset.src) {
109+
source.src = source.dataset.src;
110+
source.removeAttribute('data-src');
111+
}
112+
113+
// Set up event listeners
114+
const onLoad = () => {
115+
cleanup();
116+
resolve();
117+
};
118+
119+
const onError = (error) => {
120+
cleanup();
121+
reject(error);
122+
};
123+
124+
const cleanup = () => {
125+
video.removeEventListener('canplaythrough', onLoad);
126+
video.removeEventListener('error', onError);
127+
};
128+
129+
video.addEventListener('canplaythrough', onLoad);
130+
video.addEventListener('error', onError);
131+
132+
// Trigger video loading
133+
video.load();
134+
135+
// Timeout after 10 seconds
136+
setTimeout(() => {
137+
cleanup();
138+
reject(new Error('Video load timeout'));
139+
}, 10000);
140+
});
141+
}
142+
143+
showVideoLoading(video) {
144+
// Add a subtle loading indicator
145+
video.style.opacity = '0.3';
146+
147+
// Create loading overlay if it doesn't exist
148+
let overlay = video.parentNode.querySelector('.video-loading-overlay');
149+
if (!overlay) {
150+
overlay = document.createElement('div');
151+
overlay.className = 'video-loading-overlay';
152+
overlay.innerHTML = '<div class="loading-spinner"></div>';
153+
video.parentNode.appendChild(overlay);
154+
}
155+
overlay.style.display = 'flex';
156+
}
157+
158+
hideVideoLoading(video) {
159+
video.style.opacity = '1';
160+
const overlay = video.parentNode.querySelector('.video-loading-overlay');
161+
if (overlay) {
162+
overlay.style.display = 'none';
163+
}
164+
}
165+
}
166+
167+
// Initialize the lazy video loader
168+
new LazyVideoLoader();

0 commit comments

Comments
 (0)