Como rolar para um elemento dentro de uma div?

170

Eu tenho um rolado dive quero ter um link quando clico nele, forçará isso diva rolar para exibir um elemento dentro. Eu escrevi seu JavasSript assim:

document.getElementById(chr).scrollIntoView(true);

mas isso rola toda a página enquanto rola a divprópria. Como consertar isso?

Eu quero dizer assim

MyContainerDiv.getElementById(chr).scrollIntoView(true);
Amr Elgarhy
fonte
O Scrolltop nem sempre retorna um valor utilizável. Costumo usar a SetTimeoutna $(document).ready({})função e definir focus()o elemento para o qual você deseja rolar. Funciona para mim
DerpyNerd

Respostas:

340

Você precisa obter o deslocamento superior do elemento que deseja rolar para exibição, em relação ao pai (o contêiner div de rolagem):

var myElement = document.getElementById('element_within_div');
var topPos = myElement.offsetTop;

A variável topPos agora está definida para a distância entre a parte superior da div de rolagem e o elemento que você deseja ter visível (em pixels).

Agora dizemos ao div para rolar para essa posição usando scrollTop:

document.getElementById('scrolling_div').scrollTop = topPos;

Se você estiver usando a estrutura JS do protótipo, faça o mesmo:

var posArray = $('element_within_div').positionedOffset();
$('scrolling_div').scrollTop = posArray[1];

Novamente, isso rolará a div para que o elemento que você deseja ver esteja exatamente no topo (ou, se isso não for possível, role o mais para baixo possível, para que fique visível).

Brian Barrett
fonte
9
Isso foi realmente útil! Se você quiser definir a rolagem várias vezes, precisará compensar a localização atual da rolagem. Aqui está como eu fiz isso no jQuery: $ ('# scrolling_div'). ScrollTop ($ ('# scrolling_div'). ScrollTop () + $ ('# element_within_div'). Position (). Top);
Will
3
Cuidado que solicitar myElement.offsetTopgatilho vontade refluxo (goleada de layout), que poderia ser um gargalo de desempenho
Kestutis
27
Lembre-se de definir o pai de rolagem com css: position: relativecaso contrário, você gastará muito tempo depurando como eu acabei de fazer.
Saveed
2
Eu tive que definir a overflow-ypropriedade scrollpara o elemento pai ( scrolling_div), caso contrário não estava funcionando. O valor css padrão para a overflowpropriedade é autoe mesmo que ele também faz a rolagem manual do possível, o código js não funcionaria (nem mesmo com {psition: relative}..)
Evgenia Manolova
2
Ainda funciona em 2017. Informações adicionais: .offsetTop pode retornar 0. Então você deve consultar um elemento pai e tentar novamente. Eu fiz isso para as tags h4e divdepois as articletags e só articlefuncionou para mim.
Fenio 7/11
66

Você precisaria encontrar a posição do elemento no DIV para o qual deseja rolar e definir a propriedade scrollTop.

divElem.scrollTop = 0;

Atualização :

Código de exemplo para mover para cima ou para baixo

  function move_up() {
    document.getElementById('divElem').scrollTop += 10;
  }

  function move_down() {
    document.getElementById('divElem').scrollTop -= 10;
  }
Glennular
fonte
3
eu quero deslocá-lo ver, para ver que não rolagem com um determinado valor
Amr Elgarhy
39

Método 1 - Rolagem suave para um elemento dentro de um elemento

var box = document.querySelector('.box'),
    targetElm = document.querySelector('.boxChild'); // <-- Scroll to here within ".box"

document.querySelector('button').addEventListener('click', function(){
   scrollToElm( box, targetElm , 600 );   
});


/////////////

function scrollToElm(container, elm, duration){
  var pos = getRelativePos(elm);
  scrollTo( container, pos.top , 2);  // duration in seconds
}

function getRelativePos(elm){
  var pPos = elm.parentNode.getBoundingClientRect(), // parent pos
      cPos = elm.getBoundingClientRect(), // target pos
      pos = {};

  pos.top    = cPos.top    - pPos.top + elm.parentNode.scrollTop,
  pos.right  = cPos.right  - pPos.right,
  pos.bottom = cPos.bottom - pPos.bottom,
  pos.left   = cPos.left   - pPos.left;

  return pos;
}
    
