scrollIntoView Se desplaza demasiado lejos

107

Tengo una página donde una barra de desplazamiento que contiene filas de tabla con divs en ellas se genera dinámicamente desde la base de datos. Cada fila de la tabla actúa como un enlace, algo así como lo vería en una lista de reproducción de YouTube junto al reproductor de video.

Cuando un usuario visita la página, se supone que la opción en la que se encuentra debe ir a la parte superior del div de desplazamiento. Esta funcionalidad está funcionando. El problema es que va demasiado lejos. Como si la opción en la que están es de aproximadamente 10 píxeles demasiado alta. Entonces, se visita la página, la URL se usa para identificar qué opción se seleccionó y luego se desplaza esa opción a la parte superior del div de desplazamiento. Nota: Esta no es la barra de desplazamiento de la ventana, es un div con una barra de desplazamiento.

Estoy usando este código para que mueva la opción seleccionada a la parte superior del div:

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

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

el.scrollIntoView(true);

Lo mueve a la parte superior del div pero unos 10 píxeles demasiado hacia arriba. ¿Alguien sabe cómo solucionarlo?

Matthew Wilson
fuente
44
La falta de configuración de compensación para scrollIntoViewes preocupante.
mystrdat

Respuestas:

57

Si es de aproximadamente 10px, entonces supongo que simplemente podría ajustar manualmente el divdesplazamiento de desplazamiento del contenedor de esa manera:

el.scrollIntoView(true);
document.getElementById("containingDiv").scrollTop -= 10;
Lucas Trzesniewski
fuente
1
¿Tendrá el mismo efecto animado que scrollIntoView?
1252748
1
@thomas AFAIK, la scrollIntoViewfunción DOM simple no genera animación. ¿Estás hablando del complemento jQuery scrollIntoView?
Lucas Trzesniewski
1
Ese hombre funcionó, excepto que era la dirección equivocada, así que todo lo que tuve que hacer fue cambiarlo de + = 10 a - = 10 y ahora se está cargando correctamente, ¡muchas gracias por la ayuda!
Matthew Wilson
Sí. Estaba confundido. Pensé que estaba animado. ¡Lo siento!
1252748
17
Puede animar, solo agregue el.scrollIntoView({ behavior: 'smooth' });. ¡Sin embargo, el soporte no es excelente!
daveaspinall
108

Desplácese suavemente a una posición adecuada

Obtenga las y coordenadas y el uso correctoswindow.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
fuente
2
Esta es la respuesta más correcta - podemos encontrar fácilmente la posición del pergamino con jQuery('#el').offset().topy luego usarwindow.scrollTo
Alexander Goncharov
1
+1 para esta solución. Me ayudó a desplazarme al elemento correctamente cuando tenía un contenedor posicionado en relación con un desplazamiento superior.
Liga
Cualquiera que venga aquí usando HashLink con React Router, esto es lo que funcionó para mí. En el accesorio de desplazamiento en su HashLink, scroll={el => { const yCoordinate = el.getBoundingClientRect().top + window.pageYOffset; const yOffset = -80; window.scrollTo({ top: yCoordinate + yOffset, behavior: 'smooth' }); }}donde el desplazamiento es de 10 a 15 píxeles más que el tamaño de su encabezado solo para un mejor espaciado
Rob B
86

Puedes hacerlo en dos pasos:

el.scrollIntoView(true);
window.scrollBy(0, -10); // Adjust scrolling with a negative value here
fred727
fuente
23
Si scrollIntoView()no ha terminado de desplazarse antes de scrollBy()ejecutarse, el último desplazamiento cancelará el primero. Al menos en Chrome 71.
Dave Hughes
1
En este caso, use setTimeout como se ve en una respuesta a continuación: stackoverflow.com/a/51935129/1856258 .. Para mí, funciona sin Timeout. ¡Gracias!
leole
¿Hay alguna manera de inyectar directamente un corrector de píxeles en scrollIntoView para que la aplicación se mueva directamente al lugar relevante? Me gusta scrollIntoView({adjustment:y}), creo que debería ser posible con un código personalizado al final
Webwoman
78

Posicionar el ancla por método absoluto

