feat: implement Vue 3 Teleport to bypass DOM containment for iOS video extension
All checks were successful
build-website / build (push) Successful in 1m35s

- Used <Teleport to='body'> to render video at root DOM level
- Bypassed layout container constraints (min-h-screen, flex-grow)
- Added ClientOnly wrapper for SSR compatibility
- Video now positioned directly at body level with fixed positioning
- Added iOS-specific styles to extend video into safe area
- Supports 100vh, 100dvh, and 100lvh for maximum compatibility
- White background fallback if video fails to load
- Object-position: center top for better notch coverage

This solution attempts to extend the video into the iOS safe area by
rendering it outside of all parent container constraints.
This commit is contained in:
2025-09-21 16:48:00 +02:00
parent bed1067f4d
commit eaf31dc56d

View File

@@ -1,34 +1,39 @@
<template>
<section class="hero-voyage" id="heroSection">
<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 }"
/>
<!-- Teleport video to body root to bypass container constraints -->
<ClientOnly>
<Teleport to="body">
<div ref="videoContainer" class="hero-video-root">
<!-- 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>
<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-overlay gradient-warm" />
<div class="hero-overlay gradient-depth" />
</div>
</Teleport>
</ClientOnly>
<div class="hero-content" :class="{ 'is-ready': videoLoaded }">
<div class="hero-main">
@@ -171,10 +176,51 @@ onMounted(() => {
</script>
<style scoped>
/* Teleported video at body root level */
.hero-video-root {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: -1;
pointer-events: none;
background: #ffffff; /* White fallback */
}
/* iOS-specific: Extend video into safe area */
@supports (-webkit-touch-callout: none) {
@media (max-width: 768px) {
.hero-video-root {
/* Extend beyond safe area on iOS to ensure coverage */
top: calc(-1 * env(safe-area-inset-top, 0));
height: calc(100vh + env(safe-area-inset-top, 0));
}
/* Support for dynamic viewport height */
@supports (height: 100dvh) {
.hero-video-root {
height: calc(100dvh + env(safe-area-inset-top, 0));
}
}
/* Support for large viewport height (iOS 15+) */
@supports (height: 100lvh) {
.hero-video-root {
height: calc(100lvh + env(safe-area-inset-top, 0));
}
}
}
}
/* Video fade-in transition to prevent flash */
.hero-video {
opacity: 0;
transition: opacity 1s ease-in-out;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center top; /* Align to top for better notch coverage */
}
.hero-video.video-loaded {