Estoy tratando de imitar otras aplicaciones de chat móviles donde, cuando seleccionas el send-messagecuadro de texto y se abre el teclado virtual, el mensaje más inferior todavía está a la vista. Parece que no hay una manera de hacer esto con CSS de manera sorprendente, por lo que JavaScript resize(única forma de averiguar cuándo se abre y cierra el teclado aparentemente) eventos y desplazamiento manual al rescate.
Alguien proporcionó esta solución y descubrí esta solución , que parecen funcionar.
Excepto en un caso. Por alguna razón, si está dentro de MOBILE_KEYBOARD_HEIGHT(250 píxeles en mi caso) píxeles de la parte inferior de la división de mensajes, cuando cierra el teclado del móvil, sucede algo extraño. Con la solución anterior, se desplaza hacia abajo. Y con la última solución, en su lugar, se desplaza hacia arriba MOBILE_KEYBOARD_HEIGHTpíxeles desde la parte inferior.
Si se desplaza por encima de esta altura, ambas soluciones proporcionadas anteriormente funcionan perfectamente. Es solo cuando estás cerca del fondo que tienen este problema menor.
Pensé que tal vez era solo mi programa lo que causaba esto con un código extraño, pero no, incluso reproduje un violín y tiene este problema exacto. Mis disculpas por hacer que esto sea tan difícil de depurar, pero si vas a https://jsfiddle.net/t596hy8d/6/show (el sufijo show proporciona un modo de pantalla completa) en tu teléfono, deberías poder ver el mismo comportamiento
Ese comportamiento es, si se desplaza hacia arriba lo suficiente, abrir y cerrar el teclado mantiene la posición. Sin embargo, si cierra el teclado dentro de los MOBILE_KEYBOARD_HEIGHTpíxeles de la parte inferior, encontrará que se desplaza hacia la parte inferior.
¿Qué está causando esto?
Reproducción de código aquí:
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>

