Роблячи певну реверсивну інженерію на сторінці Airpods Pro , ми помічаємо, що в анімації використовується не a video, а acanvas . Реалізація полягає в наступному:
- Попередньо завантажте близько 1500 зображень через HTTP2, фактично кадри анімації
- Створіть масив зображень у вигляді
HTMLImageElement
- Реагуйте на кожну
scrollподію DOM та запитайте анімаційний кадр, відповідний найближчому зображенню, за допомогоюrequestAnimationFrame
- У кадрі анімації запит на зворотний виклик відображає зображення за допомогою
ctx.drawImage( ctxбудучи 2dконтекстом canvasелемента)
The requestAnimationFrame функція повинна допомогти вам досягти більш плавного ефекту, оскільки кадри будуть відкладені та синхронізовані зі швидкістю "кадрів в секунду" цільового екрану.
Для отримання додаткової інформації про те, як правильно відобразити кадр на події прокрутки, ви можете прочитати це: https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event
З цього приводу, що стосується вашої основної проблеми, у мене є робоче рішення, яке полягає в:
- Створення заповнювача, такої ж висоти та ширини, як
videoелемент. Його мета - уникнути того, щоб відео не перекривало решту HTML, коли встановленоabsolute позицію
- У
scrollзворотний виклик події, коли заповнювач місця досягає верхньої частини огляду, встановіть позицію відео absoluteта правильне topзначення
Ідея полягає в тому, що відео завжди залишається поза потоком і проходить над заповнювачем у потрібний момент, коли прокручується донизу.
Ось 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();
});
Ось 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>
Ви можете спробувати тут: https://jsfiddle.net/crkj1m0v/3/