Let’s talk about how to cleverly deal with iOS keyboard problems?

Let’s talk about how to cleverly deal with iOS keyboard problems?

Preface

Students who have written mobile applications have more or less encountered various problems caused by soft keyboards. The most typical ones are that the input box is blocked by the soft keyboard, fixed elements are invalid, and the performance of these problems on iOS is unacceptable.

Differences in webview

On the mobile side, our H5 page usually runs in the webview provided by the host APP. To put it simply, you can actually think of it as a browser, which is used to display the page content. Currently, the mainstream mobile systems are divided into Android and iOS, but there are many differences in the webview containers provided by the two. Today we will only discuss the impact of the soft keyboards of the two.

First, let's write a simple page layout: fixed header + adaptive middle + fixed bottom

picture

Android

In fact, Android's performance is not a big problem. It just reduces the height of the webview after the keyboard pops up, becoming: the original webview height minus the keyboard height

picture

This performance is exactly what we expect, and it does not affect the layout of the entire page at all

iOS

Soft Keyboard

After iOS 8.2, Safari, the only designated browser kernel for iOS and the originator of Webkit, changed the layout base area of ​​fixed elements from the visible area above the keyboard to the entire window behind the keyboard. That is to say, the height of the webview will not change at this time, and the keyboard is directly covered on top of the webview.

This is to avoid re-rendering the page after the keyboard pops up. It is convenient for them, but it is us front-end developers who suffer...

For example, on the page above, let’s see how iOS performs:

picture

It can be seen that in order to prevent the webview from being compressed and the soft keyboard from blocking the input box, iOS cleverly moves the webview upward as a whole, with the maximum moving distance being the height of the soft keyboard.

This causes our header and the upper part of the page to move outside the visible area, which is unacceptable, as at least the header should be in the visible area. (This will make us mistakenly think that fixed is invalid, but in fact its position relative to the webview has not changed, it is just that the webview has moved)

This movement seems to be illogical. If you don't believe it, you can try to put the input box in various positions on the page. I found that only when the input box is at the top, the webview will not move up, and other positions will move more or less.

Another problem is that the webview can slide at this time, so some users will slide the input box below the keyboard. I think this experience is unacceptable...

picture

And you will find that there is a white background area above and below the page that cannot be reached by Viewport or VisualViewport. We can try to change the background of all elements on the page to black and then look at it, which will be more obvious.

picture

What do you think when you see these strange questions?

The root cause of all the problems is that in order to avoid re-rendering the page after the keyboard pops up, iOS does not compress the height of the webview container, but translates the webview as a whole.

Soft keyboard monitoring

For Android, we can usually achieve this by listening to the resize event, but for iOS, we know from the above that when the keyboard pops up, the height of the iOS webview does not change, so the resize event cannot be triggered.

