'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:

Desktop

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:

Mobile

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.



Solution 1:[1]

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>

Solution 2:[2]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Felipe Saldanha
Solution 2 Graham