scrollIntoView Przewija po prostu za daleko

112

Mam stronę, na której pasek przewijania zawierający wiersze tabeli z elementami div w nich jest dynamicznie generowany z bazy danych. Każdy wiersz tabeli działa jak link, tak jak na liście odtwarzania YouTube obok odtwarzacza wideo.

Gdy użytkownik odwiedza stronę, opcja, na której jest włączony, powinna przejść na górę przewijanego elementu div. Ta funkcja działa. Problem w tym, że posuwa się on odrobinę za daleko. Na przykład opcja, w której są włączone, jest o około 10 pikseli za wysoka. Tak więc strona jest odwiedzana, adres URL jest używany do zidentyfikowania, która opcja została wybrana, a następnie przewija tę opcję na górę przewijanego elementu DIV. Uwaga: to nie jest pasek przewijania okna, jest to element div z paskiem przewijania.

Używam tego kodu, aby przenieść wybraną opcję na górę div:

var pathArray = window.location.pathname.split( '/' );

var el = document.getElementById(pathArray[5]);

el.scrollIntoView(true);

Przenosi go na górę elementu div, ale o około 10 pikseli za wysoko. Czy ktoś wie, jak to naprawić?

Matthew Wilson
źródło
47
Brak konfiguracji offsetu scrollIntoViewjest niepokojący.
mystrdat

Odpowiedzi:

58

Jeśli jest to około 10 pikseli, to myślę, że możesz po prostu ręcznie dostosować divprzesunięcie przewijania zawierającego w następujący sposób :

el.scrollIntoView(true);
document.getElementById("containingDiv").scrollTop -= 10;
Lucas Trzesniewski
źródło
2
Czy będzie to miało taki sam efekt animacji jak scrollIntoView?
1252748,
1
@thomas AFAIK, zwykła scrollIntoViewfunkcja DOM nie powoduje animacji. Czy mówisz o wtyczce scrollIntoView jQuery?
Lucas Trzesniewski,
1
To zadziałało, ale to był zły kierunek, więc wszystko, co musiałem zrobić, to zmienić go z + = 10 na - = 10, a teraz ładuje się dobrze, wielkie dzięki za pomoc !!!!
Matthew Wilson,
Tak. Byłem zmieszany. Myślałem, że to ożywione. Przepraszam!
1252748,
17
Może animować, wystarczy dodać el.scrollIntoView({ behavior: 'smooth' });. Wsparcie nie jest jednak świetne!
daveaspinall
115

Płynnie przewiń do odpowiedniej pozycji

Uzyskaj poprawne y współrzędne i użyjwindow.scrollTo({top: y, behavior: 'smooth'})

const id = 'profilePhoto';
const yOffset = -10; 
const element = document.getElementById(id);
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;

window.scrollTo({top: y, behavior: 'smooth'});
Arseniy-II
źródło
2
To jest najbardziej poprawna odpowiedź - łatwo możemy znaleźć pozycję zwoju za pomocą, jQuery('#el').offset().topa następnie użyćwindow.scrollTo
Alexander Goncharov.
1
+1 za to rozwiązanie. Pomogło mi to poprawnie przewinąć do elementu, gdy miałem kontener ustawiony względem siebie z górnym przesunięciem.
Liga
Każdy, kto przyjeżdża tutaj, używając HashLink z React Router, to działa dla mnie. W rekwizycie przewijania w HashLink, scroll={el => { const yCoordinate = el.getBoundingClientRect().top + window.pageYOffset; const yOffset = -80; window.scrollTo({ top: yCoordinate + yOffset, behavior: 'smooth' }); }}- gdzie przesunięcie jest o 10-15 pikseli większe niż rozmiar nagłówka, tylko dla lepszego odstępu
Rob B
86

Możesz to zrobić w dwóch krokach:

el.scrollIntoView(true);
window.scrollBy(0, -10); // Adjust scrolling with a negative value here
fred727
źródło
23
Jeśli scrollIntoView()przewijanie nie zakończyło się przed scrollBy()uruchomieniem, drugie przewijanie anuluje poprzednie. Przynajmniej na Chrome 71.
Dave Hughes
1
W takim przypadku użyj setTimeout, jak widać w odpowiedzi poniżej: stackoverflow.com/a/51935129/1856258 .. U mnie działa bez Timeout. Dzięki!
leole
czy istnieje sposób na bezpośrednie wstrzyknięcie korektora piksela do scrollIntoView, aby aplikacja została przeniesiona bezpośrednio w odpowiednie miejsce? Jak scrollIntoView({adjustment:y})myślę, że powinno być możliwe z niestandardowego kodu na końcu
Webwoman
80

Pozycja kotwicy metodą bezwzględną

Innym sposobem, aby to zrobić, jest umieszczenie kotwic dokładnie tam, gdzie chcesz, zamiast polegać na przewijaniu według przesunięcia. Uważam, że pozwala to na lepszą kontrolę dla każdego elementu (np. Jeśli chcesz mieć inne przesunięcie dla niektórych elementów), a także może być bardziej odporne na zmiany / różnice API przeglądarki.

<div id="title-element" style="position: relative;">
  <div id="anchor-name" style="position: absolute; top: -100px; left: 0"></div>
</div>

Teraz przesunięcie jest określone jako -100px względem elementu. Utwórz funkcję, aby utworzyć tę kotwicę do ponownego wykorzystania kodu lub jeśli używasz nowoczesnego frameworka JS, takiego jak React, zrób to, tworząc komponent, który renderuje twoją kotwicę i przekaż nazwę kotwicy i wyrównanie dla każdego elementu, co może lub może nie być tym samym.

Następnie po prostu użyj:

const element = document.getElementById('anchor-name')
element.scrollIntoView({ behavior: 'smooth', block: 'start' });

Do płynnego przewijania z przesunięciem 100px.

Steve Banton
źródło
12
Jest to zdecydowanie najlepszy sposób na płynną i prostą implementację.
catch22
2
Myślę, że najlepsza i najbardziej natywna metoda.
Даниил Пронин
1
To takie proste i sprytne. Wielu innych po prostu dodaje niepotrzebny JS.
RedSands
2
Jest to szczególnie przydatne, gdy łącze typu „początek strony” znajduje się poniżej paska nawigacji i chcesz wyświetlić nawigację, ale nie chcesz, aby inne linki były przesunięte
Joe Moon
możesz osiągnąć to samo używając ujemnego marginesu górnego, unikasz zawijania elementu razem z innym z pozycją względną
Sergi
17

Rozwiązałem ten problem używając,

element.scrollIntoView({ behavior: 'smooth', block: 'center' });

To sprawia, że ​​element pojawia się centerpo przewinięciu, więc nie muszę obliczać yOffset.

Mam nadzieję, że to pomoże...

Imtiaz Shakil Siddique
źródło
1
Powinieneś używać scrollTonie dalej, elementale dalejwindow
Arseniy-II
@ Arseniy-II Dzięki za zwrócenie uwagi !!. Brakowało mi tej części!
Imtiaz Shakil Siddique
czy blok pomaga na górę?
Ashok kumar Ganesan
12

To działa dla mnie w Chrome (z płynnym przewijaniem i bez hacków czasowych)

Po prostu przesuwa element, inicjuje przewijanie, a następnie przesuwa go z powrotem.

Nie ma widocznego „wyskakiwania”, jeśli element jest już na ekranie.

pos = targetEle.style.position;
top = targetEle.style.top;
targetEle.style.position = 'relative';
targetEle.style.top = '-20px';
targetEle.scrollIntoView({behavior: 'smooth', block: 'start'});
targetEle.style.top = top;
targetEle.style.position = pos;
Ryan How
źródło
To najlepsze rozwiązanie ... Chciałbym, żeby zostało zaznaczone jako odpowiedź. Oszczędziłoby to kilka godzin.
Shivam
7

