scrollIntoView Scorre troppo lontano


107

Ho una pagina in cui una barra di scorrimento contenente righe di tabella con div in esse viene generata dinamicamente dal database. Ogni riga della tabella si comporta come un collegamento, un po 'come vedresti su una playlist di YouTube accanto al lettore video.

Quando un utente visita la pagina, l'opzione su cui si trova dovrebbe andare all'inizio del div di scorrimento. Questa funzionalità funziona. Il problema è che va un po 'troppo lontano. Come se l'opzione su cui si trovano fosse circa 10px troppo alta. Quindi, la pagina viene visitata, l'URL viene utilizzato per identificare quale opzione è stata selezionata e quindi scorre tale opzione fino all'inizio del div di scorrimento. Nota: questa non è la barra di scorrimento per la finestra, è un div con una barra di scorrimento.

Sto usando questo codice per spostare l'opzione selezionata all'inizio del div:

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

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

el.scrollIntoView(true);

Lo sposta nella parte superiore del div ma di circa 10 pixel troppo in alto. Qualcuno sa come ripararlo?


44
La mancanza di configurazione offset per scrollIntoViewè preoccupante.
mystrdat

Risposte:


57

Se è di circa 10 px, immagino che potresti semplicemente regolare manualmente l' divoffset di scorrimento del contenitore in questo modo:

el.scrollIntoView(true);
document.getElementById("containingDiv").scrollTop -= 10;

1
Avrà lo stesso effetto animato di scrollIntoView?
1252748

1
@thomas AFAIK, la semplice scrollIntoViewfunzione DOM non causa l'animazione. Stai parlando del plugin scrollIntoView jQuery?
Lucas Trzesniewski

1
Quell'uomo ha funzionato, tranne che era la direzione sbagliata, quindi tutto quello che dovevo fare era cambiarlo da + = 10 a - = 10 e ora si sta caricando correttamente, grazie mille per l'aiuto !!!!
Matthew Wilson

Sì. Ero confuso. Ho pensato che fosse animato. Scusa!
1252748

17
Può animare, basta aggiungere el.scrollIntoView({ behavior: 'smooth' });. Il supporto non è eccezionale però!
daveaspinall

108

Scorri senza problemi fino alla posizione corretta

Ottieni coordinate corrette y e usawindow.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'});

2
Questa è la risposta più corretta - possiamo facilmente trovare la posizione di scorrimento con jQuery('#el').offset().tope quindi utilizzarlawindow.scrollTo
Alexander Goncharov

1
+1 per questa soluzione. Mi ha aiutato a scorrere correttamente fino all'elemento quando avevo un contenitore posizionato rispetto con un offset superiore.
Liga

Chiunque venga qui utilizzando HashLink con React Router, questo è ciò che ha funzionato per me. Nell'elica di scorrimento sul tuo HashLink, scroll={el => { const yCoordinate = el.getBoundingClientRect().top + window.pageYOffset; const yOffset = -80; window.scrollTo({ top: yCoordinate + yOffset, behavior: 'smooth' }); }}- dove l'offset è di 10-15 pixel in più rispetto alla dimensione dell'intestazione solo per una migliore spaziatura
Rob B

86

Puoi farlo in due passaggi:

el.scrollIntoView(true);
window.scrollBy(0, -10); // Adjust scrolling with a negative value here

23
Se scrollIntoView()non ha terminato lo scorrimento prima che venga scrollBy()eseguito, quest'ultimo annullerà il primo. Almeno su Chrome 71.
Dave Hughes

1
In questo caso usa setTimeout come mostrato in una risposta di seguito: stackoverflow.com/a/51935129/1856258 .. Per me funziona senza Timeout. Grazie!
leole

c'è un modo per iniettare direttamente il correttore di un pixel in scrollIntoView in modo da far spostare l'applicazione direttamente nella posizione pertinente? Ad esempio scrollIntoView({adjustment:y}), penso che dovrebbe essere possibile con un codice personalizzato alla fine
Webwoman

78

Posizione ancoraggio con metodo assoluto

Un altro modo per farlo è posizionare le ancore esattamente dove vuoi sulla pagina piuttosto che fare affidamento sullo scorrimento per offset. Trovo che consenta un controllo migliore per ogni elemento (ad es. Se si desidera un offset diverso per determinati elementi) e potrebbe anche essere più resistente alle modifiche / differenze dell'API del browser.

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

