scrollIntoView Scrolls trop loin

108

J'ai une page où une barre de défilement contenant des lignes de table avec des divs est générée dynamiquement à partir de la base de données. Chaque ligne de tableau agit comme un lien, un peu comme vous le verriez sur une playlist YouTube à côté du lecteur vidéo.

Lorsqu'un utilisateur visite la page, l'option sur laquelle il se trouve est censée aller en haut de la division de défilement. Cette fonctionnalité fonctionne. Le problème est que cela va un peu trop loin. Comme l'option sur laquelle ils se trouvent est environ 10px trop élevée. Ainsi, la page est visitée, l'url est utilisée pour identifier quelle option a été sélectionnée, puis fait défiler cette option vers le haut du div de défilement. Remarque: ce n'est pas la barre de défilement de la fenêtre, c'est un div avec une barre de défilement.

J'utilise ce code pour le faire déplacer l'option sélectionnée vers le haut du div:

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

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

el.scrollIntoView(true);

Il le déplace vers le haut du div mais environ 10 pixels trop haut. Quelqu'un sait comment résoudre ce problème?

Matthew Wilson
la source
45
Le manque de configuration de décalage pour scrollIntoViewest troublant.
mystrdat

Réponses:

57

Si c'est environ 10px, alors je suppose que vous pouvez simplement ajuster manuellement le divdécalage de défilement du contenant comme ça:

el.scrollIntoView(true);
document.getElementById("containingDiv").scrollTop -= 10;
Lucas Trzesniewski
la source
1
Cela aura-t-il le même effet animé que scrollIntoView?
1252748
1
@thomas AFAIK, la scrollIntoViewfonction DOM simple ne provoque pas d'animation. Parlez-vous du plugin jQuery scrollIntoView?
Lucas Trzesniewski
1
Cet homme a fonctionné, sauf que c'était la mauvaise direction, donc tout ce que j'avais à faire était de le changer de + = 10 à - = 10 et maintenant il se charge parfaitement, merci beaucoup pour l'aide !!!!
Matthew Wilson
Oui. J'étais confus. Je pensais que c'était animé. Désolé!
1252748
17
Il peut animer, il suffit d'ajouter el.scrollIntoView({ behavior: 'smooth' });. Mais le support n'est pas génial!
daveaspinall
109

Faites défiler en douceur jusqu'à une position appropriée

Obtenez les bonnes y coordonnées et utilisezwindow.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
la source
2
C'est la réponse la plus correcte - nous pouvons facilement trouver la position du rouleau avec jQuery('#el').offset().toppuis l'utiliserwindow.scrollTo
Alexander Goncharov
1
+1 pour cette solution. Cela m'a aidé à faire défiler correctement jusqu'à l'élément lorsque j'avais un conteneur positionné par rapport avec un décalage supérieur.
Liga
Quiconque vient ici en utilisant HashLink avec React Router, c'est ce qui a fonctionné pour moi. Dans l'accessoire de défilement sur votre HashLink, scroll={el => { const yCoordinate = el.getBoundingClientRect().top + window.pageYOffset; const yOffset = -80; window.scrollTo({ top: yCoordinate + yOffset, behavior: 'smooth' }); }}- où le décalage est de 10 à 15 pixels de plus que la taille de votre en-tête juste pour un meilleur espacement
Rob B
86

Vous pouvez le faire en deux étapes:

el.scrollIntoView(true);
window.scrollBy(0, -10); // Adjust scrolling with a negative value here
fred727
la source
23
Si le scrollIntoView()défilement n'a pas fini avant l' scrollBy()exécution, le dernier défilement annulera le premier. Au moins sur Chrome 71.
Dave Hughes
1
Dans ce cas, utilisez setTimeout comme indiqué dans une réponse ci-dessous: stackoverflow.com/a/51935129/1856258 .. Pour moi, cela fonctionne sans Timeout. Merci!
leole
existe-t-il un moyen d'injecter directement le correcteur d'un pixel dans scrollIntoView afin de faire bouger l'application directement à l'endroit approprié? Comme scrollIntoView({adjustment:y}), je pense que cela devrait être possible avec un code personnalisé à la fin
Webwoman
79

Positionner l'ancre par méthode absolue

Une autre façon de le faire est de positionner vos ancres exactement où vous le souhaitez sur la page plutôt que de compter sur le défilement par décalage. Je trouve que cela permet un meilleur contrôle pour chaque élément (par exemple, si vous voulez un décalage différent pour certains éléments), et peut également être plus résistant aux changements / différences d'API du navigateur.

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

