Estoy tratando de imitar otras aplicaciones de chat móviles donde, cuando seleccionas el send-message
cuadro 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_HEIGHT
pí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_HEIGHT
pí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,
scroll
se produce un evento instantáneamente antes delresize
evento. Esto parece suceder solo al cerrar el teclado, no al abrirlo. Esta es la razón por la que no puede usar elscroll
evento para establecerpxFromBottom
, porque si está cerca de la parte inferior, se establecerá en 0 en elscroll
evento justo antes delresize
evento, 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)
scrollTop
al 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
onFocus
yonBlur
eventos 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-anchor
El 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:
scrollTop
y últimoclientHeight
de.messages
aoldScrollTop
yoldHeight
respectivamenteoldScrollTop
yoldHeight
cada vez queresize
sucede unwindow
y actualizaroldScrollTop
cada vez quescroll
ocurre un.messages
window
se reduce (cuando se muestra el teclado virtual), la altura de.messages
se retraerá automáticamente. El comportamiento previsto es hacer que el contenido más inferior de.messages
todavía sea visible incluso cuando.messages
la altura se retraiga. Esto requiere que ajustemos manualmente la posiciónscrollTop
de desplazamiento de.messages
.scrollTop
de.messages
asegurarse de que la parte más inferior de.messages
antes de que ocurra su retracción altura es aún visiblescrollTop
de.messages
para asegurarse de que la parte más inferior de los.messages
restos de la parte más inferior de.messages
despué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:
resize
sucede,.messages
'la altura cambia, la actualización.messages
scrollTop
ocurre dentro de nuestroresize
controlador de eventos. Sin embargo, en.messages
la expansión de altura,scroll
curiosamente un evento ocurre antes de aresize
! Y aún más curioso, elscroll
evento solo ocurre cuando ocultamos el teclado cuando nos hemos desplazado por encima delscrollTop
valor máximo de cuando.messages
no está retraído. En mi caso, esto significa que cuando me desplazo hacia abajo270.334px
(el máximoscrollTop
antes.messages
se retrae) y oculto el teclado, ese extraño eventoscroll
antes deresize
que suceda y lo desplaza.messages
exactamente270.334px
. Obviamente, esto arruina nuestra solución anterior.Afortunadamente, podemos solucionar esto. Mi deducción personal de por qué esto
scroll
antes delresize
evento ocurre porque.messages
no puede mantener suscrollTop
posición de arriba270.334px
cuando se expande en altura (es por eso que mencioné que mi pensamiento lógico inicial es defectuoso; simplemente porque no hay forma de.messages
mantener suscrollTop
posición por encima de su máximo valor) . Por lo tanto, establece inmediatamente suscrollTop
valor máximo que puede dar (que es, como era de esperar,270.334px
).¿Qué podemos hacer?
Debido a que solo actualizamos
oldHeight
en 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 compararoldHeight
y la altura actual enscroll
para ver si ocurre este desplazamiento forzado. Esto funciona porque la condición deoldHeight
no ser igual a la altura actualscroll
solo será verdadera cuandoresize
suceda (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