function scrollTo(element, to, duration, onDone) {
    var start = element.scrollTop,
        change = to - start,
        startTime = performance.now(),
        val, now, elapsed, t;

    function animateScroll(){
        now = performance.now();
        elapsed = (now - startTime)/1000;
        t = (elapsed/duration);

        element.scrollTop = start + change * easeInOutQuad(t);

        if( t < 1 )
            window.requestAnimationFrame(animateScroll);
        else
            onDone && onDone();
    };

    animateScroll();
}

function easeInOutQuad(t){ return t<.5 ? 2*t*t : -1+(4-2*t)*t };
.box{ width:80%; border:2px dashed; height:180px; overflow:auto; }
.boxChild{ 
  margin:600px 0 300px; 
  width: 40px;
  height:40px;
  background:green;
}
<button>Scroll to element</button>
<div class='box'>
  <div class='boxChild'></div>
</div>

Método 2 - usando Element.scrollIntoView :

Observe que o suporte ao navegador não é bom para este

var targetElm = document.querySelector('.boxChild'),  // reference to scroll target
    button = document.querySelector('button');        // button that triggers the scroll
  
// bind "click" event to a button 
button.addEventListener('click', function(){
   targetElm.scrollIntoView()
})
.box {
  width: 80%;
  border: 2px dashed;
  height: 180px;
  overflow: auto;
  scroll-behavior: smooth; /* <-- for smooth scroll */
}

.boxChild {
  margin: 600px 0 300px;
  width: 40px;
  height: 40px;
  background: green;
}
<button>Scroll to element</button>
<div class='box'>
  <div class='boxChild'></div>
</div>

Método 3 - Usando o comportamento de rolagem CSS :

.box {
  width: 80%;
  border: 2px dashed;
  height: 180px;
  overflow-y: scroll;
  scroll-behavior: smooth; /* <--- */
}

#boxChild {
  margin: 600px 0 300px;
  width: 40px;
  height: 40px;
  background: green;
}
<a href='#boxChild'>Scroll to element</a>
<div class='box'>
  <div id='boxChild'></div>
</div>

vsync
fonte
1
Observe que o comportamento de rolagem não é suportado no IE / Edge / Safari
sheats 23/02/19
1
@sheats - diz exatamente isso no link da documentação do MDN que eu havia colocado em tamanho de fonte grande. Mesmo que não funcione para esses navegadores, isso não significa que você não deve usá-lo. Não existe uma "regra", tudo deve se comportar da mesma maneira em todos os navegadores. Se os navegadores modernos podem fazer mágica, deixe-os fazer mágica.
vsync
Esta postagem é sobre a rolagem de todo o documento, em vez de um elemento rolável.
@ DoMiNeLa10 - Essa é uma suposição. O OP pode ter fornecido um exemplo arbitrário que pretendia ilustrar seu problema. Além disso, o OP não é a principal preocupação, mas as pessoas provenientes de mecanismos de pesquisa que procuram uma solução saudável, e provavelmente responder especificamente às necessidades do OP não irá ajudá-los, e o objetivo deste site é criar respostas sólidas que possam ajudar muitos que possível. Minha resposta fornece os dois .
vsync
12

Para rolar um elemento na visualização de uma div, somente se necessário, você pode usar esta scrollIfNeededfunção:

function scrollIfNeeded(element, container) {
  if (element.offsetTop < container.scrollTop) {
    container.scrollTop = element.offsetTop;
  } else {
    const offsetBottom = element.offsetTop + element.offsetHeight;
    const scrollBottom = container.scrollTop + container.offsetHeight;
    if (offsetBottom > scrollBottom) {
      container.scrollTop = offsetBottom - container.offsetHeight;
    }
  }
}

document.getElementById('btn').addEventListener('click', ev => {
  ev.preventDefault();
  scrollIfNeeded(document.getElementById('goose'), document.getElementById('container'));
});
.scrollContainer {
  overflow-y: auto;
  max-height: 100px;
  position: relative;
  border: 1px solid red;
  width: 120px;
}