Opierając się na wcześniejszej odpowiedzi, robię to w projekcie Angular5.

Zaczął od:

// el.scrollIntoView(true);
el.scrollIntoView({
   behavior: 'smooth',
   block: 'start'
});
window.scrollBy(0, -10); 

Ale to powodowało pewne problemy i wymagało ustawienia Timeout dla scrollBy () w następujący sposób:

//window.scrollBy(0,-10);
setTimeout(() => {
  window.scrollBy(0,-10)
  }, 500);

I działa doskonale w MSIE11 i Chrome 68+. Nie testowałem w FF. 500 ms to najkrótsze opóźnienie, na jakie chciałbym się zdecydować. Zejście niżej czasami kończyło się niepowodzeniem, ponieważ płynne przewijanie nie zostało jeszcze zakończone. Dostosuj zgodnie z wymaganiami dla własnego projektu.

+1 do Fred727 za to proste, ale skuteczne rozwiązanie.

Podstawowa W.
źródło
6
Płynne przewijanie do elementu, a następnie przewijanie w górę nieco później, wydaje się nieco dziwne.
catch22
6

Zakładając, że chcesz przewinąć do elementów div, które są na tym samym poziomie w DOM i mają nazwę klasy „scroll-with-offset”, ten CSS rozwiąże problem:

.scroll-with-offset {    
  padding-top: 100px;
  margin-bottom: -100px;
}

Przesunięcie od góry strony wynosi 100 pikseli. Będzie działać zgodnie z przeznaczeniem tylko z blokiem: 'start':

element.scrollIntoView({ behavior: 'smooth', block: 'start' });

Dzieje się tak, że górny punkt elementów div znajduje się w normalnym miejscu, ale ich wewnętrzna zawartość zaczyna się 100 pikseli poniżej normalnej lokalizacji. Do tego służy padding-top: 100px. margin-bottom: -100px służy do wyrównania dodatkowego marginesu poniższego elementu div. Aby rozwiązanie było kompletne, dodaj również ten kod CSS, aby przesunąć marginesy / wypełnienia dla najwyższych i najniższych elementów div:

.top-div {
  padding-top: 0;
}
.bottom-div {
  margin-bottom: 0;
}
Georgy Petukhov
źródło
6

To rozwiązanie należy do @ Arseniy-II , właśnie uprościłem je do funkcji.