Maintenant, le décalage est spécifié comme -100px par rapport à l'élément. Créez une fonction pour créer cette ancre pour la réutilisation du code, ou si vous utilisez un framework JS moderne tel que React, faites-le en créant un composant qui rend votre ancre, et passez le nom de l'ancre et l'alignement pour chaque élément, ce qui peut ou peut pas être le même.

Ensuite, utilisez simplement:

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

Pour un défilement fluide avec un décalage de 100 px.

Steve Banton
la source
12
C'est de loin le meilleur moyen de mettre en œuvre cela en douceur et simplement.
catch22
2
La meilleure méthode et la plus native que je pense.
Даниил Пронин
1
C'est tellement simple et élégant. Beaucoup d'autres ajoutent simplement des JS inutiles.
RedSands
2
Ceci est particulièrement utile lorsqu'un lien de type "haut de page" se trouve sous une barre de navigation et que vous souhaitez afficher la navigation, mais que vous ne souhaitez pas que d'autres liens soient décalés
Joe Moon
vous pouvez obtenir la même chose en utilisant une marge négative en haut, vous évitez d'envelopper l'élément avec un autre avec une position relative
Sergi
16

J'ai résolu ce problème en utilisant,

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

Cela fait apparaître l'élément dans le centerdéfilement après, donc je n'ai pas à calculer yOffset.

J'espère que ça aide...

Imtiaz Shakil Siddique
la source
1
Vous devriez utiliser scrollTopas sur elementmais surwindow
Arseniy-II
@ Arseniy-II Merci de l'avoir signalé !!. J'ai raté cette partie!
Imtiaz Shakil Siddique
le bloc aide-t-il pour le haut?
Ashok kumar Ganesan le
11

Cela fonctionne pour moi dans Chrome (avec un défilement fluide et pas de hacks de synchronisation)

Il déplace simplement l'élément, lance le défilement, puis le recule.

Il n'y a pas de "popping" visible si l'élément est déjà sur l'écran.

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 Comment
la source
C'est la meilleure solution ... J'aimerais qu'elle soit marquée comme réponse. Aurait sauvé quelques heures.
Shivam
7

En me basant sur une réponse précédente, je fais cela dans un projet Angular5.

Commencé avec:

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

Mais cela a posé quelques problèmes et a nécessité de définirTimeout pour scrollBy () comme ceci:

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

Et cela fonctionne parfaitement dans MSIE11 et Chrome 68+. Je n'ai pas testé en FF. 500 ms était le délai le plus court que j'aurais osé. La descente échouait parfois car le défilement fluide n'était pas encore terminé. Ajustez au besoin pour votre propre projet.

+1 à Fred727 pour cette solution simple mais efficace.

PrimaireW
la source
6
Cela semble un peu bizarre de faire défiler en douceur jusqu'à un élément, puis de faire défiler un peu plus haut.
catch22
6

En supposant que vous vouliez faire défiler jusqu'aux divs qui sont tous au même niveau dans DOM et qui ont le nom de classe "scroll-with-offset", alors ce CSS résoudra le problème:

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

Le décalage par rapport au haut de la page est de 100 pixels. Cela ne fonctionnera que comme prévu avec block: 'start':

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

Ce qui se passe, c'est que le point supérieur des divs est à l'emplacement normal mais que leur contenu interne commence 100px en dessous de l'emplacement normal. C'est à cela que sert padding-top: 100px. margin-bottom: -100px est de compenser la marge supplémentaire de la div ci-dessous. Pour rendre la solution complète, ajoutez également ce CSS pour compenser les marges / rembourrages pour les div les plus hauts et les plus bas:

.top-div {
  padding-top: 0;
}
.bottom-div {
  margin-bottom: 0;
}
Georgy Petukhov
la source
6

Cette solution appartient à @ Arseniy-II , je viens de la simplifier en une fonction.

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

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

Utilisation (vous pouvez ouvrir la console ici même dans StackOverflow et la tester):

_scrollTo('#question-header', 0);
Diego Fortes
la source
4

Vous pouvez également utiliser les element.scrollIntoView() options

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

que la plupart des navigateurs prennent en charge

Nicholas Murray
la source
33
Cependant, cela ne prend en charge aucune option de décalage.
adriendenat
3

J'ai ceci et cela fonctionne à merveille pour moi:

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

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

J'espère que ça aide.

Fernando Brandao
la source
2

Une autre solution consiste à utiliser "offsetTop", comme ceci:

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

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

