스크롤시 고정에서 상대적으로 요소


9

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

이런 종류의 작품은 ... 그러나 매끄럽지 않은 모든 것입니다. 또한 웹 페이지를 뒤로 스크롤하는 것이 가능해야합니다.

이 문제를 해결하려고 할 때 발생하는 문제 :

  • 스크롤링 및 고정에서 상대로의 전환은 자연스럽고 부드럽게 느껴야합니다.
  • 래퍼 자체는 고정되어 있지 않고 .text 요소를 포함하고, 비디오는 고정되어 .text 요소가 비디오 요소를 넘어갈 수 있습니다 (효과 생성). 이 .text 요소는 솔루션을 찾는 데 문제를 일으 킵니다.

답변:


3

Airpods Pro 페이지 에서 리버스 엔지니어링을 수행 할 때 애니메이션은을 사용하지 않고을 사용 video합니다 canvas. 구현은 다음과 같습니다.

  • HTTP2를 통해 약 1500 개의 이미지, 실제로 애니메이션의 프레임을 미리로드
  • 다음 형식으로 이미지 배열을 만듭니다. HTMLImageElement
  • 모든 scrollDOM 이벤트에 반응 하고 가장 가까운 이미지에 해당하는 애니메이션 프레임을 요청하십시오.requestAnimationFrame
  • 애니메이션 프레임 요청 콜백에서 ctx.drawImage( 요소 ctx2d컨텍스트)를 사용하여 이미지를 표시하십시오.canvas

requestAnimationFrame기능은 대상 화면의 "초당 프레임"속도로 프레임이 지연되고 동기화되므로보다 부드러운 효과를 얻을 수 있습니다.

스크롤 이벤트에 프레임을 올바르게 표시하는 방법에 대한 자세한 내용은 https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event를 참조하십시오.

당신의 주요 문제에 관해 말하면, 나는 다음으로 구성된 작동 솔루션을 가지고 있습니다 :

  • video요소 와 높이 및 너비가 동일한 자리 표시자를 만듭니다 . absolute위치 설정시 비디오가 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();

});

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이 어떻게했는지에 대한 통찰력 정보와 훌륭하고 매끄러운 솔루션에 대한 더 큰 감사에 감사드립니다!
오닐

1

위로 스크롤 할 때 비디오가 제자리에 고정되도록하려면에서 fixed로 전환하는 위치를 표시해야 합니다 relative.

//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는 비디오 애니메이션 후에 사용자가 보는 첫 번째 콘텐츠 여야합니다.
오닐
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.