function _scrollTo(selector, yOffset = 0){
  const el = document.querySelector(selector);
  const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;

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

Użycie (możesz otworzyć konsolę tutaj w StackOverflow i przetestować ją):

_scrollTo('#question-header', 0);
Diego Fortes
źródło
4

Możesz także skorzystać z element.scrollIntoView() opcji

el.scrollIntoView(
  { 
    behavior: 'smooth', 
    block: 'start' 
  },
);

który większość przeglądarek wsparcie

Nicholas Murray
źródło
34
Jednak to nie obsługuje żadnej opcji przesunięcia.
adriendenat
3

Mam to i u mnie działa znakomicie:

// add a smooth scroll to element
scroll(el) {
el.scrollIntoView({
  behavior: 'smooth',
  block: 'start'});

setTimeout(() => {
window.scrollBy(0, -40);
}, 500);}

Mam nadzieję, że to pomoże.

Fernando Brandao
źródło
2

Innym rozwiązaniem jest użycie „offsetTop”, na przykład:

var elementPosition = document.getElementById('id').offsetTop;

window.scrollTo({
  top: elementPosition - 10, //add your necessary value
  behavior: "smooth"  //Smooth transition to roll
});
Kaio Dutra
źródło
2

Być może jest to trochę niezgrabne, ale jak dotąd tak dobre. Pracuję w kątowym 9.

plik .ts

scroll(el: HTMLElement) {
  el.scrollIntoView({ block: 'start',  behavior: 'smooth' });   
}

plik .html

<button (click)="scroll(target)"></button>
<div  #target style="margin-top:-50px;padding-top: 50px;" ></div>

Ustawiam przesunięcie za pomocą marginesu i wyściółki u góry.

Saludos!

Ignacio Basti
źródło
1

Znalazłem rozwiązanie tymczasowe. Załóżmy, że chcesz przewinąć do elementu div, na przykład elementu tutaj, i chcesz mieć nad nim odstęp 20 pikseli. Ustaw odniesienie do utworzonego div powyżej:

<div ref={yourRef} style={{position: 'relative', bottom: 20}}/> <Element />

Spowoduje to utworzenie żądanych odstępów.

Jeśli masz nagłówek, utwórz pusty element div również za nagłówkiem, przypisz mu wysokość równą wysokości nagłówka i odnieś się do niego.

Ibrahim Al Masri
źródło
0

Moim głównym pomysłem jest utworzenie tempDiv nad widokiem, do którego chcemy przewinąć. Działa dobrze bez opóźnień w moim projekcie.

scrollToView = (element, offset) => {
    var rect = element.getBoundingClientRect();
    var targetY = rect.y + window.scrollY - offset;

    var tempDiv;
    tempDiv = document.getElementById("tempDiv");
    if (tempDiv) {
        tempDiv.style.top = targetY + "px";
    } else {
        tempDiv = document.createElement('div');
        tempDiv.id = "tempDiv";
        tempDiv.style.background = "#F00";
        tempDiv.style.width = "10px";
        tempDiv.style.height = "10px";
        tempDiv.style.position = "absolute";
        tempDiv.style.top = targetY + "px";
        document.body.appendChild(tempDiv);
    }

    tempDiv.scrollIntoView({ behavior: 'smooth', block: 'start' });
}

Przykład użycia

onContactUsClick = () => {
    this.scrollToView(document.getElementById("contact-us"), 48);
}

Mam nadzieję, że to pomoże

Phan Van Linh
źródło
0

Opierając się na odpowiedzi Arseniy-II: miałem przypadek użycia, w którym przewijana jednostka nie była samym oknem, ale wewnętrznym szablonem (w tym przypadku div). W tym scenariuszu musimy ustawić identyfikator kontenera przewijanego i przekazać go getElementByIddo korzystania z funkcji przewijania:

<div class="scroll-container" id="app-content">
  ...
</div>
const yOffsetForScroll = -100
const y = document.getElementById(this.idToScroll).getBoundingClientRect().top;
const main = document.getElementById('app-content');
main.scrollTo({
    top: y + main.scrollTop + yOffsetForScroll,
    behavior: 'smooth'
  });

Zostawiając go tutaj na wypadek, gdyby ktoś znalazł się w podobnej sytuacji!

kounex
źródło
0

Oto moje 2 centy.

Miałem również problem z przewijaniem scrollIntoView nieco poza element, więc utworzyłem skrypt (natywny javascript), który dołącza element do miejsca docelowego, umieścił go nieco na górze za pomocą css i przewinął do tego. Po przewinięciu ponownie usuwam utworzone elementy.

HTML:

//anchor tag that appears multiple times on the page
<a href="#" class="anchors__link js-anchor" data-target="schedule">
    <div class="anchors__text">
        Scroll to the schedule
    </div>
</a>

//The node we want to scroll to, somewhere on the page
<div id="schedule">
    //html
</div>

Plik Javascript:

(() => {
    'use strict';

    const anchors = document.querySelectorAll('.js-anchor');

    //if there are no anchors found, don't run the script
    if (!anchors || anchors.length <= 0) return;

    anchors.forEach(anchor => {
        //get the target from the data attribute
        const target = anchor.dataset.target;

        //search for the destination element to scroll to
        const destination = document.querySelector(`#${target}`);
        //if the destination element does not exist, don't run the rest of the code
        if (!destination) return;

        anchor.addEventListener('click', (e) => {
            e.preventDefault();
            //create a new element and add the `anchors__generated` class to it
            const generatedAnchor = document.createElement('div');
            generatedAnchor.classList.add('anchors__generated');

            //get the first child of the destination element, insert the generated element before it. (so the scrollIntoView function scrolls to the top of the element instead of the bottom)
            const firstChild = destination.firstChild;
            destination.insertBefore(generatedAnchor, firstChild);

            //finally fire the scrollIntoView function and make it animate "smoothly"
            generatedAnchor.scrollIntoView({
                behavior: "smooth",
                block: "start",
                inline: "start"
            });

            //remove the generated element after 1ms. We need the timeout so the scrollIntoView function has something to scroll to.
            setTimeout(() => {
                destination.removeChild(generatedAnchor);
            }, 1);
        })
    })
})();

CSS:

.anchors__generated {
    position: relative;
    top: -100px;
}

Mam nadzieję, że to pomoże każdemu!

rubeus
źródło
0

Dodaję te wskazówki css dla tych, którzy nie rozwiązali tego problemu powyższymi rozwiązaniami:

#myDiv::before {
  display: block;
  content: " ";
  margin-top: -90px; // adjust this with your header height
  height: 90px; // adjust this with your header height
  visibility: hidden;
}
Jérémy MARQUER
źródło
0

