滚动时元素从固定到相对

O'N*_*iel 9 html javascript css jquery

我制作了一个包装器,其中我在他们的 Airpods Pro 页面上制作了与 Apple 相同的效果。它基本上是一个视频,当我滚动视频时一点一点地播放。视频的位置是固定的,因此文本可以很好地滚动。但是,文本仅在特定分区的偏移量(文本显示)之间时才可见。

那部分工作正常。现在我希望当用户滚动到视频的结尾并因此完成动画时,视频效果包装器从固定位置转到相对位置。因此该网站将在 video-animation 之后正常滚动其内容

JSFIDDLE 代码 + 演示

这是我已经尝试过的示例:

        //If video-animation ended: Make position of video-wrapper relative to continue scrolling
        if ($(window).scrollTop() >= $("#video-effect-wrapper").height()) {
            $(video).css("position", "relative");
            $("#video-effect-wrapper .text").css("display", "none");
        }
Run Code Online (Sandbox Code Playgroud)

这种作品......但一切都是顺利的。并且还需要可以向后反向滚动网页。

我在尝试解决此问题时遇到的问题:

  • 滚动和从固定到相对的过渡需要感觉自然流畅
  • 包装器本身不是固定的并且包含 .text 元素,视频是固定的,因此 .text 元素可以覆盖视频元素(创建效果)。这些 .text 元素会导致试图找到解决方案的问题

Twi*_*her 4

在Airpods Pro 页面上进行一些逆向工程时,我们注意到动画没有使用 a video,而是使用canvas. 实现如下:

  • 通过 HTTP2 预加载大约 1500 张图像,实际上是动画的帧
  • 创建以下形式的图像数组HTMLImageElement
  • 响应每个scrollDOM 事件并请求与最近的图像对应的动画帧,其中requestAnimationFrame
  • ctx.drawImage在动画帧请求回调中,使用(ctx作为元素2d的上下文canvas)显示图像

requestAnimationFrame功能应该可以帮助您实现更平滑的效果,因为帧将被延迟并与目标屏幕的“每秒帧数”速率同步。

有关如何在滚动事件上正确显示框架的更多信息,您可以阅读以下内容:https: //developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event

话虽如此,关于您的主要问题,我有一个可行的解决方案,其中包括:

  • 创建一个占位符,其高度和宽度与元素相同videoabsolute其目的是避免设置为位置时视频与 HTML 的其余部分重叠
  • 进入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();

});
Run Code Online (Sandbox Code Playgroud)

这是 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>
Run Code Online (Sandbox Code Playgroud)

您可以在这里尝试: https: //jsfiddle.net/crkj1m0v/3/

  • 虽然这是关于如何实现此类动画的有趣且有用的背景,但它似乎与 @oniel 的问题并不特别相关,该问题特定于动画完成后如何恢复页面的滚动。正如 O'Niel 所指出的,滚动和播放之间的关联已经发挥作用。 (2认同)