Impedir div rolagem para cima quando esbarrado em div abaixo dele?

10

Fiz uma pergunta semelhante ontem, mas a expliquei mal e não especifiquei meu desejo por uma solução CSS pura, o que acho que deveria ser possível, então estou tentando novamente.

Basicamente, tenho um problema em que tenho uma div de mensagens roláveis ​​e um campo de entrada abaixo dele. Quando clico em um botão, gostaria que o campo de entrada aumentasse 100 pixels, sem que a div das mensagens também fosse rolada.

Aqui está um violino que demonstra o problema em sua totalidade

Como você pode ver, quando você clica no botão "adicionar margem", a div das mensagens também é exibida. Eu gostaria que ficasse onde estava. Da mesma forma, se você estiver levemente rolado para cima para poder ver apenas a penúltima e última mensagem, clicar no botão deve manter a mesma posição ao clicar.

O interessante é que esse comportamento é "às vezes" preservado. Por exemplo, em algumas circunstâncias (que não posso deduzir), a posição de rolagem é mantida. Eu gostaria que ele se comportasse de maneira consistente como tal.

window.onload = function(e) {
  document.querySelector(".messages").scrollTop = 10000;
};

function test() {
  document.querySelector(".send-message").classList.toggle("some-margin");
}
.container {
     width: 400px;
     height: 300px;
     border: 1px solid #333;
     display: flex;
     flex-direction: column;
}
 .messages {
     overflow-y: auto;
     height: 100%;
}
 .send-message {
     width: 100%;
     display: flex;
     flex-direction: column;
}
 .some-margin {
     margin-bottom: 100px;
}
<div class="container">
   <div class="messages">
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
   </div>
   <div class="send-message">
      <input />
   </div>
</div>
<button onclick="test()">add margin</button>

insira a descrição da imagem aqui

Ryan Peschel
fonte
11
Olá novamente, sou eu quem respondeu sua pergunta usando JS. Gostaria de informá-lo sobre algo que você pode ter interpretado mal. Você mencionou: a mensagem div também é exibida . Na verdade, não é o divque está rolando para cima, é a altura da div que está diminuindo. Então, por que isso importa? Bem, o que isso significa é que o scrollbarnão está ajustando sua posição. A barra de rolagem lembra apenas sua posição em relação à parte superior do contêiner. Quando a altura da div diminui, parece que a barra de rolagem está rolando para cima, mas na verdade não está.
Richard
11
Sua postagem faz todo o sentido (no sentido de que as pessoas sabem qual resultado você deseja), mas estou compartilhando apenas algo, caso esse conhecimento não seja aparente para você (caso isso possa ajudá-lo a ter uma ideia para resolver problema referido) ;-)
Richard
Sim, é verdade, obrigado pelo esclarecimento. Parece apenas que está rolando para cima, porque você não pode ver o conteúdo mais baixo que você já viu. Na realidade, a posição de rolagem não está mudando. Um pouco de ilusão. Sim, isso pode ajudar a encontrar uma solução.
Ryan Peschel

Respostas:

5

Esta é uma solução divertida que você pode gostar.

O que sabemos sobre a div é que ela preserva apenas a posição superior da barra de rolagem; portanto, se a altura for alterada por algum motivo, a barra de rolagem permanecerá a mesma e é isso que causa o seu problema.

Como solução alternativa, você pode girar os .messages180 graus usando transform: rotate(180deg) scaleX(-1);e retroceder .messagepara cancelar o lançamento do conteúdo, e a div manterá a barra de rolagem inferior (que é a parte superior) automaticamente.

function test() {
  document.querySelector(".send-message").classList.toggle("some-margin")
}
.container {
  width: 400px;
  height: 300px;
  border: 1px solid #333;
  display: flex;
  flex-direction: column;
}

.messages {
  overflow-y: auto;
  height: 100%;
  transform: rotate(180deg) scaleX(-1);
}