UPD: Utworzyłem pakiet npm, który działa lepiej niż poniższe rozwiązanie i jest łatwiejszy w użyciu.

Moja funkcja smoothScroll

Wziąłem cudowne rozwiązanie Steve'a Bantona i napisałem funkcję, która sprawia, że ​​jest wygodniejszy w użyciu. Byłoby łatwiejsze w użyciu, window.scroll()a nawet window.scrollBy(), jak próbowałem wcześniej, ale te dwa mają pewne problemy:

  • Wszystko staje się niezdarne po ich użyciu z płynnym zachowaniem.
  • W każdym razie nie możesz im zapobiec i musisz czekać do końca zwoju. Mam więc nadzieję, że moja funkcja będzie dla Ciebie przydatna. Jest też lekki polyfill, który sprawia, że ​​działa w Safari, a nawet IE.

Oto kod

Po prostu skopiuj i zepsuć, jak chcesz.

import smoothscroll from 'smoothscroll-polyfill';

smoothscroll.polyfill();

const prepareSmoothScroll = linkEl => {
  const EXTRA_OFFSET = 0;

  const destinationEl = document.getElementById(linkEl.dataset.smoothScrollTo);
  const blockOption = linkEl.dataset.smoothScrollBlock || 'start';

  if ((blockOption === 'start' || blockOption === 'end') && EXTRA_OFFSET) {
    const anchorEl = document.createElement('div');

    destinationEl.setAttribute('style', 'position: relative;');
    anchorEl.setAttribute('style', `position: absolute; top: -${EXTRA_OFFSET}px; left: 0;`);

    destinationEl.appendChild(anchorEl);

    linkEl.addEventListener('click', () => {
      anchorEl.scrollIntoView({
        block: blockOption,
        behavior: 'smooth',
      });
    });
  }

  if (blockOption === 'center' || !EXTRA_OFFSET) {
    linkEl.addEventListener('click', () => {
      destinationEl.scrollIntoView({
        block: blockOption,
        behavior: 'smooth',
      });
    });
  }
};

export const activateSmoothScroll = () => {
  const linkEls = [...document.querySelectorAll('[data-smooth-scroll-to]')];

  linkEls.forEach(linkEl => prepareSmoothScroll(linkEl));
};

Aby utworzyć element odsyłacza, wystarczy dodać następujący atrybut danych:

data-smooth-scroll-to="element-id"

Możesz także ustawić inny atrybut jako dodatek

data-smooth-scroll-block="center"

Reprezentuje blockopcję scrollIntoView()funkcji. Domyślnie jest to start. Przeczytaj więcej na MDN .

Wreszcie

Dostosuj funkcję smoothScroll do swoich potrzeb.

