Lorsque vous effectuez un reverse engineering sur la page Airpods Pro , nous remarquons que l'animation n'utilise pas a video
, mais a canvas
. L'implémentation est la suivante:
- Préchargez environ 1500 images sur HTTP2, en fait les images de l'animation
- Créez un tableau d'images sous la forme de
HTMLImageElement
- Réagissez à chaque
scroll
événement DOM et demandez un cadre d'animation correspondant à l'image la plus proche, avecrequestAnimationFrame
- Dans le cadre d'animation demande un rappel, affichez l'image en utilisant
ctx.drawImage
( ctx
étant le 2d
contexte de l' canvas
élément)
La requestAnimationFrame
fonction devrait vous aider à obtenir un effet plus fluide car les images seront différées et synchronisées avec le taux "images par seconde" de l'écran cible.
Pour plus d'informations sur la façon d'afficher correctement un cadre sur un événement de défilement, vous pouvez lire ceci: https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event
Cela dit, concernant votre problème principal, j'ai une solution de travail qui consiste à:
- Création d'un espace réservé, de même hauteur et largeur que l'
video
élément. Son but est d'éviter que la vidéo ne chevauche le reste du code HTML lorsqu'elle est absolute
positionnée
- Dans le
scroll
rappel d'événement, lorsque l'espace réservé atteint le haut de la fenêtre, définissez la position de la vidéo sur absolute
et la bonne top
valeur
L'idée est que la vidéo reste toujours hors du flux et se déroule sur l'espace réservé au bon moment lors du défilement vers le bas.
Voici le JavaScript:
//Get video element
let video = $("#video-effect-wrapper video").get(0);
video.pause();
let topOffset;
$(window).resize(onResize);
function computeVideoSizeAndPosition() {
const { width, height } = video.getBoundingClientRect();
const videoPlaceholder = $("#video-placeholder");
videoPlaceholder.css("width", width);
videoPlaceholder.css("height", height);
topOffset = videoPlaceholder.position().top;
}
function updateVideoPosition() {
if ($(window).scrollTop() >= topOffset) {
$(video).css("position", "absolute");
$(video).css("left", "0px");
$(video).css("top", topOffset);
} else {
$(video).css("position", "fixed");
$(video).css("left", "0px");
$(video).css("top", "0px");
}
}
function onResize() {
computeVideoSizeAndPosition();
updateVideoPosition();
}
onResize();
//Initialize video effect wrapper
$(document).ready(function () {
//If .first text-element is set, place it in bottom of
//text-display
if ($("#video-effect-wrapper .text.first").length) {
//Get text-display position properties
let textDisplay = $("#video-effect-wrapper #text-display");
let textDisplayPosition = textDisplay.offset().top;
let textDisplayHeight = textDisplay.height();
let textDisplayBottom = textDisplayPosition + textDisplayHeight;
//Get .text.first positions
let firstText = $("#video-effect-wrapper .text.first");
let firstTextHeight = firstText.height();
let startPositionOfFirstText = textDisplayBottom - firstTextHeight + 50;
//Set start position of .text.first
firstText.css("margin-top", startPositionOfFirstText);
}
});
//Code to launch video-effect when user scrolls
$(document).scroll(function () {
//Calculate amount of pixels there is scrolled in the video-effect-wrapper
let n = $(window).scrollTop() - $("#video-effect-wrapper").offset().top + 408;
n = n < 0 ? 0 : n;
//If .text.first is set, we need to calculate one less text-box
let x = $("#video-effect-wrapper .text.first").length == 0 ? 0 : 1;
//Calculate how many percent of the video-effect-wrapper is currenlty scrolled
let percentage = n / ($(".text").eq(1).outerHeight(true) * ($("#video-effect-wrapper .text").length - x)) * 100;
//console.log(percentage);
//console.log(percentage);
//Get duration of video
let duration = video.duration;
//Calculate to which second in video we need to go
let skipTo = duration / 100 * percentage;
//console.log(skipTo);
//Skip to specified second
video.currentTime = skipTo;
//Only allow text-elements to be visible inside text-display
let textDisplay = $("#video-effect-wrapper #text-display");
let textDisplayHeight = textDisplay.height();
let textDisplayTop = textDisplay.offset().top;
let textDisplayBottom = textDisplayTop + textDisplayHeight;
$("#video-effect-wrapper .text").each(function (i) {
let text = $(this);
if (text.offset().top < textDisplayBottom && text.offset().top > textDisplayTop) {
let textProgressPoint = textDisplayTop + (textDisplayHeight / 2);
let textScrollProgressInPx = Math.abs(text.offset().top - textProgressPoint - textDisplayHeight / 2);
textScrollProgressInPx = textScrollProgressInPx <= 0 ? 0 : textScrollProgressInPx;
let textScrollProgressInPerc = textScrollProgressInPx / (textDisplayHeight / 2) * 100;
//console.log(textScrollProgressInPerc);
if (text.hasClass("first"))
textScrollProgressInPerc = 100;
text.css("opacity", textScrollProgressInPerc / 100);
} else {
text.css("transition", "0.5s ease");
text.css("opacity", "0");
}
});
updateVideoPosition();
});
Voici le HTML:
<div id="video-effect-wrapper">
<video muted autoplay>
<source src="https://ndvibes.com/test/video/video.mp4" type="video/mp4" id="video">
</video>
<div id="text-display"/>
<div class="text first">
Scroll down to test this little demo
</div>
<div class="text">
Still a lot to improve
</div>
<div class="text">
So please help me
</div>
<div class="text">
Thanks! :D
</div>
</div>
<div id="video-placeholder">
</div>
<div id="other-parts-of-website">
<p>
Normal scroll behaviour wanted.
</p>
<p>
Normal scroll behaviour wanted.
</p>
<p>
Normal scroll behaviour wanted.
</p>
<p>
Normal scroll behaviour wanted.
</p>
<p>
Normal scroll behaviour wanted.
</p>
<p>
Normal scroll behaviour wanted.
</p>
</div>
Vous pouvez essayer ici: https://jsfiddle.net/crkj1m0v/3/
Si vous souhaitez que la vidéo se verrouille à nouveau lorsque vous faites défiler vers le haut, vous devrez marquer l'endroit où vous passez de
fixed
àrelative
.la source