Respuestas:
Finalmente encontré una solución que realmente funciona. Aunque puede no ser ideal, en realidad funciona en todos los casos. Aquí está el código:
Algunas epifanías que tuve en el camino:
Al cerrar el teclado virtual,
scrollse produce un evento instantáneamente antes delresizeevento. Esto parece suceder solo al cerrar el teclado, no al abrirlo. Esta es la razón por la que no puede usar elscrollevento para establecerpxFromBottom, porque si está cerca de la parte inferior, se establecerá en 0 en elscrollevento justo antes delresizeevento, lo que desordenará el cálculo.Otra razón por la cual todas las soluciones tuvieron dificultades cerca de la parte inferior del div de mensajes es un poco difícil de entender. Por ejemplo, en mi solución de cambio de tamaño solo agrego o resta 250 (altura del teclado móvil)
scrollTopal abrir o cerrar el teclado virtual. Esto funciona perfectamente, excepto cerca del fondo. ¿Por qué? Porque digamos que estás a 50 píxeles de la parte inferior y cierras el teclado. Restará 250 descrollTop(la altura del teclado), ¡pero solo debería restar 50! Por lo tanto, siempre se restablecerá a la posición fija incorrecta al cerrar el teclado cerca de la parte inferior.También creo que no puede usar
onFocusyonBlureventos para esta solución, porque solo ocurren cuando se selecciona inicialmente el cuadro de texto para abrir el teclado. Es perfectamente capaz de abrir y cerrar el teclado móvil sin activar estos eventos, y como tal, no se pueden usar aquí.Creo que los puntos anteriores son importantes para desarrollar una solución, porque al principio no son obvios, pero impiden que se desarrolle una solución sólida.
No me gusta esta solución (el intervalo es un poco ineficiente y propenso a las condiciones de carrera), pero no puedo encontrar nada mejor que siempre funcione.
fuente
Creo que lo que quieres es
overflow-anchorEl soporte está aumentando, pero no es total, pero https://caniuse.com/#feat=css-overflow-anchor
De un artículo de CSS-Tricks sobre él:
Aquí hay una versión ligeramente modificada de uno de sus ejemplos:
Abra esto en el móvil: https://cdpn.io/chasebank/debug/PowxdOR
Lo que está haciendo es básicamente deshabilitar cualquier anclaje predeterminado de los nuevos elementos del mensaje, con
#scroller * { overflow-anchor: none }Y en cambio, anclar un elemento vacío
#anchor { overflow-anchor: auto }que siempre vendrá después de esos nuevos mensajes, ya que los nuevos mensajes se están insertando antes .Tiene que haber un desplazamiento para notar un cambio en el anclaje, lo que creo que generalmente es una buena experiencia de usuario. Pero de cualquier manera, la posición de desplazamiento actual debe mantenerse cuando se abre el teclado.
fuente
Mi solución es la misma que su solución propuesta con una adición de verificación condicional. Aquí hay una descripción de mi solución:
scrollTopy últimoclientHeightde.messagesaoldScrollTopyoldHeightrespectivamenteoldScrollTopyoldHeightcada vez queresizesucede unwindowy actualizaroldScrollTopcada vez quescrollocurre un.messageswindowse reduce (cuando se muestra el teclado virtual), la altura de.messagesse retraerá automáticamente. El comportamiento previsto es hacer que el contenido más inferior de.messagestodavía sea visible incluso cuando.messagesla altura se retraiga. Esto requiere que ajustemos manualmente la posiciónscrollTopde desplazamiento de.messages.scrollTopde.messagesasegurarse de que la parte más inferior de.messagesantes de que ocurra su retracción altura es aún visiblescrollTopde.messagespara asegurarse de que la parte más inferior de los.messagesrestos de la parte más inferior de.messagesdespués de la expansión de altura (a menos que la expansión no puede pasar hacia arriba, lo que pasa cuando estás casi en la parte superior de.messages)¿Qué causó el problema?
Mi pensamiento lógico (inicial posiblemente defectuoso) es:
resizesucede,.messages'la altura cambia, la actualización.messagesscrollTopocurre dentro de nuestroresizecontrolador de eventos. Sin embargo, en.messagesla expansión de altura,scrollcuriosamente un evento ocurre antes de aresize! Y aún más curioso, elscrollevento solo ocurre cuando ocultamos el teclado cuando nos hemos desplazado por encima delscrollTopvalor máximo de cuando.messagesno está retraído. En mi caso, esto significa que cuando me desplazo hacia abajo270.334px(el máximoscrollTopantes.messagesse retrae) y oculto el teclado, ese extraño eventoscrollantes deresizeque suceda y lo desplaza.messagesexactamente270.334px. Obviamente, esto arruina nuestra solución anterior.Afortunadamente, podemos solucionar esto. Mi deducción personal de por qué esto
scrollantes delresizeevento ocurre porque.messagesno puede mantener suscrollTopposición de arriba270.334pxcuando se expande en altura (es por eso que mencioné que mi pensamiento lógico inicial es defectuoso; simplemente porque no hay forma de.messagesmantener suscrollTopposición por encima de su máximo valor) . Por lo tanto, establece inmediatamente suscrollTopvalor máximo que puede dar (que es, como era de esperar,270.334px).¿Qué podemos hacer?
Debido a que solo actualizamos
oldHeighten el cambio de tamaño, podemos verificar si este desplazamiento forzado (o más correctamenteresize) ocurre y, si es así, no actualiceoldScrollTop(¡porque ya lo hemos manejadoresize!) Simplemente necesitamos compararoldHeighty la altura actual enscrollpara ver si ocurre este desplazamiento forzado. Esto funciona porque la condición deoldHeightno ser igual a la altura actualscrollsolo será verdadera cuandoresizesuceda (que es coincidencia cuando ocurre ese desplazamiento forzado).Aquí está el código (en JSFiddle) a continuación:
Probado en Firefox y Chrome para dispositivos móviles y funciona para ambos navegadores.
fuente