body {
  padding: 10px;
}

.box {
  margin: 5px;
  background-color: yellow;
  height: 25px;
  display: flex;
  align-items: center;
  justify-content: center;
}

#goose {
  background-color: lime;
}
<div id="container" class="scrollContainer">
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div id="goose" class="box">goose</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
</div>

<button id="btn">scroll to goose</button>

mpen
fonte
1
Isso foi realmente útil. Lutei um pouco porque perdi a posição: relativa no contêiner. Isso foi crucial!
brohr 18/05/19
11

O código deve ser:

var divElem = document.getElementById('scrolling_div');
var chElem = document.getElementById('element_within_div');
var topPos = divElem.offsetTop;
divElem.scrollTop = topPos - chElem.offsetTop;

Você deseja rolar a diferença entre a posição superior do filho e a posição superior do div.

Obtenha acesso a elementos filho usando:

var divElem = document.getElementById('scrolling_div'); 
var numChildren = divElem.childNodes.length;

e assim por diante....

pgp
fonte
1
A segunda linha não deveria realmente ler var chElem = document.getElementById('element_within_div');e a terceira linha ler var topPos = divElem.offsetTop;?
jayp
7

Se você estiver usando jQuery, poderá rolar com uma animação usando o seguinte:

$(MyContainerDiv).animate({scrollTop: $(MyContainerDiv).scrollTop() + ($('element_within_div').offset().top - $(MyContainerDiv).offset().top)});

A animação é opcional: você também pode pegar o valor scrollTop calculado acima e colocá-lo diretamente na propriedade scrollTop do contêiner .

dlauzon
fonte
5

JS nativo, navegador cruzado, rolagem suave (atualização 2020)

A configuração ScrollTopfornece o resultado desejado, mas a rolagem é muito abrupta. Usar jqueryuma rolagem suave não era uma opção. Então, aqui está uma maneira nativa de realizar o trabalho que suporta todos os principais navegadores. Referência - caniuse

// get the "Div" inside which you wish to scroll (i.e. the container element)
const El = document.getElementById('xyz');

// Lets say you wish to scroll by 100px, 
El.scrollTo({top: 100, behavior: 'smooth'});

// If you wish to scroll until the end of the container
El.scrollTo({top: El.scrollHeight, behavior: 'smooth'});

É isso aí!


E aqui está um trecho de trabalho para os duvidosos -

document.getElementById('btn').addEventListener('click', e => {
  e.preventDefault();
  // smooth scroll
  document.getElementById('container').scrollTo({top: 175, behavior: 'smooth'});
});
/* just some styling for you to ignore */
.scrollContainer {
  overflow-y: auto;
  max-height: 100px;
  position: relative;
  border: 1px solid red;
  width: 120px;
}

body {
  padding: 10px;
}

.box {
  margin: 5px;
  background-color: yellow;
  height: 25px;
  display: flex;
  align-items: center;
  justify-content: center;
}

#goose {
  background-color: lime;
}
<!-- Dummy html to be ignored -->
<div id="container" class="scrollContainer">
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div id="goose" class="box">goose</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
</div>

<button id="btn">goose</button>

Atualização: Como você pode perceber nos comentários, parece que isso Element.scrollTo()não é suportado no IE11. Portanto, se você não se importa com o IE11 (realmente não deveria), fique à vontade para usá-lo em todos os seus projetos. Observe que existe suporte para o Edge! Então você não está realmente deixando seus usuários do Edge / Windows para trás;)

Referência

Niket Pathak
fonte
1
scrollTo()pode ser suportado em todos os principais navegadores para Windowobjetos, mas não no IE ou Edge para elementos.
Tim Down
De acordo com caniuse , ele é suportado no IE11 e Edge. Não testei pessoalmente nesses navegadores, mas parece ser suportado.
Niket Pathak
1
Isso window.scrollTonão é Element.scrollTo. Tente isso no Edge, por exemplo, e verifique o console: codepen.io/timdown/pen/abzVEMB
Tim Down
você está certo. O IE11 não é suportado. No entanto, o Edge v76 (ref) e superior parecem ser suportados
Niket Pathak 06/01
2

