Cuando hacemos ingeniería inversa en la página de Airpods Pro , notamos que la animación no usa a video
, sino a canvas
. La implementación es la siguiente:
- Precargue aproximadamente 1500 imágenes sobre HTTP2, en realidad los cuadros de la animación
- Crear una matriz de imágenes en forma de
HTMLImageElement
- Reaccione a cada
scroll
evento DOM y solicite un cuadro de animación correspondiente a la imagen más cercana, conrequestAnimationFrame
- En el marco de animación, solicita la devolución de llamada, muestra la imagen usando
ctx.drawImage
( ctx
siendo el 2d
contexto del canvas
elemento)
La requestAnimationFrame
función debería ayudarlo a lograr un efecto más suave ya que los fotogramas se diferirán y sincronizarán con la velocidad de "fotogramas por segundo" de la pantalla de destino.
Para obtener más información sobre cómo mostrar correctamente un marco en un evento de desplazamiento, puede leer esto: https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event
Dicho esto, con respecto a su problema principal, tengo una solución de trabajo que consiste en:
- Crear un marcador de posición, de la misma altura y anchura que el
video
elemento. Su propósito es evitar que el video se superponga con el resto del HTML cuando se establece en la absolute
posición
- En la
scroll
devolución de llamada del evento, cuando el marcador de posición llegue a la parte superior de la ventana gráfica, establezca la posición del video absolute
y el top
valor correcto
La idea es que el video siempre permanezca fuera del flujo y tenga lugar sobre el marcador de posición en el momento correcto al desplazarse hacia abajo.
Aquí está el 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();
});
Aquí está el 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>
Puedes probar aquí: https://jsfiddle.net/crkj1m0v/3/
Si desea que el vídeo a la espalda de bloqueo en su lugar mientras se desplaza una copia de seguridad, tendrá que marcar el lugar donde se cambia de
fixed
arelative
.fuente