简体   繁体   中英

Mobile safari - on screen keyboard hiding some elements

I'm building a very simple POC for an HTML chat app, using flexbox. The idea is to have chat messages which start at the bottom of the message window. Typical stuff.

I did this by using a nested flexbox with an inner div set to flex-direction: column and an outer div set to flex-direction: column-reverse and overflow-y: auto:

<div class="outer">
    <div id="messages" class="inner">
        <div class="message">hello</div>
    </div>
</div>
.outer {
    flex-grow: 1;
    display: flex;
    flex-direction: column-reverse;
    overflow-y: auto;
}

.inner {
    display: flex;
    flex-direction: column;
}

It works well for desktop browsers:

桌面

It also works fine on iOS safari up to a certain number of messages. However at some point the new messages get hidden behind the onscreen keyboard and the only way to see them is either to manually scroll down or close the on screen keyboard. Note: opening the keyboard again will no longer hide the messages, closing the keyboard seems to reset the scroll.

Opening Safari dev tools reveals something interesting. When selecting an html element Safari thinks it is where it should be, in fact the preceding element is shown:

移动的

Notice how I selected the last element "Two" but dev tools highlights message "One"

Something else I noticed. Changing the outer div's overflow-y to hidden solves the problem, but obviously I can no longer scroll through the messages.

I'm guessing the issue is related to having two sets of scroll bars, one for the div and one for the page itself which is shifted by the keyboard.

Does anyone know why this is happening and how to prevent it?

I've created a fiddle and also hosted the page on S3

On my phone, adding around 12/13 messages is enough for them to start "hiding" behind the keyboard.

This iOS behavior may be desired since it prevents sudden jumps when opening the keyboard, but if you want to override that, try adding to your code:

const outer = document.querySelector(".outer")

const scrollToBottom = () => {
  outer.scrollTop = 0
}

input.addEventListener("focus", scrollToBottom)

 const messages = document.getElementById("messages"); const input = document.getElementById("messageInput"); const target = document.getElementById("send"); const template = document.getElementById("messageTemplate"); const pushMessage = () => { const clone = template.content.cloneNode(true); clone.querySelector("div.message").innerText = input.value; messages.appendChild(clone); } const keyUpHandler = (e) => { if (e.key === 'Enter') pushMessage() } target.addEventListener("click", pushMessage) input.addEventListener("keyup", keyUpHandler) const outer = document.querySelector('.outer'); const scrollToBottom = () => { outer.scrollTop = 0; } input.addEventListener("focus", scrollToBottom)
 .page { position: fixed; top: 0; left: 0; width: 100%; display: flex; flex-direction: column; height: 100%; } .header, .footer { padding: 1rem; background-color: lightgrey; display: flex; flex-direction: row; } .outer { flex-grow: 1; display: flex; flex-direction: column-reverse; overflow-y: auto; /*overflow-y: hidden;*/ } .inner { display: flex; flex-direction: column; } .message { padding: 1rem; }
 <div class="page"> <div class="header"> header </div> <div class="outer"> <div id="messages" class="inner"> <div class="message">hello</div> </div> </div> <div class="footer"> <input id="messageInput" type="text" placeholder="Enter your message" /> <button id="send">send</button> </div> </div> <template id="messageTemplate"> <div class="message"></div> </template>

What I usually do is create a div surrounding the entire page. This element get's an height of 100vh and a width of 100vw and a position relative.

This way you have one scrollable element which on focus you can scroll to the bottom.

Also if you for example try to stick (position absolute) something to the bottom of the page it will actually be above the keyboard.

I had this exact issue (building a chat UI too) and managed to finally figure out a solution

After playing around in the Safari console, I realised that if I set scrollTop to 0 it would correctly jump me to the bottom of the thread, but if I then sent another message, that message would hide behind the input.

What was interesting though, is that if i set it to a similar value (like -1 ), it would jump me to the bottom again.

With this insight, I tried to combine it, and found a solution that works!

This is what I ended up with:

 scrollNewMessage() {
  const scrollTop = this.$refs["message-thread-wrapper"].scrollTop

  // iOS Safari refuses to update scrollTop if the given value is the same as its
  // current value. That would be fine, except that the current value is _incorrect_
  // when the keyboard is open
  // This was resulting in messages hiding behind the message compose box
  // Ensuring the value is always different fixes it
  this.$refs["message-thread-wrapper"].scrollTop = scrollTop === 0 ? -1 : 0
},

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM