viewport-fit=cover を設定した時のソフトウェアキーボード対応について

画面のフッター部分に入力エリアがある場合、ソフトウェアキーボードが表示されると入力エリアが隠れてしまい、入力することができなくなる場合があります。

そのような場合は、ソフトウェアキーボードが表示される際に、JavaScript からソフトウェアキーボード分、画面の高さを変更する処理を行う必要があります。


#viewport-fit=cover の影響

iPhone X が登場し、iOS 11 からセーフエリアが導入されました。

Monaca で iPhone X の画面に対応する場合は、viewport 設定に `viewport-fit=cover` を追加する必要があります。(参考：[iPhoneXのセーフ エリア対応について](https://qiita.com/keeey/items/c175bd8ef12ee65ac3fc))

viewport 設定に viewport-fit=cover を追加した場合、コンテンツ内容を画面全体に表示するため、JavaScript から画面の高さ ( height ) を変更しても設定が反映されない場合があります。

viewport 設定に viewport-fit=cover を追加した場合は、画面の高さを変更するのではなく、フッター要素の `margin-bottom` にソフトウェアキーボード分の高さを設定することで対応することができます。


# ソフトウェアキーボードの高さを取得する

フッター要素の `margin-bottom` にソフトウェアキーボードの高さを設定するためには、初めにソフトウェアキーボードの高さを取得する必要があります。

JavaScript からは、直接ソフトウェアキーボードの高さを取得することができないため、今回は、[cordova-plugin-ionic-keyboard](https://github.com/ionic-team/cordova-plugin-ionic-keyboard) プラグイン ( v2.1.3 ) を利用します。

注意点として、サードパーティー製 Cordova プラグインを利用する場合は、`Proプラン` 以上が必要になります。また、cordova-plugin-ionic-keyboard プラグインは、カスタムビルドデバッガーでは正常に動作しないため、デバッグビルドをして実機で確認する必要があります。

# cordova-plugin-ionic-keyboard プラグインのイベント

cordova-plugin-ionic-keyboard プラグインでは、下記のイベントが用意されています。

| イベント名 | 説明|
|:-----------|:------------|
| keyboardWillShow | キーボードが表示される前に実行されます。|
| keyboardWillHide | キーボードが閉じる前に実行されます。|
| keyboardDidShow  | キーボードが完全に表示された時に実行されます。|
| keyboardDidHide  | キーボードが完全に閉じた時に実行されます。|


ソフトウェアキーボードの高さは、`keyboardWillShow` や `keyboardDidShow` イベントから `event.keyboardHeight` で取得することができます。

```javascript:例
window.addEventListener('keyboardWillShow', (event) => {
  console.log(event.keyboardHeight);
});
```

`keyboardWillShow` でフッター要素の `margin-bottom` にソフトウェアキーボードの高さを設定し、`keyboardWillHide` でフッター要素の `margin-bottom` を `0px` に戻すことで対応することができます。


# サンプル

Monaca で提供している `Onsen UI V2 JS Tabbar` テンプレートをベースにサンプルを作成してみました。

サンプルでのフッター要素は、`ons-tabbar` になります。

`ons-tabbar` に `id` として `tabbar1` を設定し、JavaScript から tabbar1 に対し、マージン設定を行なっています。

```html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">
  <script src="components/loader.js"></script>
  <script src="lib/onsenui/js/onsenui.min.js"></script>

  <link rel="stylesheet" href="components/loader.css">
  <link rel="stylesheet" href="lib/onsenui/css/onsenui.css">
  <link rel="stylesheet" href="lib/onsenui/css/onsen-css-components.css">
  <link rel="stylesheet" href="css/style.css">

  <script>
    ons.ready(function() {
      console.log("Onsen UI is ready!");

      // iPhoneX対応
      if (ons.platform.isIPhoneX()) {
        document.documentElement.setAttribute('onsflag-iphonex-portrait', '');
        document.documentElement.setAttribute('onsflag-iphonex-landscape', '');
      }
    });

    document.addEventListener('show', function(event) {
      var page = event.target;
      var titleElement = document.querySelector('#toolbar-title');

      if (page.matches('#page1')) {
        titleElement.innerHTML = 'My app - Page 1';

        //ソフトウェアキーボード表示前イベント処理
        window.addEventListener('keyboardWillShow', (event) => {
          // マージンを追加。
          if (ons.platform.isIOS) {
            document.getElementById("tabbar1").style.marginBottom = event.keyboardHeight + "px";
          }
        });
    
        //ソフトウェアキーボード非表示前イベント処理
        window.addEventListener('keyboardWillHide', () => {
          // マージンを削除。
          if (ons.platform.isIOS) {
            document.getElementById("tabbar1").style.marginBottom = "0px";
          }
        });
      } else if (page.matches('#page2')) {
        titleElement.innerHTML = 'My app - Page 2';
      }
    });
  </script>

    <style>
      html {
        height: 100%;
      }

      #page1 {
        margin: 0;
        padding: 0;
        min-height: 100%;
        height: auto !important;
        height: 100%;
        position: relative;
      }

      .page_bottom {
        position: absolute;
        width: 100%;
        bottom: 0;
        text-align: center;
        overflow: hidden;
      }

      textarea {
        width: 80%;
        height: 8em;
      }
    </style>
</head>
<body>
  <ons-page>
    <ons-toolbar>
      <div class="center" id="toolbar-title"></div>
    </ons-toolbar>
    <ons-tabbar id="tabbar1" position="bottom">
      <ons-tab label="Tab 1" page="tab1.html" active>
      </ons-tab>
      <ons-tab label="Tab 2" page="tab2.html">
      </ons-tab>
    </ons-tabbar>
  </ons-page>

  <ons-template id="tab1.html">
    <ons-page id="page1">
      <div class="content">
        <p style="text-align: center;">
          This is the first page.

          <br /><br />
          <textarea  id="textarea1"></textarea>

          <br /><br />
          <textarea  id="textarea2"></textarea>

          <br /><br />
          <textarea  id="textarea3"></textarea>
        </p>

        <div class="page_bottom">
          <textarea  id="textarea4"></textarea>
        </div>
      </div>
    </ons-page>
  </ons-template>

  <ons-template id="tab2.html">
    <ons-page id="page2">
      <p style="text-align: center;">
        This is the second page.
      </p>
    </ons-page>
  </ons-template>
</body>
</html>
```


# おわりに
viewport 設定に `viewport-fit=cover` を追加している場合に、画面の高さに対しソフトウェアキーボード分の高さの設定が反映されない場合は、フッター要素に対してマージン設定を試してみてください。