Na przykład, jeśli masz jakiś stały nagłówek (lub nazywam to słowem masthead), możesz zrobić coś takiego:

const mastheadEl = document.querySelector(someMastheadSelector);

// and add it's height to the EXTRA_OFFSET variable

const EXTRA_OFFSET = mastheadEl.offsetHeight - 3;

Jeśli nie masz takiego przypadku, po prostu go usuń, dlaczego nie :-D.

Dmitrij Zacharow
źródło
-1

Proste rozwiązanie do przewijania określonego elementu w dół

const element = document.getElementById("element-with-scroll");
element.scrollTop = element.scrollHeight - 10;
Huri
źródło
-1

W przypadku elementu w wierszu tabeli użyj JQuery, aby uzyskać wiersz powyżej tego, który chcesz , i po prostu przewiń do tego wiersza.

Załóżmy, że mam wiele wierszy w tabeli, z których część powinna zostać sprawdzona przez administratora. Każdy wiersz wymagający recenzji ma zarówno strzałkę w górę, jak i w dół, która prowadzi do poprzedniego lub następnego elementu do przeglądu.

Oto kompletny przykład, który powinien zostać uruchomiony, jeśli utworzysz nowy dokument HTML w notatniku i zapiszesz go. Dostępny jest dodatkowy kod do wykrywania górnej i dolnej części naszych produktów do przeglądu, aby nie zgłaszać żadnych błędów.

<html>
<head>
    <title>Scrolling Into View</title>
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <style>
        div.scroll { height: 6em; width: 20em; overflow: auto; }
        thead th   { position: sticky; top: -1px; background: #fff; }
        .up, .down { cursor: pointer; }
        .up:hover, .down:hover { color: blue; text-decoration:underline; }
    </style>
</head>
<body>
<div class='scroll'>
<table border='1'>
    <thead>
        <tr>
            <th>Review</th>
            <th>Data</th>
        </tr>
    </thead>
    <tbody>
        <tr id='row_1'>
            <th></th>
            <td>Row 1 (OK)</td>
        </tr>
        <tr id='row_2'>
            <th></th>
            <td>Row 2 (OK)</td>
        </tr>
        <tr id='row_3'>
            <th id='jump_1'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 3 (REVIEW)</td>
        </tr>
        <tr id='row_4'>
            <th></th>
            <td>Row 4 (OK)</td>
        </tr>
        <tr id='row_5'>
            <th id='jump_2'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 5 (REVIEW)</td>
        </tr>
        <tr id='row_6'>
            <th></th>
            <td>Row 6 (OK)</td>
        </tr>
        <tr id='row_7'>
            <th></th>
            <td>Row 7 (OK)</td>
        </tr>
        <tr id='row_8'>
            <th id='jump_3'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 8 (REVIEW)</td>
        </tr>
        <tr id='row_9'>
            <th></th>
            <td>Row 9 (OK)</td>
        </tr>
        <tr id='row_10'>
            <th></th>
            <td>Row 10 (OK)</td>
        </tr>
    </tbody>
</table>
</div>
<script>
$(document).ready( function() {
    $('.up').on('click', function() {
        var id = parseInt($(this).parent().attr('id').split('_')[1]);
        if (id>1) {
            var row_id = $('#jump_' + (id - 1)).parent().attr('id').split('_')[1];
            document.getElementById('row_' + (row_id-1)).scrollIntoView({behavior: 'smooth', block: 'start'});
        } else {
            alert('At first');
        }
    });

    $('.down').on('click', function() {
        var id = parseInt($(this).parent().attr('id').split('_')[1]);
        if ($('#jump_' + (id + 1)).length) {
            var row_id = $('#jump_' + (id + 1)).parent().attr('id').split('_')[1];
            document.getElementById('row_' + (row_id-1)).scrollIntoView({behavior: 'smooth', block: 'start'});
        } else {
            alert('At last');
        }
    });
});
</script>
</body>
</html>
Martin Francis
źródło