Ora l'offset è specificato come -100px rispetto all'elemento. Crea una funzione per creare questo ancoraggio per il riutilizzo del codice, o se stai usando un moderno framework JS come React fallo creando un componente che renda il tuo ancoraggio e passa il nome dell'ancora e l'allineamento per ogni elemento, che può o può non essere lo stesso.

Quindi usa:

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

Per uno scorrimento fluido con un offset di 100 px.


12
Questo è di gran lunga il modo migliore per implementarlo in modo fluido e semplice.
catch22

2
Il metodo migliore e più nativo credo.
Даниил Пронин

1
Questo è così semplice e fluido. Molti degli altri aggiungono semplicemente JS non necessari.
RedSands

2
Ciò è particolarmente utile quando un collegamento di tipo "inizio pagina" si trova sotto una barra di navigazione e si desidera visualizzare la navigazione, ma non si desidera che altri collegamenti vengano spostati
Joe Moon,

puoi ottenere lo stesso utilizzando il margine superiore negativo, eviti di avvolgere l'elemento insieme a un altro con posizione relativa
Sergi

15

Ho risolto questo problema utilizzando,

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

Questo fa apparire l'elemento centerdopo lo scorrimento, quindi non devo calcolare yOffset.

Spero che sia d'aiuto...


1
Dovresti usare scrollTonon acceso elementma accesowindow
Arseniy-II

@ Arseniy-II Grazie per averlo fatto notare !!. Mi sono persa quella parte!
Imtiaz Shakil Siddique

il blocco aiuta per la parte superiore?
Ashok kumar Ganesan,

11

Questo funziona per me in Chrome (con scorrimento fluido e senza hack di temporizzazione)

Sposta semplicemente l'elemento, avvia lo scorrimento, quindi lo sposta indietro.

Non c'è alcun "popping" visibile se l'elemento è già sullo schermo.

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;

Questa è la soluzione migliore ... Vorrei che fosse contrassegnata come risposta. Avrei risparmiato un paio d'ore.
Shivam

7

Basandosi su una risposta precedente, lo sto facendo in un progetto Angular5.

Iniziato con:

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

Ma questo ha dato alcuni problemi e aveva bisogno di setTimeout per scrollBy () in questo modo:

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

E funziona perfettamente con MSIE11 e Chrome 68+. Non ho provato in FF. 500 ms è stato il ritardo più breve che mi sarei avventurato. Andare più in basso a volte falliva perché lo scorrimento regolare non era ancora completato. Regola come richiesto per il tuo progetto.

+1 a Fred727 per questa semplice ma efficace soluzione.


6
Questo sembra un po 'strano per regolare lo scorrimento di un elemento e quindi scorrere un po' dopo.
catch22

6

Supponendo che tu voglia scorrere fino ai div che sono tutti allo stesso livello in DOM e hanno il nome della classe "scroll-with-offset", questo CSS risolverà il problema:

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

L'offset dalla parte superiore della pagina è di 100 px. Funzionerà solo come previsto con il blocco: 'start':

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

Quello che sta succedendo è che il punto più alto dei div è nella posizione normale ma il loro contenuto interno inizia 100px sotto la posizione normale. Ecco a cosa serve il padding-top: 100px. margin-bottom: -100px serve per compensare il margine extra del div sottostante. Per completare la soluzione aggiungi anche questo CSS per compensare i margini / paddings per i div più in alto e più in basso:

.top-div {
  padding-top: 0;
}
.bottom-div {
  margin-bottom: 0;
}

6

Questa soluzione appartiene a @ Arseniy-II , l'ho appena semplificata in una funzione.

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

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

Utilizzo (puoi aprire la console proprio qui in StackOverflow e provarlo):

_scrollTo('#question-header', 0);


3

Ho questo e funziona perfettamente per me:

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

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

Spero che sia d'aiuto.


2

Un'altra soluzione è usare "offsetTop", in questo modo:

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

window.scrollTo({
  top: elementPosition - 10, //add your necessary value
  behavior: "smooth"  //Smooth transition to roll
});

2

Quindi, forse questo è un po 'goffo ma finora tutto bene. Sto lavorando in angolare 9.

file .ts

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

file .html

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

