fix(expertise): refonte logique vidéo forward/reverse sans flash
Problèmes corrigés : - Flash au changement de direction : l'ancien code attendait un événement seeked avant de switcher la visibilité, montrant un mauvais frame. Maintenant on positionne la vidéo cible AVANT de la rendre visible. - Reprise forward depuis mauvaise position : stopActiveVideo() + playForward() synchronisent correctement currentFwdTime avant le switch. - segmentEnds[0] = 0 → currentFwdTime snappé exactement à la cible dans les handlers timeupdate (plus de dérive à 0.1). Changements : - switchToForward/switchToReverse : positionnent la cible puis changent isReverse - playForward/playReverse simplifiés : plus de logique seeked conditionnelle - navigate() simplifié : forward=down, reverse=up, extension de cible si déjà actif - CSS : opacity 0/1 + transition au lieu de display none/block pour éviter les flashes - timeupdate : snap exact à la cible au lieu d'approximation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dd69e54746
commit
ff5b0028f1
1 changed files with 65 additions and 46 deletions
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
// --- Constants ---
|
||||
const PLAY_DELAY_MS = 300 // ms delay before starting playback on slide activation
|
||||
const REV_TARGET_MIN = 0.1 // min revTarget to avoid pausing exactly at videoDuration
|
||||
|
||||
// --- State ---
|
||||
let currentItem = $state(0)
|
||||
|
|
@ -37,10 +36,12 @@
|
|||
if (isActive) navigation.setScrolled(currentItem > 0)
|
||||
})
|
||||
|
||||
// segmentEnds[i] = position forward correspondant à l'item i.
|
||||
// Item 0 = début de vidéo (0), item final = fin de vidéo (duration).
|
||||
const segmentEnds = $derived(
|
||||
itemCount > 0 && videoDuration > 0
|
||||
? Array.from({ length: itemCount }, (_, i) =>
|
||||
itemCount === 1 ? videoDuration : videoDuration * i / (itemCount - 1))
|
||||
itemCount > 1 && videoDuration > 0
|
||||
? Array.from({ length: itemCount }, (_, i) => videoDuration * i / (itemCount - 1))
|
||||
: itemCount === 1 ? [0]
|
||||
: []
|
||||
)
|
||||
|
||||
|
|
@ -68,6 +69,8 @@
|
|||
}
|
||||
|
||||
// --- Video control helpers ---
|
||||
|
||||
/** Stoppe la vidéo active et sauvegarde la position forward-équivalente. */
|
||||
function stopActiveVideo() {
|
||||
if (videoFwd && !videoFwd.paused) {
|
||||
currentFwdTime = videoFwd.currentTime
|
||||
|
|
@ -81,53 +84,65 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch visuel entre les deux vidéos.
|
||||
* Positionne la vidéo cible au bon frame AVANT de l'afficher
|
||||
* pour éliminer les flashes.
|
||||
*/
|
||||
function switchToForward() {
|
||||
if (!isReverse) return
|
||||
// Positionner forward au bon frame avant de l'afficher
|
||||
if (videoFwd) videoFwd.currentTime = currentFwdTime
|
||||
isReverse = false
|
||||
}
|
||||
|
||||
function switchToReverse() {
|
||||
if (isReverse) return
|
||||
// Positionner reverse au bon frame avant de l'afficher
|
||||
const revPos = videoDuration - currentFwdTime
|
||||
if (videoRev) videoRev.currentTime = revPos
|
||||
isReverse = true
|
||||
}
|
||||
|
||||
function playForward(targetTime) {
|
||||
fwdTarget = targetTime
|
||||
if (videoFwd) videoFwd.currentTime = currentFwdTime
|
||||
if (isReverse) {
|
||||
videoFwd?.addEventListener('seeked', () => {
|
||||
isReverse = false
|
||||
// Switcher visuellement, puis lancer la lecture
|
||||
switchToForward()
|
||||
videoFwd?.play().catch(() => {})
|
||||
}, { once: true })
|
||||
} else {
|
||||
videoFwd?.play().catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
function playReverse(targetTime) {
|
||||
const revStart = Math.max(0, videoDuration - currentFwdTime)
|
||||
revTarget = targetTime
|
||||
function playReverse(targetFwdPos) {
|
||||
// targetFwdPos = la position forward-équivalente où on veut arriver
|
||||
// On la convertit en position dans la vidéo reverse
|
||||
revTarget = videoDuration - Math.max(targetFwdPos, 0)
|
||||
const revStart = videoDuration - currentFwdTime
|
||||
if (videoRev) videoRev.currentTime = revStart
|
||||
if (!isReverse) {
|
||||
videoRev?.addEventListener('seeked', () => {
|
||||
isReverse = true
|
||||
// Switcher visuellement, puis lancer la lecture
|
||||
switchToReverse()
|
||||
videoRev?.play().catch(() => {})
|
||||
}, { once: true })
|
||||
} else {
|
||||
videoRev?.play().catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
// --- Navigate: move one item up or down (called by composable) ---
|
||||
function navigate(direction, newItem) {
|
||||
const prevItem = currentItem
|
||||
currentItem = newItem
|
||||
const targetPos = segmentEnds[currentItem]
|
||||
|
||||
if (direction === 'down' && videoFwd && !videoFwd.paused) {
|
||||
fwdTarget = segmentEnds[currentItem]
|
||||
} else if (direction === 'up' && videoRev && !videoRev.paused) {
|
||||
const liveFwdPos = videoDuration - videoRev.currentTime
|
||||
const segBoundary = segmentEnds[currentItem]
|
||||
const targetFwd = liveFwdPos > segBoundary ? segBoundary : 0
|
||||
revTarget = videoDuration - Math.max(targetFwd, REV_TARGET_MIN)
|
||||
if (direction === 'down') {
|
||||
if (videoFwd && !videoFwd.paused) {
|
||||
// Forward déjà en cours : juste mettre à jour la cible
|
||||
fwdTarget = targetPos
|
||||
} else {
|
||||
stopActiveVideo()
|
||||
if (direction === 'down') {
|
||||
playForward(segmentEnds[currentItem])
|
||||
playForward(targetPos)
|
||||
}
|
||||
} else {
|
||||
const segBoundary = segmentEnds[currentItem]
|
||||
const targetFwd = currentFwdTime > segBoundary ? segBoundary : 0
|
||||
playReverse(videoDuration - Math.max(targetFwd, REV_TARGET_MIN))
|
||||
if (videoRev && !videoRev.paused) {
|
||||
// Reverse déjà en cours : juste mettre à jour la cible
|
||||
revTarget = videoDuration - Math.max(targetPos, 0)
|
||||
} else {
|
||||
stopActiveVideo()
|
||||
playReverse(targetPos)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +152,7 @@
|
|||
// --- Playback lifecycle ---
|
||||
function initPlayback() {
|
||||
setTimeout(() => {
|
||||
const tryPlay = () => {
|
||||
const init = () => {
|
||||
const dur = videoFwd?.duration
|
||||
if (!dur || itemCount === 0) return
|
||||
videoDuration = dur
|
||||
|
|
@ -148,11 +163,11 @@
|
|||
requestAnimationFrame(() => computeOffset())
|
||||
}
|
||||
if (videoFwd?.duration) {
|
||||
tryPlay()
|
||||
init()
|
||||
} else {
|
||||
videoFwd?.addEventListener('loadedmetadata', () => {
|
||||
videoDuration = videoFwd.duration
|
||||
tryPlay()
|
||||
init()
|
||||
}, { once: true })
|
||||
}
|
||||
}, PLAY_DELAY_MS)
|
||||
|
|
@ -177,7 +192,8 @@
|
|||
const onFwdUpdate = () => {
|
||||
if (!videoFwd || videoFwd.paused || fwdTarget === null) return
|
||||
if (videoFwd.currentTime >= fwdTarget) {
|
||||
currentFwdTime = videoFwd.currentTime
|
||||
currentFwdTime = fwdTarget // snap exactement à la cible
|
||||
videoFwd.currentTime = fwdTarget
|
||||
videoFwd.pause()
|
||||
fwdTarget = null
|
||||
}
|
||||
|
|
@ -187,7 +203,9 @@
|
|||
const onRevUpdate = () => {
|
||||
if (!videoRev || videoRev.paused || revTarget === null) return
|
||||
if (videoRev.currentTime >= revTarget) {
|
||||
currentFwdTime = videoDuration - videoRev.currentTime
|
||||
const fwdPos = videoDuration - revTarget
|
||||
currentFwdTime = Math.max(fwdPos, 0) // snap exactement
|
||||
videoRev.currentTime = revTarget
|
||||
videoRev.pause()
|
||||
revTarget = null
|
||||
}
|
||||
|
|
@ -303,11 +321,12 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.expertise-bg video.active {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Custom vertical lines */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue