J'essaie d'imiter d'autres applications de chat mobile où lorsque vous sélectionnez la send-message
zone de texte et qu'il ouvre le clavier virtuel, le message le plus bas est toujours en vue. Il ne semble pas y avoir un moyen incroyable de le faire avec CSS, donc JavaScript resize
(seul moyen de savoir quand le clavier est ouvert et fermé apparemment) et le défilement manuel à la rescousse.
Quelqu'un a fourni cette solution et j'ai découvert cette solution , qui semblent fonctionner toutes les deux.
Sauf dans un cas. Pour une raison quelconque, si vous êtes à moins de MOBILE_KEYBOARD_HEIGHT
(250 pixels dans mon cas) pixels du bas de la div des messages, lorsque vous fermez le clavier mobile, quelque chose d'étrange se produit. Avec l'ancienne solution, il défile vers le bas. Et avec cette dernière solution, il fait défiler les MOBILE_KEYBOARD_HEIGHT
pixels à partir du bas.
Si vous faites défiler au-dessus de cette hauteur, les deux solutions fournies ci-dessus fonctionnent parfaitement. Ce n'est que lorsque vous êtes près du fond qu'ils ont ce problème mineur.
Je pensais que c'était peut-être juste mon programme qui causait cela avec du code errant étrange, mais non, j'ai même reproduit un violon et il a ce problème exact. Mes excuses pour avoir rendu cela si difficile à déboguer, mais si vous allez sur https://jsfiddle.net/t596hy8d/6/show (le suffixe d'émission fournit un mode plein écran) sur votre téléphone, vous devriez pouvoir voir le même comportement.
Ce comportement étant, si vous faites défiler suffisamment vers le haut, l'ouverture et la fermeture du clavier maintient la position. Cependant, si vous fermez le clavier à quelques MOBILE_KEYBOARD_HEIGHT
pixels du bas, vous constaterez qu'il défile vers le bas à la place.
Quelle est la cause de cela?
Reproduction du code ici:
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>
Réponses:
J'ai enfin trouvé une solution qui fonctionne réellement . Bien qu'il ne soit pas idéal, il fonctionne en fait dans tous les cas. Voici le code:
Quelques épiphanies que j'ai eues en cours de route:
Lors de la fermeture du clavier virtuel, un
scroll
événement se produit instantanément avant l'resize
événement. Cela semble se produire uniquement lors de la fermeture du clavier, pas de son ouverture. C'est la raison pour laquelle vous ne pouvez pas utiliser l'scroll
événement pour définirpxFromBottom
, car si vous êtes près du bas, il se mettra à 0 dans l'scroll
événement juste avant l'resize
événement, gâchant le calcul.Une autre raison pour laquelle toutes les solutions ont rencontré des difficultés près du bas des messages div est un peu difficile à comprendre. Par exemple, dans ma solution de redimensionnement, j'ajoute ou soustrais simplement 250 (hauteur du clavier mobile)
scrollTop
lors de l'ouverture ou de la fermeture du clavier virtuel. Cela fonctionne parfaitement sauf près du bas. Pourquoi? Parce que disons que vous êtes à 50 pixels du bas et fermez le clavier. Il soustrait 250 descrollTop
(la hauteur du clavier), mais il ne devrait soustraire que 50! Ainsi, il sera toujours réinitialisé à la mauvaise position fixe lors de la fermeture du clavier vers le bas.Je crois également que vous ne pouvez pas utiliser
onFocus
et lesonBlur
événements pour cette solution, car ceux-ci ne se produisent que lors de la sélection initiale de la zone de texte pour ouvrir le clavier. Vous pouvez parfaitement ouvrir et fermer le clavier mobile sans activer ces événements, et en tant que tels, ils ne peuvent pas être utilisés ici.Je crois que les points ci-dessus sont importants pour développer une solution, car ils ne sont pas évidents au début, mais empêchent le développement d'une solution robuste.
Je n'aime pas cette solution (l'intervalle est un peu inefficace et sujet aux conditions de course), mais je ne trouve rien de mieux qui fonctionne toujours.
la source
Je pense que ce que tu veux c'est
overflow-anchor
Le support est en augmentation, mais pas total, pourtant https://caniuse.com/#feat=css-overflow-anchor
À partir d'un article CSS-Tricks à ce sujet:
Voici une version légèrement modifiée d'un de leurs exemples:
Ouvrez ceci sur mobile: https://cdpn.io/chasebank/debug/PowxdOR
Cela revient à désactiver fondamentalement tout ancrage par défaut des nouveaux éléments de message, avec
#scroller * { overflow-anchor: none }
Et à la place, ancrer un élément vide
#anchor { overflow-anchor: auto }
qui viendra toujours après ces nouveaux messages, car les nouveaux messages sont insérés avant lui.Il doit y avoir un défilement pour remarquer un changement d'ancrage, ce qui est généralement une bonne UX. Mais de toute façon, la position de défilement actuelle doit être maintenue lorsque le clavier s'ouvre.
la source
Ma solution est la même que votre solution proposée avec un ajout de vérification conditionnelle. Voici une description de ma solution:
scrollTop
et dernièreclientHeight
de.messages
àoldScrollTop
etoldHeight
respectivementoldScrollTop
etoldHeight
chaque fois qu'unresize
se produitwindow
et mettre à jouroldScrollTop
chaque fois qu'unscroll
se produit.messages
window
est rétréci (lorsque le clavier virtuel s'affiche), la hauteur de.messages
se rétracte automatiquement. Le comportement prévu est de rendre le contenu le plus bas.messages
encore visible même lorsque.messages
la hauteur se rétracte. Cela nous oblige à ajuster manuellement la positionscrollTop
de défilement de.messages
.scrollTop
de.messages
faire en sorte que la partie de plus basse.messages
avant sa rétraction de la hauteur se produit est encore visiblescrollTop
de.messages
pour vous assurer que la partie de plus basse.messages
reste la partie de plus basse.messages
après l' expansion de la hauteur ( à moins que l' expansion ne peut se faire vers le haut, ce qui arrive quand vous êtes presque au sommet de.messages
)Qu'est-ce qui a causé le problème?
Ma pensée logique (initiale peut-être imparfaite) est:
resize
se produit,.messages
'les changements de hauteur, la mise à jour.messages
scrollTop
se produit à l'intérieur de notreresize
gestionnaire d'événements. Cependant, lors.messages
de l'expansion en hauteur, unscroll
événement se produit curieusement avant unresize
! Et encore plus curieux, l'scroll
événement ne se produit que lorsque nous masquons le clavier lorsque nous avons fait défiler au-dessus de lascrollTop
valeur maximale de quand.messages
n'est pas rétracté. Dans mon cas, cela signifie que lorsque je défile en dessous270.334px
(le maximumscrollTop
avant.messages
est rétracté) et que je masque le clavier, cet événement étrangescroll
avantresize
se produit et vous fait défiler.messages
exactement270.334px
. Cela perturbe évidemment notre solution ci-dessus.Heureusement, nous pouvons contourner ce problème. Ma déduction personnelle de la raison pour laquelle cela
scroll
avant que l'resize
événement se produise est parce que.messages
ne peut pas maintenir sascrollTop
position au-dessus270.334px
quand il se dilate en hauteur (c'est pourquoi j'ai mentionné que ma pensée logique initiale est défectueuse; simplement parce qu'il n'y a aucun moyen.messages
de maintenir sascrollTop
position au-dessus de son maximum valeur) . Par conséquent, il définit immédiatement sonscrollTop
à la valeur maximale qu'il peut donner (ce qui n'est pas surprenant270.334px
).Que pouvons-nous faire?
Parce que nous ne mettons à jour que
oldHeight
lors du redimensionnement, nous pouvons vérifier si ce défilement forcé (ou plus correctement,resize
) se produit et s'il le fait, ne pas mettre à jouroldScrollTop
(car nous l'avons déjà géréresize
!) Nous avons simplement besoin de compareroldHeight
et la hauteur actuelle surscroll
pour voir si ce défilement forcé se produit. Cela fonctionne parce que la condition deoldHeight
ne pas être égale à la hauteur actuellescroll
ne sera vraie que lorsqueresize
cela se produit (ce qui est une coïncidence lorsque ce défilement forcé se produit).Voici le code (dans JSFiddle) ci-dessous:
Testé sur Firefox et Chrome pour mobile et il fonctionne pour les deux navigateurs.
la source