Khi thực hiện một số kỹ thuật đảo ngược trên trang Airpods Pro , chúng tôi nhận thấy rằng hình ảnh động không sử dụng a video
, nhưng a canvas
. Việc thực hiện như sau:
- Tải trước khoảng 1500 hình ảnh qua HTTP2, thực sự là các khung hình của hình ảnh động
- Tạo một mảng hình ảnh ở dạng
HTMLImageElement
- Phản ứng với mọi
scroll
sự kiện DOM và yêu cầu khung hình động tương ứng với hình ảnh gần nhất, vớirequestAnimationFrame
- Trong khung hình động yêu cầu gọi lại, hiển thị hình ảnh bằng cách sử dụng
ctx.drawImage
( ctx
là 2d
bối cảnh của canvas
phần tử)
Các requestAnimationFrame
chức năng này sẽ giúp bạn đạt được một hiệu ứng mượt mà như các khung sẽ được hoãn lại và đồng bộ với "khung hình mỗi giây" tỷ lệ của màn hình mục tiêu.
Để biết thêm thông tin về cách hiển thị đúng khung trên sự kiện cuộn, bạn có thể đọc phần này: https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event
Điều đó đang được nói, liên quan đến vấn đề chính của bạn, tôi có một giải pháp làm việc bao gồm:
- Tạo một trình giữ chỗ, có cùng chiều cao và chiều rộng so với
video
phần tử. Mục đích của nó là để tránh video chồng lấp phần còn lại của HTML khi được đặt thành absolute
vị trí
- Trong cuộc
scroll
gọi lại sự kiện, khi trình giữ chỗ đạt đến đỉnh của chế độ xem, đặt vị trí của video thành absolute
và top
giá trị đúng
Ý tưởng là video luôn nằm ngoài luồng và diễn ra trên trình giữ chỗ vào đúng thời điểm khi cuộn xuống phía dưới.
Đây là 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();
});
Đây là 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>
Bạn có thể thử tại đây: https://jsfiddle.net/crkj1m0v/3/