Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Video Comparison Slider</title> | |
<style> | |
body, html { | |
margin: 0; | |
padding: 0; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
height: 100vh; | |
background-color: black; | |
} | |
.video-container { | |
position: relative; | |
width: 80vw; /* Set the width to 80% of the page */ | |
height: 45vw; /* Maintain aspect ratio */ | |
overflow: hidden; | |
} | |
video { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
} | |
.second-video { | |
clip-path: inset(0 50% 0 0); | |
} | |
.slider { | |
position: absolute; | |
top: 0; | |
left: 30%; | |
width: 10px; /* Increase width to make dragging easier */ | |
height: 100%; | |
background: rgb(0, 80, 150); | |
cursor: ew-resize; | |
z-index: 10; | |
animation: slide-animation 5s ease-in-out forwards; | |
border-radius: 4px; | |
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); | |
} | |
.slider::before, | |
.slider::after { | |
content: attr(data-arrow); /* Use the data-arrow attribute to show the arrow */ | |
position: absolute; | |
top: 50%; | |
transform: translateY(-50%); | |
width: 30px; /* Increase width for easier hover */ | |
height: 30px; /* Increase height for easier hover */ | |
font-size: 20px; /* Increase font size for visibility */ | |
color: rgb(0, 80, 150); /* Arrow color */ | |
background: transparent; | |
text-align: center; | |
line-height: 30px; /* Center text vertically */ | |
transition: opacity 0.3s, transform 0.3s; /* Smooth transition */ | |
} | |
.slider::before { | |
left: -40px; /* Move left to create more hover space */ | |
} | |
.slider::after { | |
right: -40px; /* Move right to create more hover space */ | |
} | |
.slider.ready:hover::before, | |
.slider.ready:hover::after { | |
transform: translateY(-50%) scale(1.3); /* Increase size on hover */ | |
} | |
.slider.ready::before, | |
.slider.ready::after { | |
opacity: 0.5; /* Initial opacity */ | |
} | |
.slider.ready:hover::before, | |
.slider.ready:hover::after { | |
opacity: 1; /* Full opacity on hover */ | |
} | |
@keyframes slide-animation { | |
0% { | |
left: 100%; | |
} | |
10% { | |
left: 80%; | |
} | |
20% { | |
left: 85%; | |
} | |
50% { | |
left: 20%; | |
} | |
70% { | |
left: 60%; | |
} | |
100% { | |
left: 30%; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="video-container" id="videoContainer"> | |
<video id="video1" src="flir_brain_padded.mp4" playsinline autoplay loop muted></video> | |
<video id="video2" class="second-video" src="flir_20240905_120243.mp4" playsinline autoplay loop muted></video> | |
<div class="slider" id="slider" data-arrow=">"></div> <!-- Add 'data-arrow' attribute for arrows --> | |
</div> | |
<script> | |
const slider = document.getElementById('slider'); | |
const container = document.getElementById('videoContainer'); | |
const video1 = document.getElementById('video1'); | |
const video2 = document.getElementById('video2'); | |
let isDragging = false; | |
const syncTolerance = 0.1; // Tolerance in seconds for syncing | |
// Function to sync the second video to the first video | |
function syncVideos(mainVideo, secondaryVideo) { | |
if (Math.abs(mainVideo.currentTime - secondaryVideo.currentTime) > syncTolerance) { | |
secondaryVideo.currentTime = mainVideo.currentTime; | |
} | |
if (mainVideo.paused && !secondaryVideo.paused) { | |
secondaryVideo.pause(); | |
} else if (!mainVideo.paused && secondaryVideo.paused) { | |
secondaryVideo.play(); | |
} | |
} | |
// Set up timeupdate event listeners for smoother sync | |
video1.addEventListener('timeupdate', () => syncVideos(video1, video2)); | |
video2.addEventListener('timeupdate', () => syncVideos(video2, video1)); | |
// Mouse events for slider | |
slider.addEventListener('mousedown', () => isDragging = true); | |
window.addEventListener('mouseup', () => isDragging = false); | |
window.addEventListener('mousemove', (event) => { | |
if (!isDragging) return; | |
handleSlide(event.clientX); | |
}); | |
// Touch events for slider | |
slider.addEventListener('touchstart', () => isDragging = true); | |
window.addEventListener('touchend', () => isDragging = false); | |
window.addEventListener('touchmove', (event) => { | |
if (!isDragging) return; | |
handleSlide(event.touches[0].clientX); | |
}); | |
function handleSlide(positionX) { | |
const rect = container.getBoundingClientRect(); | |
const offsetX = positionX - rect.left; | |
const sliderPosition = Math.max(0, Math.min(offsetX, rect.width)); | |
// Move slider position | |
slider.style.left = `${sliderPosition}px`; | |
// Adjust second video visibility using clip-path | |
video2.style.clipPath = `inset(0 ${rect.width - sliderPosition}px 0 0)`; | |
} | |
// Function to update clip-path in real-time | |
function updateClipPath() { | |
const rect = container.getBoundingClientRect(); | |
const sliderPosition = parseFloat(window.getComputedStyle(slider).left); | |
video2.style.clipPath = `inset(0 ${rect.width - sliderPosition}px 0 0)`; | |
requestAnimationFrame(updateClipPath); | |
} | |
// Enable user control after the animation ends | |
slider.addEventListener('animationend', () => { | |
slider.classList.add('ready'); | |
slider.style.animation = 'none'; | |
slider.style.cursor = 'ew-resize'; | |
slider.style.pointerEvents = 'auto'; | |
slider.setAttribute('data-arrow', '<'); // Set left arrow after animation ends | |
}); | |
// Start updating clip-path once the page loads | |
window.onload = () => updateClipPath(); | |
</script> | |
</body> | |
</html> | |