.message
{
  transform: rotate(180deg) scaleX(-1);
}
.send-message {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.some-margin {
  margin-bottom: 100px;
}
<div class="container">
  <div class="messages">
  <div class="message">hello1</div>
  <div class="message">hello2</div>
  <div class="message">hello3</div>
  <div class="message">hello4</div>
  <div class="message">hello5</div>
  <div class="message">hello6</div>
  <div class="message">hello7</div>
  <div class="message">hello8</div>
  <div class="message">hello9</div>
  <div class="message">hello10</div>
  <div class="message">hello11</div>
  <div class="message">hello12</div>
  <div class="message">hello13</div>
  <div class="message">hello14</div>
  <div class="message">hello15</div>
  <div class="message">hello16</div>
  <div class="message">hello17</div>
  <div class="message">hello18</div>
  <div class="message">hello19</div>
  <div class="message">hello20</div>
  </div>
  <div class="send-message">
  <input />
  </div>
</div>

<button onclick="test()">add margin</button>

Manjericão
fonte
Isso tem o problema em que a rolagem do mouse fica para trás (a rolagem para cima diminui e a rolagem para baixo aumenta)
Ryan Peschel
Ok, passei muito tempo nisso, mas ainda agora outros desenvolvedores têm 3 respostas que você não está procurando :) Eu esperava ajudá-lo a desejar boa sorte. mas como um conselho positivo que gasta tempo tentando responder à sua pergunta, você não será cobrado por isso.
Basil
2
Coisa certa. Obrigado de qualquer maneira
Ryan Peschel 07/01
Isso é muito inteligente ;-) Infelizmente, a rolagem invertida é um pouco desanimadora.
Richard
3

O comportamento normal da barra de rolagem deve estar no topo; portanto, quando você o define na parte inferior do carregamento da página, é necessário mantê-lo, porque sempre que o conteúdo abaixo for pressionado, o div scroll será movido para o topo.

Então, eu tenho duas soluções para você:

  1. Inverta as mensagens dentro da div de mensagens, para que a última mensagem seja a primeira, para que a rolagem esteja sempre no topo.

  2. Eu criei uma função javascript para rolar para a parte inferior de qualquer elemento, para que você a chame sempre que quiser rolar para a parte inferior.

function scrollbottom(e)
    {
      e.scrollTop = e.clientHeight;
    }

Verifique o trecho

var elem = document.querySelector(".messages");

window.onload = function(e){ 
  scrollbottom(elem);
}

function test() {
  document.querySelector(".send-message").classList.toggle("some-margin");
  scrollbottom(elem);
}

function scrollbottom(e)
{
  e.scrollTop = e.clientHeight;
}
.container {
  width: 400px;
  height: 300px;
  border: 1px solid #333;
  display: flex;
  flex-direction: column;
}

.messages {
  overflow-y: auto;
  height: 100%;
}

