องค์ประกอบจากคงที่ญาติเมื่อเลื่อน


9

ผมได้ทำเสื้อคลุมที่ฉันได้เคลื่อนไหวผลเช่นเดียวกับแอปเปิ้ลในAirpods Pro หน้าของพวกเขา เป็นวิดีโอเมื่อฉันเลื่อนดูวิดีโอเล่นทีละนิด ตำแหน่งของวิดีโอได้รับการแก้ไขเพื่อให้ข้อความเลื่อนไปทางเหนือ อย่างไรก็ตามข้อความจะมองเห็นได้เฉพาะเมื่อระหว่างออฟเซ็ตของส่วนเฉพาะ (text-display)

ส่วนนั้นทำงานได้ดี ตอนนี้ฉันต้องการที่เมื่อผู้ใช้เลื่อนไปที่จุดสิ้นสุดของวิดีโอและทำให้การเคลื่อนไหวเสร็จสิ้นแล้ววิดีโอเอฟเฟ็กต์แรปเปอร์จะเปลี่ยนจากตำแหน่งคงที่ไปยังตำแหน่งสัมพัทธ์ ดังนั้นเว็บไซต์ที่จะเลื่อนเนื้อหาของมันตามปกติหลังจากที่วิดีโอภาพเคลื่อนไหว

JSFIDDLE CODE + DEMO

นี่คือตัวอย่างของสิ่งที่ฉันได้ลองไปแล้ว:

        //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");
        }

งานประเภทนี้ ... แต่ทุกอย่างก็ราบรื่นยกเว้น และมันก็จำเป็นที่จะต้องย้อนกลับเลื่อนหน้าเว็บไปข้างหลัง

ปัญหาที่ฉันพบเมื่อพยายามแก้ไขปัญหานี้:

  • การเลื่อนและการเปลี่ยนจากความต้องการคงที่ไปเป็นความรู้สึกที่เป็นธรรมชาติและราบรื่น
  • เสื้อคลุมตัวเองไม่ได้รับการแก้ไขและมีองค์ประกอบ. text วิดีโอได้รับการแก้ไขเพื่อให้องค์ประกอบ. text สามารถข้ามองค์ประกอบวิดีโอ (การสร้างเอฟเฟกต์) องค์ประกอบ. text เหล่านี้ทำให้เกิดปัญหาในการพยายามค้นหาวิธีแก้ไข

คำตอบ:


3

เมื่อทำวิศวกรรมย้อนกลับบางอย่างเกี่ยวกับAirpods Pro หน้าเราสังเกตเห็นว่าการเคลื่อนไหวไม่ได้ใช้แต่video canvasการดำเนินการดังต่อไปนี้:

  • โหลดล่วงหน้าประมาณ 1,500 ภาพผ่าน HTTP2 ซึ่งเป็นเฟรมภาพเคลื่อนไหว
  • สร้างอาร์เรย์ของรูปภาพในรูปแบบของ HTMLImageElement
  • ตอบสนองต่อทุกscrollเหตุการณ์ DOM และขอเฟรมภาพเคลื่อนไหวที่สอดคล้องกับภาพที่ใกล้ที่สุดด้วยrequestAnimationFrame
  • ในเฟรมภาพเคลื่อนไหวขอให้โทรกลับแสดงภาพโดยใช้ctx.drawImage( ctxเป็น2dบริบทของcanvasองค์ประกอบ)

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/


2
แม้ว่าสิ่งนี้จะเป็นพื้นหลังที่น่าสนใจและมีประโยชน์เกี่ยวกับวิธีการนำแอนิเมชันประเภทนี้มาใช้ แต่ดูเหมือนว่าจะไม่เกี่ยวข้องกับคำถามของ @ oniel ซึ่งเฉพาะเจาะจงกับวิธีการเลื่อนหน้าต่อไปเมื่อการเคลื่อนไหวเสร็จสิ้น ตามที่ O'Niel ตั้งข้อสังเกตว่าความสัมพันธ์ระหว่างการเลื่อนและการเล่นนั้นใช้งานได้แล้ว
Jeremy Caney

1
ขอบคุณสำหรับข้อมูลเชิงลึกเกี่ยวกับวิธีที่ Apple ทำและขอบคุณที่ยิ่งใหญ่กว่าสำหรับโซลูชันที่ดีและราบรื่น!
O'Niel

1

หากคุณต้องการวิดีโอเพื่อล็อคกลับมาอยู่ในสถานที่ที่คุณเลื่อนกลับขึ้นคุณจะต้องทำเครื่องหมายสถานที่ที่คุณเปลี่ยนจากการfixedrelative

//Get video element
let video = $("#video-effect-wrapper video").get(0);
video.pause();

let videoLocked = true;
let lockPoint = -1;
const vidHeight = 408;

//Initialize video effect wrapper
$(document).ready(function() {

  const videoHeight = $("#video-effect-wrapper").height();

  //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 + vidHeight;
    n = n < 0 ? 0 : n;
    // console.log('n: ' + 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);

    //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");
      }
    });


    //If video-animation ended: Make position of video-wrapper relative to continue scrolling
    if (videoLocked) {
      if ($(window).scrollTop() >= videoHeight) {
        $('video').css("position", "relative");
        videoLocked = false;
        lockPoint = $(window).scrollTop() - 10;
        // I gave it an extra 10px to avoid flickering between locked and unlocked.
      }
    } else if ($(window).scrollTop() < lockPoint) {
      $('video').css("position", "fixed");
      videoLocked = true;
    }

  });




});
body {
  margin: 0;
  padding: 0;
  background-color: green;
}

#video-effect-wrapper {
  height: auto;
  width: 100%;
}

#video-effect-wrapper video {
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  z-index: -2;
  object-fit: cover;
}

#video-effect-wrapper::after {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: block;
  background: #000000;
  background: linear-gradient(to top, #434343, #000000);
  opacity: 0.4;
  z-index: -1;
}

#video-effect-wrapper .text {
  color: #FFFFFF;
  font-weight: bold;
  font-size: 3em;
  width: 100%;
  margin-top: 50vh;
  font-family: Arial, sans-serif;
  text-align: center;
  opacity: 0;
  /*
                background-color: blue;
                */
}

#video-effect-wrapper .text.first {
  margin-top: 50vh;
  opacity: 1;
}

#video-effect-wrapper .text:last-child {
  /*margin-bottom: 100vh;*/
  margin-bottom: 50vh;
}

#video-effect-wrapper #text-display {
  display: block;
  width: 100%;
  height: 225px;
  position: fixed;
  top: 50%;
  transform: translate(0, -50%);
  z-index: -1;
  /*
                background-color: red;
                */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<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>
  <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="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>


สวัสดีขอบคุณสำหรับคำตอบของคุณ วิธีนี้ใกล้เข้ามา แต่ฉันยังคงพบปัญหาใหญ่กับรหัสของคุณ: ทันทีที่วิดีโอจบตำแหน่งขององค์ประกอบวิดีโอจะคงที่ แต่มีย่อหน้าสีขาวที่เคลื่อนไหวอยู่บนพื้นหลังสีเขียว div # other-parts-of-website ควรเป็นเนื้อหาแรกที่ผู้ใช้เห็นหลังจากภาพเคลื่อนไหววิดีโอ
O'Niel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.