Otra forma de hacer esto es ubicar sus anclajes exactamente donde desea en la página en lugar de depender del desplazamiento por desplazamiento. Creo que permite un mejor control para cada elemento (por ejemplo, si desea un desplazamiento diferente para ciertos elementos), y también puede ser más resistente a los cambios / diferencias de la API del navegador.

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

Ahora el desplazamiento se especifica como -100px en relación con el elemento. Cree una función para crear este ancla para la reutilización de código, o si está utilizando un marco JS moderno como React, haga esto creando un componente que represente su ancla, y pase el nombre del ancla y la alineación para cada elemento, que puede o puede no ser el mismo.

Entonces solo usa:

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

Para un desplazamiento suave con un desplazamiento de 100 px.

Steve Banton
fuente
12
Esta es, con mucho, la mejor manera de implementar esto de manera sencilla y sin problemas.
catch22
2
Creo que es el método mejor y más nativo.
Даниил Пронин
1
Esto es tan simple y hábil. Muchos de los otros simplemente agregan JS innecesarios.
RedSands
2
Esto es especialmente útil cuando un vínculo del tipo "parte superior de la página" está debajo de una barra de navegación, y desea mostrar la navegación, pero no desea que ningún otro vínculo se desplace
Joe Moon
puede lograr lo mismo usando margen negativo superior, evita envolver el elemento junto con otro con posición relativa
Sergi
15

Resolví este problema usando,

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

Esto hace que el elemento aparezca en el centerdesplazamiento posterior, por lo que no tengo que calcular yOffset.

Espero eso ayude...

Imtiaz Shakil Siddique
fuente
1
No debe usarlo scrollToen elementsino enwindow
Arseniy-II
@ Arseniy-II ¡¡Gracias por señalarlo !!. ¡Me perdí esa parte!
Imtiaz Shakil Siddique
¿El bloque ayuda a la cima?
Ashok kumar Ganesan
11

Esto me funciona en Chrome (con un desplazamiento suave y sin cortes de tiempo)

Simplemente mueve el elemento, inicia el desplazamiento y luego lo mueve hacia atrás.

No hay "estallido" visible si el elemento ya está en la pantalla.

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 cómo
fuente
Esta es la mejor solución ... Ojalá estuviera marcada como respuesta. Habría ahorrado un par de horas.
Shivam
7

Sobre la base de una respuesta anterior, estoy haciendo esto en un proyecto Angular5.

Empezó con:

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

Pero esto dio algunos problemas y necesitaba establecer el tiempo de espera para scrollBy () de esta manera:

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

Y funciona perfectamente en MSIE11 y Chrome 68+. No he probado en FF. 500 ms fue el retraso más corto que me atrevería a aventurar. Bajar a veces fallaba porque el suave desplazamiento aún no se había completado. Realice los ajustes necesarios para su propio proyecto.

+1 a Fred727 para obtener esta solución simple pero efectiva.

PrimaryW
fuente
6
Esto se siente un poco extraño para suavizar el desplazamiento a un elemento y luego desplazarse hacia arriba un poco después.
catch22
6

Suponiendo que desea desplazarse a los divs que están todos en el mismo nivel en DOM y tienen el nombre de clase "scroll-with-offset", entonces este CSS resolverá el problema:

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

El desplazamiento desde la parte superior de la página es de 100 píxeles. Solo funcionará según lo previsto con block: 'start':

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

Lo que está sucediendo es que el punto superior de los divs está en la ubicación normal pero su contenido interno comienza 100px por debajo de la ubicación normal. Para eso está padding-top: 100px. margin-bottom: -100px es para compensar el margen adicional del div inferior. Para completar la solución, agregue también este CSS para compensar los márgenes / rellenos para los divs más altos e inferiores:

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

Esta solución pertenece a @ Arseniy-II , acabo de simplificarla en una función.

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

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

Uso (puede abrir la consola aquí mismo en StackOverflow y probarla):

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

También puedes usar las element.scrollIntoView() opciones

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

que admite la mayoría de los navegadores

Nicolás Murray
fuente
33
Sin embargo, esto no admite ninguna opción de compensación.
adriendenat
3

Tengo esto y funciona de maravilla para mí:

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

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

Espero eso ayude.

