Defilare lină și decalaj cu scrollIntoView în JavaScript


Am acest cod pentru site-ul meu:

function clickMe() {
  var element = document.getElementById('about');
  element.scrollIntoView({
    block: 'start',
    behavior: 'smooth',
  });
}

Acest lucru funcționează destul de bine, dar am un antet fix, așa că atunci când codul derulează până la element, antetul stă în calea acestuia.

Există o modalitate de a avea un offset și de a face să deruleze lin?

Răspuns #1

Există o modalitate de a avea un offset și de a face să deruleze lin?

#Da, dar nu cu scrollIntoView()

scrollIntoViewOptions al Element.scrollIntoView() nu vă permite să folosiți un offset. Este util doar atunci când doriți să derulați către poziția exactă a elementului.

Cu toate acestea, poți folosi Window.scrollTo() cu opțiuni pentru a derula atât la o poziție offset, cât și pentru a face acest lucru lin.

Dacă aveți un antet cu o înălțime de 30px, de exemplu, ați putea face următoarele:

function scrollToTargetAdjusted(){
    var element = document.getElementById('targetElement');
    var headerOffset = 45;
    var elementPosition = element.getBoundingClientRect().top;
    var offsetPosition = elementPosition + window.pageYOffset - headerOffset;
  
    window.scrollTo({
         top: offsetPosition,
         behavior: "smooth"
    });
}

Acest lucru va derula lin către elementul tău astfel încât să nu fie blocat de vizualizare de antetul tău.

Notă: Scazi decalajul deoarece vrei să te oprești înainte să derulezi antetul peste elementul tău.

#Vedeți-l în acțiune

Poți compara ambele opțiuni în fragmentul de mai jos.

<script type="text/javascript">
  function scrollToTarget() {

    var element = document.getElementById('targetElement');
    element.scrollIntoView({
      block: "start",
      behavior: "smooth",
    });
  }

  function scrollToTargetAdjusted() {
        var element = document.getElementById('targetElement');
      var headerOffset = 45;
        var elementPosition = element.getBoundingClientRect().top;
      var offsetPosition = elementPosition + window.pageYOffset - headerOffset;
      
      window.scrollTo({
          top: offsetPosition,
          behavior: "smooth"
      });   
  }

  function backToTop() {
    window.scrollTo(0, 0);
  }
</script>

<div id="header" style="height:30px; width:100%; position:fixed; background-color:lightblue; text-align:center;"> <b>Fixed Header</b></div>

<div id="mainContent" style="padding:30px 0px;">

  <button type="button" onclick="scrollToTarget();">element.scrollIntoView() smooth, header blocks view</button>
  <button type="button" onclick="scrollToTargetAdjusted();">window.scrollTo() smooth, with offset</button>

  <div style="height:1000px;"></div>
  <div id="targetElement" style="background-color:red;">Target</div>
  <br/>
  <button type="button" onclick="backToTop();">Back to top</button>
  <div style="height:1000px;"></div>
</div>

Editează

window.pageYOffset a fost adăugat, pentru a rezolva problema legată de comentariile lui @coreyward

Răspunsul #2

Răspunsul de la Søren D. Ptæus m-a pus pe drumul cel bun, dar am avut probleme cu getBoundingClientRect() când nu eram în partea de sus a ferestrei.

Soluția mea adaugă puțin la a lui pentru a face getBoundingClientRect() să funcționeze puțin mai consistent și cu mai multă versatilitate. Am folosit abordarea prezentată aici și am implementat-o pentru ca acest lucru să funcționeze așa cum era intenționat.

const element = document.getElementById('targetElement');
const offset = 45;
const bodyRect = document.body.getBoundingClientRect().top;
const elementRect = element.getBoundingClientRect().top;
const elementPosition = elementRect - bodyRect;
const offsetPosition = elementPosition - offset;

window.scrollTo({
  top: offsetPosition,
  behavior: 'smooth'
});

Exemplu Codepen

Amintește-ți să incluzi polyfill-ul când implementezi asta!

Răspunsul #3

Poți folosi scrollIntoView() așa cum ai în exemplul tău

function clickMe() {
  var element = document.getElementById('about');
  element.scrollIntoView({
    block: 'start',
    behavior: 'smooth',
  });
}

dacă adaugi scroll-margin cu înălțimea antetului la elementul țintă (about):

.about {
  scroll-margin: 100px;
}

Nu mai este nevoie de nimic altceva. scroll-margin este susținut de toate browserele moderne.

Răspunsul #4

Soluție simplă, dar elegantă dacă elementul are o înălțime mică (mai scurtă decât viewport-ul):

element.scrollIntoView({ behavior: 'auto' /*or smooth*/, block: 'center' });

block: center va derula elementul astfel încât centrul elementului să fie la centrul vertical al ferestrei de vizualizare, astfel încât antetul de sus să nu îl acopere.

EDIT 8.5.22: behavior: instant a fost folosit în trecut, dar a fost eliminat din browsere.

Răspunsul #5

Răspunsul lui Søren D. Ptæus este aproape corect, dar funcționează numai când utilizatorul este în vârf. Acest lucru se datorează faptului că getBoundingClientRect ne va oferi întotdeauna înălțimea relativă, iar utilizarea window.scrollTo cu o înălțime relativă nu funcționează.

ekfuhrmann a îmbunătățit răspunsul obținând înălțimea totală din elementul body și calculând înălțimea reală. Totuși, cred că poate fi mai simplu decât atât, putem pur și simplu să folosim poziția relativă și să folosim window.scrollBy.

Notă: Diferența cheie este window.scrollBy

const HEADER_HEIGHT = 45;

function scrollToTargetAdjusted(){
    const element = document.getElementById('targetElement');
    const elementPosition = element.getBoundingClientRect().top;
    const offsetPosition = elementPosition - HEADER_HEIGHT;

    window.scrollBy({
         top: offsetPosition,
         behavior: "smooth"
    });
}

Răspuns #6

Am încercat celelalte soluții, dar am întâmpinat un comportament ciudat. Totuși, aceasta a funcționat pentru mine.

function scrollTo(id) {
    var element = document.getElementById(id);
    var headerOffset = 60;
    var elementPosition = element.offsetTop;
    var offsetPosition = elementPosition - headerOffset;
    document.documentElement.scrollTop = offsetPosition;
    document.body.scrollTop = offsetPosition; // For Safari
}

și stilul:

html {
    scroll-behavior: smooth;
}