Existem dois fatos:

1) O componente scrollIntoView não é suportado pelo safari.

2) A estrutura JS jQuery pode fazer o trabalho da seguinte maneira:

parent = 'some parent div has css position==="fixed"' || 'html, body';

$(parent).animate({scrollTop: $(child).offset().top}, duration)
nickmit
fonte
1

Aqui está uma solução JavaScript pura e simples que funciona para um número de destino (valor para scrollTop), elemento DOM de destino ou alguns casos especiais de String:

/**
 * target - target to scroll to (DOM element, scrollTop Number, 'top', or 'bottom'
 * containerEl - DOM element for the container with scrollbars
 */
var scrollToTarget = function(target, containerEl) {
    // Moved up here for readability:
    var isElement = target && target.nodeType === 1,
        isNumber = Object.prototype.toString.call(target) === '[object Number]';

    if (isElement) {
        containerEl.scrollTop = target.offsetTop;
    } else if (isNumber) {
        containerEl.scrollTop = target;
    } else if (target === 'bottom') {
        containerEl.scrollTop = containerEl.scrollHeight - containerEl.offsetHeight;
    } else if (target === 'top') {
        containerEl.scrollTop = 0;
    }
};

E aqui estão alguns exemplos de uso:

// Scroll to the top
var scrollableDiv = document.getElementById('scrollable_div');
scrollToTarget('top', scrollableDiv);

ou

// Scroll to 200px from the top
var scrollableDiv = document.getElementById('scrollable_div');
scrollToTarget(200, scrollableDiv);

ou

// Scroll to targetElement
var scrollableDiv = document.getElementById('scrollable_div');
var targetElement= document.getElementById('target_element');
scrollToTarget(targetElement, scrollableDiv);
Acéfalo
fonte
1

Outro exemplo de uso do jQuery e do Animate.

var container = $('#container');
var element = $('#element');

container.animate({
    scrollTop: container.scrollTop = container.scrollTop() + element.offset().top - container.offset().top
}, {
    duration: 1000,
    specialEasing: {
        width: 'linear',
        height: 'easeOutBounce'
    },
    complete: function (e) {
        console.log("animation completed");
    }
});
Hopefulee
fonte
0

Rolagem animada pelo usuário

Aqui está um exemplo de como rolar programaticamente uma <div>horizontalmente, sem o JQuery . Para rolar verticalmente, você substitui as gravações do JavaScript por scrollLeftpor scrollTop.

JSFiddle

https://jsfiddle.net/fNPvf/38536/

HTML

<!-- Left Button. -->
<div style="float:left;">
    <!-- (1) Whilst it's pressed, increment the scroll. When we release, clear the timer to stop recursive scroll calls. -->
    <input type="button" value="«" style="height: 100px;" onmousedown="scroll('scroller',3, 10);" onmouseup="clearTimeout(TIMER_SCROLL);"/>
</div>
<!-- Contents to scroll. -->
<div id="scroller" style="float: left; width: 100px; height: 100px; overflow: hidden;">
    <!-- <3 -->
    <img src="https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a" alt="image large" style="height: 100px" />
</div>
<!-- Right Button. -->
<div style="float:left;">
    <!-- As (1). (Use a negative value of 'd' to decrease the scroll.) -->
    <input type="button" value="»" style="height: 100px;" onmousedown="scroll('scroller',-3, 10);" onmouseup="clearTimeout(TIMER_SCROLL);"/>
</div>

Javascript

// Declare the Shared Timer.
var TIMER_SCROLL;
/** 
Scroll function. 
@param id  Unique id of element to scroll.
@param d   Amount of pixels to scroll per sleep.
@param del Size of the sleep (ms).*/
function scroll(id, d, del){
    // Scroll the element.
    document.getElementById(id).scrollLeft += d;
    // Perform a delay before recursing this function again.
    TIMER_SCROLL = setTimeout("scroll('"+id+"',"+d+", "+del+");", del);
 }

Crédito para Dux .


Rolagem animada automática

