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,6 +1,9 @@
<template> <template>
<section class="hero-voyage" id="heroSection"> <section class="hero-voyage" id="heroSection">
<div ref="videoContainer" class="hero-video-container"> <!-- 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 --> <!-- White overlay that fades out when video is ready -->
<div <div
class="video-white-overlay" class="video-white-overlay"
@@ -29,6 +32,8 @@
<div class="hero-overlay gradient-warm" /> <div class="hero-overlay gradient-warm" />
<div class="hero-overlay gradient-depth" /> <div class="hero-overlay gradient-depth" />
</div> </div>
</Teleport>
</ClientOnly>
<div class="hero-content" :class="{ 'is-ready': videoLoaded }"> <div class="hero-content" :class="{ 'is-ready': videoLoaded }">
<div class="hero-main"> <div class="hero-main">
@@ -171,10 +176,51 @@ onMounted(() => {
</script> </script>
<style scoped> <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 */ /* Video fade-in transition to prevent flash */
.hero-video { .hero-video {
opacity: 0; opacity: 0;
transition: opacity 1s ease-in-out; 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 { .hero-video.video-loaded {