.send-message {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.some-margin {
  margin-bottom: 100px;
}
<div class="container">
  <div class="messages">
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  </div>
  <div class="send-message">
  <input />
  </div>
</div>

<button onclick="test()">add margin</button>

Manjericão
fonte
Infelizmente, não quero rolar para rolar até o final. Eu quero manter a sua posição. Além disso, é provável que isso possa ser feito sem Javascript. No OP: "Da mesma forma, se você estiver levemente rolado para cima para poder ver apenas a penúltima mensagem, clicar no botão deve manter a mesma posição ao clicar".
Ryan Peschel
3

Você pode fazer o contrário, dando a altura para o em .messages vez de dar para .container, neste caso, não afetará as mensagens, divmas se você estiver dando para .containerele, ele empurrará sua div porque a margem está dentro do principal div que tem uma altura.

Verifique este trecho

function test() {
  document.querySelector(".send-message").classList.toggle("some-margin");
}
.container {
  width: 400px;
  border: 1px solid #333;
  display: flex;
  flex-direction: column;
}

.messages {
  height: 300px;
  overflow-y: auto;
}

.send-message {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.some-margin {
  margin-bottom: 50px;
}
<div class="container">
  <div class="messages">
  <div class="message">hello1</div>
  <div class="message">hello2</div>
  <div class="message">hello3</div>
  <div class="message">hello4</div>
  <div class="message">hello5</div>
  <div class="message">hello6</div>
  <div class="message">hello7</div>
  <div class="message">hello8</div>
  <div class="message">hello9</div>
  <div class="message">hello10</div>
  <div class="message">hello11</div>
  <div class="message">hello12</div>
  <div class="message">hello13</div>
  <div class="message">hello14</div>
  <div class="message">hello15</div>
  <div class="message">hello16</div>
  <div class="message">hello17</div>
  <div class="message">hello18</div>
  <div class="message">hello19</div>
  <div class="message">hello20</div>
  </div>
  <div class="send-message">
  <input />
  </div>
</div>

<button onclick="test()">add margin</button>

Manjericão
fonte
Isso não funciona porque a margem precisa ser aplicada .send-message. Se você olhar para a minha pergunta anterior, isso é feito porque é semelhante ao aumento de margem da abertura do teclado virtual em dispositivos móveis (a margem inferior é apenas uma substituição de 1: 1 para depurar a solução)
Ryan Peschel
3

A solução simples é definir o anterior scrollTopem alternância. Aqui estou usando o conjunto de dados para armazenar o scrollTopvalor anterior , você também pode usar a variável

window.onload = function(e) {
  document.querySelector(".messages").scrollTop = 10000;
}

function test() {
  let state = document.querySelector(".send-message").classList.toggle("some-margin")
  let div = document.querySelector(".messages");

  if (state) {
    div.dataset.top = div.scrollTop;
    div.scrollTop += 100 // same as margin-bottom of .some-margin
  } else {
    div.scrollTop = div.dataset.top;
  }
}
.container {
  width: 400px;
  height: 300px;
  border: 1px solid #333;
  display: flex;
  flex-direction: column;
}

.messages {
  overflow-y: auto;
  height: 100%;
}

.send-message {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.some-margin {
  margin-bottom: 100px;
}
<div class="container">
  <div class="messages">
    <div class="message">hello1</div>
    <div class="message">hello2</div>
    <div class="message">hello3</div>
    <div class="message">hello4</div>
    <div class="message">hello5</div>
    <div class="message">hello6</div>
    <div class="message">hello7</div>
    <div class="message">hello8</div>
    <div class="message">hello9</div>
    <div class="message">hello10</div>
    <div class="message">hello11</div>
    <div class="message">hello12</div>
    <div class="message">hello13</div>
    <div class="message">hello14</div>
    <div class="message">hello15</div>
    <div class="message">hello16</div>
    <div class="message">hello17</div>
    <div class="message">hello18</div>
    <div class="message">hello19</div>
    <div class="message">hello20</div>
  </div>
  <div class="send-message">
    <input />
  </div>
</div>

<button onclick="test()">add margin</button>

Usuário863
fonte
0

Quero dizer, a seguir é uma solução apenas CSS que resolve exatamente o seu exemplo:

.some-margin{margin-bottom:100px;}

.messages{margin-top:-100px;}

mas acho que não o ajudará com seu problema original do teclado virtual. Mas talvez isso possa inspirar outra pessoa a uma solução.

O principal problema parece ser que a barra de rolagem está fazendo exatamente o que você deseja:

Manter sua posição para que o conteúdo lido mais recentemente seja visível.

Exceto que a barra de rolagem decide que você deseja ver a PARTE SUPERIOR do conteúdo visível no momento, não a parte inferior. (É mais fácil ver se você usa números: https://jsfiddle.net/1co3x48n/ )

Se você deseja usar o javascript, há todo tipo de respostas: https://www.google.com/search?q=scrollbar+always+on+bottom+css+site:stackoverflow.com

Honestamente, o fato de você poder passar por ~ 3 + páginas disso e encontrar apenas respostas Javascript me diz que você não encontrará uma resposta somente CSS, certamente não uma que seja amplamente compatível com o navegador.

Eliezer Berlin
fonte
0

Infelizmente, nenhuma das soluções fornecidas realmente parece funcionar. O problema com o uso onFocusde uma caixa de texto é que ele não será aplicado se a caixa de texto já estiver focada. Eu estou mexendo com isso há horas e a solução mais próxima que eu encontrei até agora é a seguinte:

componentDidMount() {
  this.screenHeight = window.innerHeight;

  let chatElem = document.querySelector(".conversations-chat");

  window.addEventListener('resize', () => {
    let diff = this.screenHeight - window.innerHeight;

    chatElem.scrollTop += diff;

    this.screenHeight = window.innerHeight;      
  });
}

No entanto, isso parece funcionar algumas vezes. Funciona 100% do tempo ao clicar na caixa de texto, mas, por algum motivo, ao fechar o teclado virtual, funciona apenas se tiver sido rolado para cima o suficiente. Caso contrário, ele será redefinido para a mesma posição todas as vezes (próximo à parte inferior, mas não exatamente na parte inferior).

Não tenho certeza do que está causando isso. Terá que investigar mais amanhã, suponho. Este é o mais próximo até agora.

Ryan Peschel
fonte
Como todos dissemos em nossas respostas, parece impossível fazer apenas CSS. (Há um problema com seu código JS, que é o chatElem.scrollTop não pode ser menor que 0 , portanto, se estiver muito próximo do topo, ele tenta se tornar um número negativo, se torna 0 e, em vez disso, quando você aumenta a tela novamente, um pergaminho aparece onde não havia antes.)
Eliezer Berlin
Da mesma forma, chatElem.scrollTop não pode ser mais do que chatElem.scrollHeight - chatElem.offsetHeight , portanto, ele criará uma rolagem extra onde não havia antes, se você tentar fazer isso.
Eliezer Berlin
0

Crie um contêiner como nos exemplos acima, mas apenas altere o CSS quando começar a digitar.

Não defino a posição da barra de rolagem, basta mover todo o contêiner de mensagens para cima. Portanto, qualquer que seja sua posição de rolagem, permanecerá a mesma. Pelo menos para baixo, é claro que você não poderá ver as linhas no topo.

Você poderá ajustar o css às ​​suas necessidades. Você pode até ficar sem JS se usar: foco ou configuração CSS ligeiramente diferente, seja criativo :)

function activate(){
    document.querySelector("#container").classList.toggle("active");
  }
#container{
    position:relative;
    width:300px;
    height:100vh;
    max-height:230px;
    overflow:hidden;
  }
  #messages{
    position:absolute;
    bottom:30px;
    overflow:auto;
    height:200px;
    width:100%;
    background:#eee;
  }
  #container.active #messages {
    bottom:100px;
  }
  #send-message{
    position:absolute;
    border:1px solid #ccc;
    bottom:0;
    height:28px;
  }
  #container.active #send-message{
    height:100px;
  }
