Как узнать прокрутку к элементу в Javascript?

Я использую Javascript метод Element.scrollIntoView()
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

Есть ли способ узнать, когда свиток закончился. Скажем, была анимация, или я установил {behavior: smooth}.

Я предполагаю, что прокрутка является асинхронной и хочет знать, есть ли какой-либо механизм обратного вызова.

Ответ 1

Вы можете использовать IntersectionObserver, проверить, есть ли элемент .isIntersecting в функции обратного вызова IntersectionObserver

const element = document.getElementById("box");

const intersectionObserver = new IntersectionObserver((entries) => {
  let [entry] = entries;
  if (entry.isIntersecting) {
    setTimeout(() => alert('${entry.target.id} is visible'), 100)
  }
});
// start observing
intersectionObserver.observe(box);

element.scrollIntoView({behavior: "smooth"});
body {
  height: calc(100vh * 2);
}

#box {
  position: relative;
  top:500px;
}
<div id="box">
box
</div>

Ответ 2

Событие scrollEnd отсутствует, но вы можете прослушивать событие scroll и проверять, продолжает ли оно прокручивать окно:

var scrollTimeout;
addEventListener('scroll', function(e) {
    clearTimeout(scrollTimeout);
    scrollTimeout = setTimeout(function() {
        console.log('Scroll ended');
    }, 100);
});

Ответ 3

Я не эксперт в javascript, но я сделал это с помощью jQuery. Я надеюсь, что это помогает

$("#mybtn").click(function() {
    $('html, body').animate({
        scrollTop: $("div").offset().top
    }, 2000);
});

$( window ).scroll(function() {
  $("div").html("scrolling");
  if($(window).scrollTop() == $("div").offset().top) {
    $("div").html("Ended");
  }
})
body { height: 2000px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<button id="mybtn">Scroll to Text</button>
<br><br><br><br><br><br><br><br>
<div>example text</div>

Ответ 4

Для этого "гладкого" поведения все спецификации говорят, что это

Когда пользовательский агент должен выполнить плавную прокрутку окна прокрутки в поле, он должен обновить позицию прокрутки поля в соответствии с пользовательским агентом по сравнению с пользовательским агентом, определенным пользователем. количество времени.

(emphasizes mine)

Так что не только нет единственного события, которое сработает после его завершения, но мы даже не можем предполагать какое-либо стабилизированное поведение между различными браузерами.

И действительно, нынешние Firefox и Chrome уже различаются по своему поведению:

  • Похоже, что Firefox имеет фиксированную длительность, и, независимо от расстояния прокрутки, он будет делать это за фиксированную длительность (~ 500 мс)
  • Chrome, с другой стороны, будет использовать скорость, то есть продолжительность операции будет зависеть от расстояния до прокрутки с жестким ограничением в 3 с.

Так что это уже дисквалифицирует все решения, основанные на тайм-ауте для этой проблемы.

Теперь один из ответов здесь предлагает использовать MutationObserver, который является не слишком плохим решением, но не слишком переносимым и не учитывает опции inline и block.

Поэтому лучше всего регулярно проверять, прекратили ли мы прокрутку. Чтобы сделать это неинвазивным способом, мы можем запустить цикл с питанием requestAnimationFrame, чтобы наши проверки выполнялись только один раз за кадр.

Вот одна из таких реализаций, которая вернет Обещание, которое будет разрешено после завершения операции прокрутки.
Примечание. В этом коде отсутствует способ проверить, была ли операция выполнена успешно, поскольку, если на странице произойдет другая операция прокрутки, все текущие операции будут отменены, но я позволю это как упражнение для читателя.

const buttons = [ ...document.querySelectorAll( 'button' ) ];

document.addEventListener( 'click', ({ target }) => {
  // handle delegated event
  target = target.closest('button');
  if( !target ) { return; }
  // find where to go next
  const next_index =  (buttons.indexOf(target) + 1) % buttons.length;
  const next_btn = buttons[next_index];
  const block_type = target.dataset.block;

  // make it red
  document.body.classList.add( 'scrolling' );
  
  smoothScroll( next_btn, { block: block_type })
    .then( () => {
      // remove the red
      document.body.classList.remove( 'scrolling' );
    } )
});


/* 
 *
 * Promised based scrollIntoView( { behavior: 'smooth' } )
 * @param { Element } elem
 **  ::An Element on which we'll call scrollIntoView
 * @param { object } [options]
 **  ::An optional scrollIntoViewOptions dictionary
 * @return { Promise } (void)
 **  ::Resolves when the scrolling ends
 *
 */
function smoothScroll( elem, options ) {
  return new Promise( (resolve) => {
    if( !( elem instanceof Element ) ) {
      throw new TypeError( 'Argument 1 must be an Element' );
    }
    let same = 0; // a counter
    let lastPos = null; // last known Y position
    // pass the user defined options along with our default
    const scrollOptions = Object.assign( { behavior: 'smooth' }, options );

    // let begin
    elem.scrollIntoView( scrollOptions );
    requestAnimationFrame( check );
    
    // this function will be called every painting frame
    // for the duration of the smooth scroll operation
    function check() {
      // check our current position
      const newPos = elem.getBoundingClientRect().top;
      
      if( newPos === lastPos ) { // same as previous
        if(same ++ > 2) { // if it more than two frames
/* @todo: verify it succeeded
 * if(isAtCorrectPosition(elem, options) {
 *   resolve();
 * } else {
 *   reject();
 * }
 * return;
 */
          return resolve(); // we've come to an halt
        }
      }
      else {
        same = 0; // reset our counter
        lastPos = newPos; // remember our current position
      }
      // check again next painting frame
      requestAnimationFrame(check);
    }
  });
}
p {
  height: 400vh;
  width: 5px;
  background: repeat 0 0 / 5px 10px
    linear-gradient(to bottom, black 50%, white 50%);
}
body.scrolling {
  background: red;
}
<button data-block="center">scroll to next button <code>block:center</code></button>
<p></p>
<button data-block="start">scroll to next button <code>block:start</code></button>
<p></p>
<button data-block="nearest">scroll to next button <code>block:nearest</code></button>
<p></p>
<button>scroll to top</button>

Ответ 5

Эти ответы выше оставляют обработчик событий на месте даже после завершения прокрутки (так что если пользователь прокручивает, его метод продолжает вызываться). Они также не уведомят вас, если прокрутка не требуется. Здесь немного лучший ответ:

$("#mybtn").click(function() {
    $('html, body').animate({
        scrollTop: $("div").offset().top
    }, 2000);

    $("div").html("Scrolling...");

    callWhenScrollCompleted(() => {
        $("div").html("Scrolling is completed!");
    });
});

// Wait for scrolling to stop.
function callWhenScrollCompleted(callback, checkTimeout = 200, parentElement = $(window)) {
  const scrollTimeoutFunction = () => {
    // Scrolling is complete
    parentElement.off("scroll");
    callback();
  };
  let scrollTimeout = setTimeout(scrollTimeoutFunction, checkTimeout);

  parentElement.on("scroll", () => {
    clearTimeout(scrollTimeout);
    scrollTimeout = setTimeout(scrollTimeoutFunction, checkTimeout);
  });
}
body { height: 2000px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<button id="mybtn">Scroll to Text</button>
<br><br><br><br><br><br><br><br>
<div>example text</div>