fix: nuclear option - inject video directly into DOM bypassing Vue/Nuxt
All checks were successful
build-website / build (push) Successful in 1m42s
All checks were successful
build-website / build (push) Successful in 1m42s
- Remove video from Vue template entirely - Inject video HTML directly into document.body on mount - Video now exists completely outside #__nuxt and Vue control - Bypasses all framework hydration and virtual DOM issues - Add cleanup on unmount to remove injected element This nuclear approach ensures the video is rendered as pure HTML/CSS without any framework interference that could be blocking iOS Safari safe area extension.
This commit is contained in:
@@ -1,35 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="hero-voyage" id="heroSection">
|
<section class="hero-voyage" id="heroSection">
|
||||||
<!-- Hero video background using safe-area-max-inset-top for iOS Safari -->
|
<!-- Video is now injected directly into DOM to bypass Vue/Nuxt issues -->
|
||||||
<div ref="videoContainer" class="hero-video-container">
|
|
||||||
<!-- White overlay that fades out when video is ready -->
|
|
||||||
<div
|
|
||||||
class="video-white-overlay"
|
|
||||||
:class="{ 'fade-out': videoLoaded }"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<video
|
|
||||||
ref="videoElement"
|
|
||||||
autoplay
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
playsinline
|
|
||||||
preload="auto"
|
|
||||||
class="hero-video"
|
|
||||||
:class="{ 'video-loaded': videoLoaded }"
|
|
||||||
@loadeddata="handleVideoLoaded"
|
|
||||||
@canplaythrough="handleVideoLoaded"
|
|
||||||
@play="handleVideoLoaded"
|
|
||||||
>
|
|
||||||
<source
|
|
||||||
src="https://videos.pexels.com/video-files/3571264/3571264-uhd_2560_1440_30fps.mp4"
|
|
||||||
type="video/mp4"
|
|
||||||
>
|
|
||||||
</video>
|
|
||||||
|
|
||||||
<div class="hero-overlay gradient-warm" />
|
|
||||||
<div class="hero-overlay gradient-depth" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="hero-content" :class="{ 'is-ready': videoLoaded }">
|
<div class="hero-content" :class="{ 'is-ready': videoLoaded }">
|
||||||
<div class="hero-main">
|
<div class="hero-main">
|
||||||
@@ -66,7 +37,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { useParallax } from '~/composables/useParallax'
|
import { useParallax } from '~/composables/useParallax'
|
||||||
import { useIntersectionAnimations } from '~/composables/useIntersectionAnimations'
|
import { useIntersectionAnimations } from '~/composables/useIntersectionAnimations'
|
||||||
import { useRipple } from '~/composables/useRipple'
|
import { useRipple } from '~/composables/useRipple'
|
||||||
@@ -78,7 +49,8 @@ const videoContainer = ref<HTMLElement | null>(null)
|
|||||||
const videoElement = ref<HTMLVideoElement | null>(null)
|
const videoElement = ref<HTMLVideoElement | null>(null)
|
||||||
const animatedCount = ref(0)
|
const animatedCount = ref(0)
|
||||||
|
|
||||||
useParallax(videoContainer, 0.5)
|
// Parallax will be disabled for injected video
|
||||||
|
// useParallax(videoContainer, 0.5)
|
||||||
useIntersectionAnimations()
|
useIntersectionAnimations()
|
||||||
useRipple()
|
useRipple()
|
||||||
|
|
||||||
@@ -98,25 +70,7 @@ const handleScrollToExplore = () => {
|
|||||||
scrollToElement('#services', 800) // Constant-speed scroll to services
|
scrollToElement('#services', 800) // Constant-speed scroll to services
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleVideoVisibility = () => {
|
// Video visibility handling removed - video is now injected directly into DOM
|
||||||
if (!videoElement.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver((entries) => {
|
|
||||||
entries.forEach((entry) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
videoElement.value?.play()
|
|
||||||
} else {
|
|
||||||
videoElement.value?.pause()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, { threshold: 0.25 })
|
|
||||||
|
|
||||||
observer.observe(videoElement.value)
|
|
||||||
|
|
||||||
return () => observer.disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
const initSmoothScroll = () => {
|
const initSmoothScroll = () => {
|
||||||
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
||||||
@@ -158,7 +112,66 @@ const animateCount = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
handleVideoVisibility()
|
// NUCLEAR OPTION: Inject video directly into DOM, bypassing Vue/Nuxt entirely
|
||||||
|
const videoHTML = `
|
||||||
|
<div id="ios-video-bg" style="
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: -1;
|
||||||
|
background: #000;
|
||||||
|
">
|
||||||
|
<video
|
||||||
|
id="bg-video-element"
|
||||||
|
autoplay
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
loop
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center top;
|
||||||
|
">
|
||||||
|
<source src="https://videos.pexels.com/video-files/3571264/3571264-uhd_2560_1440_30fps.mp4" type="video/mp4">
|
||||||
|
</video>
|
||||||
|
<div style="
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(to bottom,
|
||||||
|
rgba(0, 31, 63, 0.3) 0%,
|
||||||
|
rgba(0, 31, 63, 0.5) 50%,
|
||||||
|
rgba(0, 31, 63, 0.7) 100%);
|
||||||
|
"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Insert at the very beginning of body, outside #__nuxt
|
||||||
|
document.body.insertAdjacentHTML('afterbegin', videoHTML);
|
||||||
|
|
||||||
|
// Get reference to injected video
|
||||||
|
const injectedVideo = document.getElementById('bg-video-element') as HTMLVideoElement;
|
||||||
|
|
||||||
|
// Handle video load state
|
||||||
|
if (injectedVideo) {
|
||||||
|
injectedVideo.addEventListener('loadeddata', () => {
|
||||||
|
videoLoaded.value = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// iOS autoplay fallback
|
||||||
|
const tryPlay = () => {
|
||||||
|
if (injectedVideo.paused) {
|
||||||
|
injectedVideo.play().catch(() => {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('pointerdown', tryPlay, { once: true });
|
||||||
|
window.addEventListener('touchstart', tryPlay, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
initSmoothScroll()
|
initSmoothScroll()
|
||||||
animateCount()
|
animateCount()
|
||||||
|
|
||||||
@@ -168,18 +181,11 @@ onMounted(() => {
|
|||||||
videoLoaded.value = true
|
videoLoaded.value = true
|
||||||
}
|
}
|
||||||
}, 1500)
|
}, 1500)
|
||||||
|
|
||||||
// iOS autoplay fallback - play on first user interaction
|
|
||||||
const tryPlay = () => {
|
|
||||||
if (videoElement.value?.paused) {
|
|
||||||
videoElement.value.play().catch(() => {
|
|
||||||
// Silently fail if autoplay is still blocked
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
onUnmounted(() => {
|
||||||
// Listen for any user interaction to trigger video play
|
// Clean up injected video on component unmount
|
||||||
window.addEventListener('pointerdown', tryPlay, { once: true })
|
document.getElementById('ios-video-bg')?.remove();
|
||||||
window.addEventListener('touchstart', tryPlay, { once: true })
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user