<div id="container">
  <div id="messages">
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  <div class="message">hello</div>
  </div>
  <div id="send-message">
    <input id="newmessage" onclick="activate()" type="text" />
  </div>
</div>

Rmaxx
fonte
0

você deve adicionar document.querySelector(".messages").scrollTop = 10000;à testfunção quando adicionar margem e scrollTopterminar no carregamento definido scrolltope quando você adicionar margem ou remover margem scrolltopnão definir novamente, altere seu script para que fique assim

<script>
    window.onload = function(e){ 
    document.querySelector(".messages").scrollTop = 10000;
}

function test() {
    document.querySelector(".send-message").classList.toggle("some-margin")
            document.querySelector(".messages").scrollTop = 10000;
}
    </script>
MBadrian
fonte
0

Parece haver um problema com a função de alternância js. Eu mudei para um simples hide & show. Também adicionei uma largura na tag de entrada. Você também precisa adicionar uma posição: absoluta na div de envio de mensagens e inferior: 0 para que fique na parte inferior. em seguida, coloque position: relativa no contêiner para que a div de envio da mensagem permaneça dentro do contêiner. Isso significa que o contêiner de mensagens não afetará as próprias mensagens e as empurrará para cima.

window.onload = function(e) {
  document.querySelector(".messages").scrollTop = 10000;
};