Donc, c'est peut-être un peu maladroit mais jusqu'ici tout va bien. Im travaillant en angulaire 9.

fichier .ts

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

fichier .html

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

J'ajuste le décalage avec la marge et le haut de remplissage.

Saludos!

Ignacio Basti
la source
1

J'ai trouvé une solution de contournement. Dites que vous voulez faire défiler jusqu'à un div, Element ici par exemple, et que vous voulez avoir un espacement de 20px au-dessus. Définissez la référence sur un div créé au-dessus:

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

Cela créera cet espacement que vous souhaitez.

Si vous avez un en-tête, créez également un div vide derrière l'en-tête et attribuez-lui une hauteur égale à la hauteur de l'en-tête et référencez-le.

Ibrahim Al Masri
la source
0

Mon idée principale est de créer un tempDiv au-dessus de la vue vers laquelle nous voulons faire défiler. Cela fonctionne bien sans prendre de retard dans mon projet.

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

Exemple d'utilisation

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

J'espère que ça aide

Phan Van Linh
la source
0

Basé sur la réponse d'Arseniy-II: j'ai eu le cas d'utilisation où l'entité de défilement n'était pas la fenêtre elle-même mais un modèle interne (dans ce cas, un div). Dans ce scénario, nous devons définir un ID pour le conteneur de défilement et l'obtenir via getElementByIdpour utiliser sa fonction de défilement:

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

Le laisser ici au cas où quelqu'un serait confronté à une situation similaire!

Kounex
la source
0

Voici mes 2 cents.

J'ai également eu le problème du scrollIntoView défilant un peu au-delà de l'élément, j'ai donc créé un script (javascript natif) qui ajoute un élément à la destination, le positionne un peu en haut avec css et fait défiler jusqu'à celui-ci. Après le défilement, je supprime à nouveau les éléments créés.

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>

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

J'espère que cela aide n'importe qui!

rubeus
la source
0

J'ajoute ces conseils css pour ceux qui n'ont pas résolu ce problème avec les solutions ci-dessus:

#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
la source
0

UPD: j'ai créé un package npm qui fonctionne mieux que la solution suivante et plus facile à utiliser.

Ma fonction smoothScroll

J'ai pris la merveilleuse solution de Steve Banton et écrit une fonction qui la rend plus pratique à utiliser. Ce serait plus facile à utiliser window.scroll()ou même window.scrollBy(), comme je l'ai déjà essayé, mais ces deux-là ont quelques problèmes:

  • Tout devient junky après les avoir utilisés avec un comportement fluide.
  • Vous ne pouvez pas les empêcher de toute façon et devez attendre le et du rouleau. J'espère donc que ma fonction vous sera utile. En outre, il existe un polyfill léger qui le permet de fonctionner dans Safari et même IE.

Voici le code

Copiez-le et dérangez-le comme vous le souhaitez.

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

Pour créer un élément de lien, ajoutez simplement l'attribut de données suivant:

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

Vous pouvez également définir un autre attribut comme addtion

data-smooth-scroll-block="center"

Il représente l' blockoption de la scrollIntoView()fonction. Par défaut, c'eststart . En savoir plus sur MDN .

finalement

Adaptez la fonction smoothScroll à vos besoins.

Par exemple, si vous avez un en-tête fixe (ou je l'appelle avec le mot masthead), vous pouvez faire quelque chose comme ceci:

const mastheadEl = document.querySelector(someMastheadSelector);

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

const EXTRA_OFFSET = mastheadEl.offsetHeight - 3;

Si vous n'avez pas un tel cas, supprimez-le, pourquoi pas :-D.

Dmitry Zakharov
la source
0

Pour un élément dans une ligne de tableau, utilisez JQuery pour obtenir la ligne au-dessus de celle que vous voulez , et faites simplement défiler jusqu'à cette ligne à la place.

Supposons que j'ai plusieurs lignes dans une table, dont certaines devraient être examinées par et par l'administrateur. Chaque ligne nécessitant une révision comporte à la fois une flèche vers le haut et une flèche vers le bas pour vous diriger vers l'élément précédent ou suivant à réviser.

Voici un exemple complet qui devrait simplement s'exécuter si vous créez un nouveau document HTML dans le bloc-notes et que vous l'enregistrez. Il y a un code supplémentaire pour détecter le haut et le bas de nos éléments à examiner afin que nous ne renvoyions aucune erreur.

<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
la source
-1

Solution simple pour faire défiler un élément spécifique vers le bas

const element = document.getElementById("element-with-scroll");
element.scrollTop = element.scrollHeight - 10;
Huri
la source