Regolo l'offset con margine e imbottitura in alto.

Saludos!


1

Ho trovato una soluzione alternativa. Supponiamo che tu voglia scorrere fino a un div, ad esempio Element qui, e desideri avere una spaziatura di 20px sopra di esso. Imposta il riferimento su un div creato sopra di esso:

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

In questo modo creerai questa spaziatura che desideri.

Se hai un'intestazione, crea un div vuoto anche dietro l'intestazione e assegnagli un'altezza uguale all'altezza dell'intestazione e fai riferimento ad essa.


0

La mia idea principale è creare un tempDiv sopra la vista a cui vogliamo scorrere. Funziona bene senza ritardi nel mio progetto.

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

Esempio utilizzando

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

Spero che aiuti


0

Basato sulla risposta di Arseniy-II: avevo il caso d'uso in cui l'entità di scorrimento non era la finestra stessa ma un modello interno (in questo caso un div). In questo scenario dobbiamo impostare un ID per il contenitore a scorrimento e ottenerlo tramite getElementByIdper utilizzare la sua funzione di scorrimento:

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

Lasciandolo qui nel caso qualcuno debba affrontare una situazione simile!


0

Ecco i miei 2 centesimi.

Ho anche avuto il problema dello scrollIntoView che scorreva un po 'oltre l'elemento, quindi ho creato uno script (javascript nativo) che antepone un elemento alla destinazione, lo ho posizionato un po' in alto con css e ho fatto scorrere quello. Dopo lo scorrimento, rimuovo di nuovo gli elementi creati.

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>

File 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;
}

Spero che questo aiuti chiunque!


0

Aggiungo questi suggerimenti CSS per coloro che non hanno risolto questo problema con le soluzioni di cui sopra:

#myDiv::before {
  display: block;
  content: " ";
  margin-top: -90px; // adjust this with your header height
  height: 90px; // adjust this with your header height
  visibility: hidden;
}

0

UPD: Ho creato un pacchetto npm che funziona meglio della seguente soluzione e più facile da usare.

La mia funzione smoothScroll

Ho preso la meravigliosa soluzione di Steve Banton e ho scritto una funzione che lo rende più comodo da usare. Sarebbe più semplice da usare window.scroll()o addirittura window.scrollBy(), come ho provato prima, ma questi due hanno alcuni problemi:

  • Tutto diventa scadente dopo averli usati con un comportamento regolare.
  • Non puoi impedirli comunque e devi aspettare fino alla fine del rotolo. Quindi spero che la mia funzione ti sia utile. Inoltre, c'è un polyfill leggero che lo fa funzionare in Safari e persino in IE.

Ecco il codice

Basta copiarlo e rovinarlo come vuoi.

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

Per creare un elemento di collegamento è sufficiente aggiungere il seguente attributo di dati:

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

Inoltre puoi impostare un altro attributo come aggiunta

data-smooth-scroll-block="center"

Rappresenta l' blockopzione della scrollIntoView()funzione. Per impostazione predefinita, è start. Maggiori informazioni su MDN .

Finalmente

Adatta la funzione smoothScroll alle tue esigenze.

Ad esempio, se hai un'intestazione fissa (o la chiamo con la parola masthead) puoi fare qualcosa del genere:

const mastheadEl = document.querySelector(someMastheadSelector);

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

const EXTRA_OFFSET = mastheadEl.offsetHeight - 3;

Se non hai un caso del genere, eliminalo, perché no MrGreen.


0

Per un elemento in una riga della tabella, usa JQuery per ottenere la riga sopra quella desiderata e scorri semplicemente fino a quella riga.

Supponiamo di avere più righe in una tabella, alcune delle quali dovrebbero essere riviste da e admin. Ogni riga che richiede la revisione ha sia una freccia su che una freccia giù per portarti all'elemento precedente o successivo da esaminare.

Ecco un esempio completo che dovrebbe essere eseguito solo se crei un nuovo documento HTML nel blocco note e lo salvi. È disponibile un codice aggiuntivo per rilevare la parte superiore e inferiore dei nostri articoli per la revisione in modo da non generare errori.

<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>

-1

Soluzione semplice per scorrere verso il basso un elemento specifico

const element = document.getElementById("element-with-scroll");
element.scrollTop = element.scrollHeight - 10;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.