function test() {
  var x = document.querySelector(".send-message");

  if (x.style.display === "none") {
    x.style.display = "block";
  } else {
    x.style.display = "none";
  }
}
.container {
     width: 400px;
     height: 300px;
     border: 1px solid #333;
     display: flex;
     flex-direction: column;
     position: relative;
}
 .messages {
     overflow-y: auto;
     height: 100%;
}
 .send-message {
     width: 100%;
     display: flex;
     flex-direction: column;
     position: absolute;
     bottom: 0;
}
 .send-message input {
     width:100%;
}
 .some-margin {
     margin-bottom: 100px;
}

 
 
<div class="container">
   <div class="messages">
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">test</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
      <div class="message">hello</div>
   </div>
   <div class="send-message">
      <input />
   </div>
</div>
<button onclick="test()">add margin</button>

Shannaki
fonte
Eu não entendo essa solução. Parece apenas ocultar a caixa de texto de envio de mensagem quando você clica em adicionar margem?
Ryan Peschel
-1

Uma abordagem CSS pura e imperfeita que eu posso criar é a seguinte:

window.onload = function(e){ 
  document.querySelector(".messages").scrollTop = 10000;
}

document.querySelector('button').addEventListener('click', test)

function test() {
  document.querySelector(".send-message").classList.toggle("some-margin")
  document.querySelector('.wrapper').classList.toggle('wrapper--transformed')
}
.container {
  width: 400px;
  height: 300px;
  border: 1px solid #333;
  display: flex;
  flex-direction: column;
}

.messages {
  position: relative;
  overflow-y: auto;
  height: 100%;
}

.wrapper {
  display: block;
}

.wrapper--transformed {
  transform: translateY(-100px);
  margin-bottom: -100px;
}

.send-message {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.some-margin {
  margin-bottom: 100px;
}
<div class="container">
  <div class="messages">
    <div class = "wrapper">
      <div class="message">hello1</div>
      <div class="message">hello2</div>
      <div class="message">hello3</div>
      <div class="message">hello4</div>
      <div class="message">hello5</div>
      <div class="message">hello6</div>
      <div class="message">hello7</div>
      <div class="message">hello8</div>
      <div class="message">hello9</div>
      <div class="message">hello1</div>
      <div class="message">hello2</div>
      <div class="message">hello3</div>
      <div class="message">hello4</div>
      <div class="message">hello5</div>
      <div class="message">hello6</div>
      <div class="message">hello7</div>
      <div class="message">hello8</div>
      <div class="message">hello9</div>
      <div class="message">hello1</div>
      <div class="message">hello2</div>
    </div>
  </div>
  <div class="send-message">
    <input />
  </div>
</div>
<button>add margin</button>

A seção superior tem um pequeno problema quando a margem existe. O truque aqui é usar margem negativa e traduzir para "rolar para cima" (não é realmente um pergaminho, apenas uma ilusão) a wrapperdiv.

Richard
fonte
A questão da seção superior é um pouco problemática, sim. Além disso, isso pressupõe saber o tamanho exato do teclado móvel para saber quanto compensar? Não tenho certeza se tenho acesso a essas informações.
Ryan Peschel
Para responder sua pergunta, sim. A técnica acima deve saber a altura exata do teclado. No entanto, existem maneiras de usar JS para determinar a altura de um elemento e alterar o CSS.
Richard
Como você determinaria a altura do teclado virtual móvel?
Ryan Peschel
@RyanPeschel Esse teclado virtual é um elemento do HTML (por exemplo, div )?
Richard
Não sei ao certo como é codificado.
Ryan Peschel 10/01
-2

Pode muito simplesmente ser endereçado com nomes de âncora. Veja o violino do JS em https://jsfiddle.net/gvbz93sx/

Alteração de HTML <a name="bottom"></a>

Mudança de JS document.location.hash = "#bottom";

Animesh
fonte