Fernando Brandao
fuente
2

Otra solución es usar "offsetTop", así:

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

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

Entonces, tal vez esto sea un poco torpe, pero hasta ahora todo va bien. Estoy trabajando en angular 9.

expediente .ts

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

expediente .html

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

Ajuste el desplazamiento con margen y parte superior de relleno.

¡Saludos!

Ignacio Basti
fuente
1

Encontré una solución alternativa. Digamos que desea desplazarse a un div, Element here, por ejemplo, y desea tener un espaciado de 20px por encima. Establezca la referencia en un div creado encima de él:

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

Si lo hace, creará este espaciado que desee.

Si tiene un encabezado, cree un div vacío también detrás del encabezado y asígnele una altura igual a la altura del encabezado y haga referencia a él.

Ibrahim Al Masri
fuente
0

Mi idea principal es crear un tempDiv encima de la vista a la que queremos desplazarnos. Funciona bien sin retrasos en mi proyecto.

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

Ejemplo usando

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

Espero que ayude

Phan Van Linh
fuente
0

Basado en la respuesta de Arseniy-II: tuve el caso de uso en el que la entidad de desplazamiento no era la ventana en sí, sino una plantilla interna (en este caso, un div). En este escenario, necesitamos establecer un ID para el contenedor de desplazamiento y obtenerlo a través de getElementByIdpara usar su función de desplazamiento:

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

¡Dejándolo aquí en caso de que alguien enfrente una situación similar!

kounex
fuente
0

Aquí están mis 2 centavos.

También tuve el problema del scrollIntoView desplazándose un poco más allá del elemento, así que creé un script (javascript nativo) que antepone un elemento al destino, lo coloqué un poco en la parte superior con css y me desplacé hasta ese. Después de desplazarme, elimino los elementos creados nuevamente.

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>

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

¡Espero que esto ayude a alguien!

rubeus
fuente
0

Agrego estos consejos de CSS para aquellos que no resolvieron este problema con las soluciones anteriores:

#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
fuente
0

UPD: He creado un paquete npm que funciona mejor que la siguiente solución y es más fácil de usar.

Mi función smoothScroll

Tomé la maravillosa solución de Steve Banton y escribí una función que la hace más conveniente de usar. Sería más fácil de usar window.scroll()o incluso window.scrollBy(), como lo intenté antes, pero estos dos tienen algunos problemas:

  • Todo se vuelve basura después de usarlos con un comportamiento suave.
  • No puedes evitarlos de todos modos y tienes que esperar hasta el y del pergamino. Así que espero que mi función te sea útil. Además, hay un polyfill ligero que lo hace funcionar en Safari e incluso en IE.

Aqui esta el codigo

Simplemente cópielo y límpielo con él como quiera.

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

Para crear un elemento de enlace, simplemente agregue el siguiente atributo de datos:

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

También puede establecer otro atributo como complemento

data-smooth-scroll-block="center"

Representa la blockopción de la scrollIntoView()función. De forma predeterminada, es start. Leer más sobre MDN .

Finalmente

Ajuste la función smoothScroll a sus necesidades.

Por ejemplo, si tiene algún encabezado fijo (o lo llamo con la palabra masthead), puede hacer algo como esto:

const mastheadEl = document.querySelector(someMastheadSelector);

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

const EXTRA_OFFSET = mastheadEl.offsetHeight - 3;

Si no tiene tal caso, simplemente bórrelo, por qué no :-D.

Dmitry Zakharov
fuente
0

Para un elemento en una fila de la tabla, use JQuery para obtener la fila encima de la que desea y simplemente desplácese hasta esa fila.

Supongamos que tengo varias filas en una tabla, algunas de las cuales deberían ser revisadas por un administrador. Cada fila que requiere revisión tiene una flecha hacia arriba y hacia abajo para llevarlo al elemento anterior o siguiente para su revisión.

Aquí hay un ejemplo completo que debería ejecutarse si crea un nuevo documento HTML en el bloc de notas y lo guarda. Hay un código adicional para detectar la parte superior e inferior de nuestros artículos para su revisión, de modo que no arrojemos ningún error.

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

Solución simple para desplazar un elemento específico hacia abajo

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