Além disso, aqui estão as funções para rolar um <div>totalmente para a esquerda e direita. A única coisa que mudamos aqui é verificar se a extensão completa da rolagem foi utilizada antes de fazer uma chamada recursiva para rolar novamente.

JSFiddle

https://jsfiddle.net/0nLc2fhh/1/

HTML

<!-- Left Button. -->
<div style="float:left;">
    <!-- (1) Whilst it's pressed, increment the scroll. When we release, clear the timer to stop recursive scroll calls. -->
    <input type="button" value="«" style="height: 100px;" onclick="scrollFullyLeft('scroller',3, 10);"/>
</div>
<!-- Contents to scroll. -->
<div id="scroller" style="float: left; width: 100px; height: 100px; overflow: hidden;">
  <!-- <3 -->
  <img src="https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a" alt="image large" style="height: 100px" />
</div>
<!-- Right Button. -->
<div style="float:left;">
    <!-- As (1). (Use a negative value of 'd' to decrease the scroll.) -->
    <input type="button" value="»" style="height: 100px;" onclick="scrollFullyRight('scroller',3, 10);"/>
</div>

Javascript

// Declare the Shared Timer.
var TIMER_SCROLL;
/** 
Scroll fully left function; completely scrolls  a <div> to the left, as far as it will go.
@param id  Unique id of element to scroll.
@param d   Amount of pixels to scroll per sleep.
@param del Size of the sleep (ms).*/
function scrollFullyLeft(id, d, del){
    // Fetch the element.
    var el = document.getElementById(id);
    // Scroll the element.
    el.scrollLeft += d;
    // Have we not finished scrolling yet?
    if(el.scrollLeft < (el.scrollWidth - el.clientWidth)) {
        TIMER_SCROLL = setTimeout("scrollFullyLeft('"+id+"',"+d+", "+del+");", del);
    }
}

/** 
Scroll fully right function; completely scrolls  a <div> to the right, as far as it will go.
@param id  Unique id of element to scroll.
@param d   Amount of pixels to scroll per sleep.
@param del Size of the sleep (ms).*/
function scrollFullyRight(id, d, del){
    // Fetch the element.
    var el = document.getElementById(id);
    // Scroll the element.
    el.scrollLeft -= d;
    // Have we not finished scrolling yet?
    if(el.scrollLeft > 0) {
        TIMER_SCROLL = setTimeout("scrollFullyRight('"+id+"',"+d+", "+del+");", del);
    }
}
Mapsy
fonte
0

Isto é o que finalmente me serviu

/** Set parent scroll to show element
 * @param element {object} The HTML object to show
 * @param parent {object} The HTML object where the element is shown  */
var scrollToView = function(element, parent) {
    //Algorithm: Accumulate the height of the previous elements and add half the height of the parent
    var offsetAccumulator = 0;
    parent = $(parent);
    parent.children().each(function() {
        if(this == element) {
            return false; //brake each loop
        }
        offsetAccumulator += $(this).innerHeight();
    });
    parent.scrollTop(offsetAccumulator - parent.innerHeight()/2);
}
Iñigo Panera
fonte
0

o navegador faz a rolagem automática para um elemento que obtém o foco; portanto, o que você também pode fazer para agrupar o elemento no qual você precisa ser rolado <a>...</a>e, quando precisar rolar, defina o focoa

Trident D'Gao
fonte
0

Após selecionar o elemento, use a scrollIntoViewfunção com a opção abaixo:

const option = {
  top: 0, // number,
  left: 0, // number,
  behavior: 'auto', // auto or smooth 
    // - auto for one jump motion and smooth for animated motion -
};

Portanto, a resposta para este post é:

const el = document.getElementById('id-name');
el.scrollIntoView({
  top: 0,
  left: 0,
  behavior: 'auto',
});
AmerllicA
fonte
0

dado que você tem um elemento div que precisa rolar para dentro, tente este trecho de código

document.querySelector('div').scroll(x,y)

isso funciona comigo dentro de uma div com um pergaminho, isso deve funcionar com você caso você aponte o mouse sobre esse elemento e tente rolar para baixo ou para cima. Se funcionar manualmente, também deve funcionar

Amado Saladino
fonte