Estou tentando imitar outros aplicativos de bate-papo para celular onde, quando você seleciona a send-message
caixa de texto e ela abre o teclado virtual, a mensagem mais abaixo ainda está em exibição. Parece não haver uma maneira de fazer isso com o CSS de maneira surpreendente, então o JavaScript resize
(única maneira de descobrir quando o teclado é aberto e fechado aparentemente) eventos e rolagem manual para o resgate.
Alguém forneceu essa solução e eu descobri essa solução , que parece funcionar.
Exceto em um caso. Por alguma razão, se você estiver dentro MOBILE_KEYBOARD_HEIGHT
(250 pixels no meu caso) de pixels da parte inferior da div das mensagens, quando fechar o teclado do celular, algo estranho acontecerá. Com a solução anterior, ela rola para o fundo. E com a última solução, em vez disso, rola os MOBILE_KEYBOARD_HEIGHT
pixels para baixo.
Se você for rolado acima dessa altura, as duas soluções fornecidas acima funcionarão perfeitamente. Somente quando você está perto do fundo é que eles têm esse problema menor.
Eu pensei que talvez fosse apenas o meu programa causando isso com algum código estranho, mas não, eu até reproduzi um violino e ele tem esse problema exato. Peço desculpas por tornar isso tão difícil de depurar, mas se você acessar https://jsfiddle.net/t596hy8d/6/show (o sufixo show fornece um modo de tela cheia) no seu telefone, você poderá ver o mesmo comportamento.
Sendo esse comportamento, se você rolar o suficiente, abrir e fechar o teclado mantém a posição. No entanto, se você fechar o teclado dentro dos MOBILE_KEYBOARD_HEIGHT
pixels da parte inferior, verá que ele rola para a parte inferior.
O que está causando isso?
Reprodução de código aqui:
window.onload = function(e){
document.querySelector(".messages").scrollTop = 10000;
bottomScroller(document.querySelector(".messages"));
}
function bottomScroller(scroller) {
let scrollBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;
scroller.addEventListener('scroll', () => {
scrollBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;
});
window.addEventListener('resize', () => {
scroller.scrollTop = scroller.scrollHeight - scrollBottom - scroller.clientHeight;
scrollBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;
});
}
.container {
width: 400px;
height: 87vh;
border: 1px solid #333;
display: flex;
flex-direction: column;
}
.messages {
overflow-y: auto;
height: 100%;
}
.send-message {
width: 100%;
display: flex;
flex-direction: column;
}
<div class="container">
<div class="messages">
<div class="message">hello 1</div>
<div class="message">hello 2</div>
<div class="message">hello 3</div>
<div class="message">hello 4</div>
<div class="message">hello 5</div>
<div class="message">hello 6 </div>
<div class="message">hello 7</div>
<div class="message">hello 8</div>
<div class="message">hello 9</div>
<div class="message">hello 10</div>
<div class="message">hello 11</div>
<div class="message">hello 12</div>
<div class="message">hello 13</div>
<div class="message">hello 14</div>
<div class="message">hello 15</div>
<div class="message">hello 16</div>
<div class="message">hello 17</div>
<div class="message">hello 18</div>
<div class="message">hello 19</div>
<div class="message">hello 20</div>
<div class="message">hello 21</div>
<div class="message">hello 22</div>
<div class="message">hello 23</div>
<div class="message">hello 24</div>
<div class="message">hello 25</div>
<div class="message">hello 26</div>
<div class="message">hello 27</div>
<div class="message">hello 28</div>
<div class="message">hello 29</div>
<div class="message">hello 30</div>
<div class="message">hello 31</div>
<div class="message">hello 32</div>
<div class="message">hello 33</div>
<div class="message">hello 34</div>
<div class="message">hello 35</div>
<div class="message">hello 36</div>
<div class="message">hello 37</div>
<div class="message">hello 38</div>
<div class="message">hello 39</div>
</div>
<div class="send-message">
<input />
</div>
</div>
Respostas:
Finalmente encontrei uma solução que realmente funciona. Embora possa não ser o ideal, ele realmente funciona em todos os casos. Aqui está o código:
Algumas epifanias que tive ao longo do caminho:
Ao fechar o teclado virtual, um
scroll
evento ocorre instantaneamente antes doresize
evento. Isso parece acontecer apenas ao fechar o teclado, não ao abri-lo. Esse é o motivo pelo qual você não pode usar oscroll
evento para definirpxFromBottom
, porque se você estiver próximo à parte inferior, ele será definido como 0 noscroll
evento imediatamente antes doresize
evento, atrapalhando o cálculo.Outra razão pela qual todas as soluções tiveram dificuldade perto da parte inferior das mensagens div é um pouco difícil de entender. Por exemplo, na minha solução de redimensionamento, basta adicionar ou subtrair 250 (altura do teclado móvel) para
scrollTop
quando abrir ou fechar o teclado virtual. Isso funciona perfeitamente, exceto perto do fundo. Por quê? Porque digamos que você esteja a 50 pixels da parte inferior e feche o teclado. Subtrairá 250 descrollTop
(altura do teclado), mas subtrairá apenas 50! Portanto, ele sempre será redefinido para a posição fixa incorreta ao fechar o teclado próximo à parte inferior.Eu também acredito que você não pode usar
onFocus
eonBlur
eventos para esta solução, porque esses ocorrem apenas ao selecionar inicialmente a caixa de texto para abrir o teclado. Você é perfeitamente capaz de abrir e fechar o teclado móvel sem ativar esses eventos e, como tal, eles não podem ser usados aqui.Acredito que os pontos acima são importantes para o desenvolvimento de uma solução, porque no início não são óbvios, mas impedem o desenvolvimento de uma solução robusta.
Não gosto dessa solução (o intervalo é um pouco ineficiente e propenso a condições de corrida), mas não consigo encontrar nada melhor que sempre funcione.
fonte
Eu acho que o que você quer é
overflow-anchor
O suporte está aumentando, mas não total, ainda https://caniuse.com/#feat=css-overflow-anchor
De um artigo sobre CSS-Tricks:
Aqui está uma versão ligeiramente modificada de um de seus exemplos:
Abra isso no celular: https://cdpn.io/chasebank/debug/PowxdOR
O que está fazendo é basicamente desativar qualquer ancoragem padrão dos novos elementos da mensagem, com
#scroller * { overflow-anchor: none }
E, em vez disso, ancore um elemento vazio
#anchor { overflow-anchor: auto }
que sempre virá após essas novas mensagens, pois as novas mensagens serão inseridas antes dele.Tem que haver um pergaminho para notar uma mudança na ancoragem, o que eu acho que geralmente é um bom UX. Mas de qualquer maneira, a posição atual de rolagem deve ser mantida quando o teclado abrir.
fonte
Minha solução é igual à sua solução proposta com uma adição de verificação condicional. Aqui está uma descrição da minha solução:
scrollTop
e últimoclientHeight
de.messages
aoldScrollTop
eoldHeight
, respectivamente,oldScrollTop
eoldHeight
sempre queresize
acontecewindow
e atualizaroldScrollTop
toda vez quescroll
acontece.messages
window
é reduzido (quando o teclado virtual é exibido), a altura de.messages
retrai-se automaticamente. O comportamento pretendido é tornar o conteúdo mais baixo.messages
ainda visível, mesmo quando.messages
'height retrai. Isso exige que ajustemos manualmente a posiçãoscrollTop
de rolagem de.messages
.scrollTop
de.messages
ter certeza de que a parte mais inferior de.messages
antes de sua retração altura acontece ainda é visívelscrollTop
de.messages
ter certeza de que a parte mais inferior de.messages
restos a parte mais inferior da.messages
após a expansão de altura (a menos que a expansão não pode acontecer para cima, o que acontece quando você está quase no topo.messages
)O que causou o problema?
Meu pensamento lógico (possivelmente possivelmente defeituoso) é:
resize
acontece,.messages
a altura muda, a atualização.messages
scrollTop
acontece dentro do nossoresize
manipulador de eventos. No entanto, após.messages
a expansão da altura, umscroll
evento ocorre curiosamente antes de aresize
! E ainda mais curioso, oscroll
evento só acontece quando ocultamos o teclado quando rolamos acima doscrollTop
valor máximo de quando.messages
não é retraído. No meu caso, isso significa que, quando eu rolar abaixo270.334px
(o máximoscrollTop
antes.messages
é retraído) e ocultar o teclado, esse evento estranhoscroll
antes doresize
acontecimento acontece e rola o seu.messages
exatamente270.334px
. Obviamente, isso atrapalha nossa solução acima.Felizmente, podemos solucionar isso. Minha dedução pessoal de por que isso
scroll
antes doresize
evento acontecer é porque.messages
não é possível manter suascrollTop
posição acima270.334px
quando se expande em altura (é por isso que mencionei que meu pensamento lógico inicial é defeituoso; simplesmente porque não há como.messages
manter suascrollTop
posição acima de seu máximo valor) . Portanto, ele define seuscrollTop
valor imediatamente para o valor máximo que pode fornecer (que é, sem surpresa270.334px
).O que podemos fazer?
Como atualizamos apenas
oldHeight
no redimensionamento, podemos verificar se esse deslocamento forçado (ou mais corretamenteresize
) acontece e se ocorrer, não é atualizadooldScrollTop
(porque já lidamos com issoresize
!) Simplesmente precisamos compararoldHeight
e a altura atual emscroll
para ver se essa rolagem forçada acontece. Isso funciona porque a condição deoldHeight
não ser igual à altura atual ativadascroll
somente será verdadeira quandoresize
acontecer (o que coincidentemente ocorre quando a rolagem forçada acontece).Aqui está o código (no JSFiddle) abaixo:
Testado no Firefox e Chrome para celular e funciona nos dois navegadores.
fonte