In iOS, you can monitor focusin & focusout events

 export const watchKeyBoard = (callback: (isShow: boolean) => void) => { // IOS if (isIOSByUA()) { document.body.addEventListener('focusin', () => { //软键盘弹出的事件处理callback(true) }) document.body.addEventListener('focusout', () => { //软键盘收起的事件处理callback(false) }) } else { // Android const originalHeight = document.documentElement.clientHeight || document.body.clientHeight window.addEventListener('resize', () => { const resizeHeight = document.documentElement.clientHeight || document.body.clientHeight if (resizeHeight - 0 < originalHeight - 0) { // 键盘弹起事件callback(true) } else { // 键盘收起事件callback(false) } }) } }

Solution

After understanding the cause of the problem, we can try to solve it. However, if we want to solve this problem purely on the front end, there will be some experience problems. Maybe you can encourage your client classmates to help solve this problem. As long as the performance of iOS webview when the keyboard pops up is consistent with Android, there will be no strange problems. But it seems that they are also very difficult to deal with...

Imitating Android's processing

Although we cannot change the height of the webview, we can change the height of our layout. We only need to change the page height to the height of the visible area of ​​the page. If the page content has scrolling interaction, additional processing is required to isolate it from the scrolling of the webview.

VisualViewport

Let's first understand this API, which can be used to obtain the visual viewport of the corresponding window

  • VisualViewport.offsetLeft : Returns the CSS pixel distance from the left border of the visual viewport to the left border of the layout viewport.
  • VisualViewport.offsetTop: Returns the CSS pixel distance from the top border of the visual viewport to the top border of the layout viewport.
  • VisualViewport.pageLeft: Returns the number of CSS pixels corresponding to the X-axis coordinate relative to the initial viewport property.
  • VisualViewport.pageTop: Returns the number of CSS pixels corresponding to the Y-axis coordinate relative to the initial viewport property.
  • VisualViewport.width: Returns the width of the visual viewport in CSS pixels.
  • VisualViewport.height: Returns the height of the visual viewport in CSS pixels.
  • VisualViewport.scale: Returns the scale applied to the current visual viewport.

What we need here is this VisualViewport.height, which is used to get the height of the visible area.

But it should be noted that this API only supports iOS13 at the minimum. For iOS13 and below, use window.innerHeight as a fallback.

Page Layout

The overall layout uses flex layout, so the head and bottom do not need to be fixed to position, and the middle is adaptive to fill the remaining height, and the super long scroll

picture

Calculate height and re-layout when keyboard is opened

We need to calculate the height of the visible area after the keyboard pops up, and assign the height of the outermost container to the height of the visible area.

 watchKeyBoard((status) => { setTimeout(() => { console.log( 'status', status ? '键盘打开' : '键盘关闭', ) const container = document.getElementById('container') if (status) { container.style.height = `${ window.visualViewport.height || window.innerHeight }px` window.scrollTo(0, 0) } else { container.style.height = `100vh` document.removeEventListener('touchmove', this.stopMove) } }, 100) })

This page display is normal.

But then comes the scrolling problem 😓

picture

Handling scrolling

We need to disable global scrolling, but enable some areas that need scrolling, such as the list in the middle.

 if (utils.isIOSByUA()) { watchKeyBoard((status) => { setTimeout(() => { console.log( 'status', status ? '键盘打开' : '键盘关闭', window.innerHeight, ) const container = document.getElementById('container') if (status) { container.style.height = `${ window.visualViewport.height || window.innerHeight }px` window.scrollTo(0, 0) document.addEventListener('touchmove', this.stopMove, { passive: false, }) document.addEventListener('touchend', this.scroll) } else { container.style.height = `100vh` document.removeEventListener('touchmove', this.stopMove) document.removeEventListener('touchend', this.scroll) } }, 100) }) }
 stopMove(e) { // 排除可以滚动的区域if (['content', 'keyboard_center'].includes(e.target?.className)) return e.preventDefault() } scroll() { window.scrollTo(0, 0) }

The complete experience is as follows

picture

Compared with the original experience of occlusion, scrolling, fixed failure, etc., the current experience is acceptable (all operations here only need to be performed on iOS)

<<:  Exploring the Android system: dumpsys command to obtain detailed information about system services

>>:  Key APIs and techniques for adjusting Android brightness to achieve personalized APP brightness settings

Recommend

Avocado is a calorie bomb, so why do so many people eat it to lose weight?

There is a kind of fruit whose reputation is very...

How to boost your immunity? Eat like a rainbow!

Written by: Li Caihong, Chief Nutrition Technicia...

Stunning and stunning, how are these atmospheric wonders formed?

Produced by: Science Popularization China Author:...

After watching ofo’s new ad, I threw away the client’s brief!

Recently, ofo, the shared yellow bike company, re...

Jing Wei: "Short Video Camera Lighting Practical Teaching"

Jing Wei's "Short Video Camera Lighting ...

The most noteworthy tips on using Apple ID password

What is an Apple ID? You can use it to change you...

What are the factors that affect keyword quality in SEM bidding?

When it comes to the factors that can affect keyw...

News app finally appears in iOS 9 Beta 3 hands-on testing

Apple released its next-generation operating syst...

Introduction to Baidu Promotion Brand Zone Information Flow Advertising!

What is a Brand Zone? Baidu Brand Zone is an info...

Awesome! This asteroid is named after a Taizhou person

Recently, the Small Body Nomenclature Working Gro...

Evaluation of the effectiveness of Xiaohongshu advertising!

What do you think about whether Xiaohongshu adver...