みかづきブログ・カスタム

基本的にはちょちょいのほいです。

スマートフォンのキーボードが表示された際にコンテンツが隠れないようにハンドリングする ⌨️

YouTube

「ブラウザ版のYouTubeでもコメントを打ちながら動画を見たい」

そんな体験を実現するための実装方法を検討してみました。

ことの発端

YouTubeに投稿された動画https://youtu.be/1g0ebPju_eE )をYouTubeアプリで観たり、スマホブラウザで観たりしてみてください。

YouTube

左がアプリ、右がブラウザです。

アプリ版では動画をみながらコメントを打つことができますが、ブラウザ版ではコメントを打とうとしてキーボードを表示すると、動画が隠れてしまいます。
これはinput要素などをタップしてキーボードが表示された際、input要素が見切れないように表示領域が調整されるためなのですが、それによってinput要素より上にある要素が隠れてしまうこともあるでしょう。
今回はそれを対策する方法を考えてみました。目指すはアプリ版YouTubeの「動画を見ながらコメントを入力する体験」です。
ただ、僕の実力ではYouTubeのサイトを編集することはできない(ブックマークレットならつくれるかもしれません)ので、まずはYouTubeの動画をAPIで表示した疑似的なページをつくって検証します。


やりたいこと

  1. どうにかしてキーボードが表示されていることをキャッチする
  2. キーボードが表示されている場合は動画をいい感じの位置に表示する

この2点を解決すれば良さそうです。


DEMO

develop.kimizuka.org
とりあえず、先に完成形をみたい方はこちらをご覧ください。(スマホのポートフォリオ表示限定です)


どうにかしてキーボードが表示されていることをキャッチするかを考える

focusイベント案

これは意外に簡単で、「inputやtextareaなどキーボードを呼び出すDOMにフォーカスがあたっているか否か」で判断できそうです。
つまり、

$('[type="text"], textarera, [contenteditable="true"]').on('focus', function() {
  // ここがキーボードが表示されているはず!
});

直前で[contenteditable="true"]を入れたのは、contenteditable属性を持ったDOMもキーボードが表示されるDOMだからです。他にもなにかを見逃してるような気がします。
input要素の[type="email"]とか[type="name"]とかにも対応しないといけないですが、それ以外は一見良さそうに見えます。('input'で一括指定したいところですが、キーボードが出現しない[type="checkbox"]とかもあるので考えどころです)
しかし、input要素が別ドメインのiframeの中にある場合など、直接イベントを振ることができないときに困りそうです。

リサイズイベント案

次に考えたのは、リサイズイベントです。キーボードが出てくるとwindowの大きさが変化するので、window.innerWidthとwindow.innerHeightからキーボードが出ているか否かを探ることができそうです。
input要素に直接イベントを振ることのできない環境でも動作させることができるので、良さげなんですが、実際に試してみたところ、リサイズイベントのコールバックではinnerWidth、innerHeightの値が安定せず、非同期にしてワンテンポ待つか、setIntervalで常時監視するかしないと厳しそうだったので、導入を見送りました。

スクロールイベント案

そもそも、「キーボードが出現したらvideoの位置を考える」ではなく、「videoが隠れるぐらいスクロールしたらvideoの位置を考える」と考えれば、window.scrollYを監視するという方法もあります。この方法でもinput要素に直接イベントを振る必要はありません。使えるページに制限がありますがうまくいきそうな気がします。
使える条件を具体的に挙げると、キーボード出現時以外に(バウンスを除いた)スクロールが発生しないページであることです。逆にスクロールで動画が隠れた際も位置を調整したいのであればこの方法がベストでしょう。スマホを横向きにした際の処理を考えなければならない気もしますが、そもそも横向きでは、画面の高さ的に動画とキーボードを同時に見ることは不可能な気がします。
そんなこんなでDEMOではこちらを採用しました。


キーボードが表示されている場合は動画をいい感じの位置に表示するかを考える

こちらはpositionを動的に変更してあげるのが良さそうです。
YouTubeみたいなサイトをつくるのであれば、動画はposition: fixed; の top: 0;になっているでしょうから、キーボード表示時には逆にtop: auto; bottom: 0;にしてあげればうまくいく気がします。ただ、input要素が隠れてしまうと入力が困難になってしまうため、input要素が最下部にあることを前提にtop: auto; bottom: {{input要素のtop}};としたいところです。


DEMO

develop.kimizuka.org

時間短縮のため、チャットの部分が過去につくったコンテンツの使い回しになっており、若干デザインが古めかしいですし、チャットの内容もノイズになってしまってますがが、検証には関係ないので良しとしましょう。スピード重視です。
余談ですが、jQueryをひさしぶりに使いました。いろいろ懐かしいですね。

nottoli.kimizuka.fm

ポイントを抜粋しますが、JavaScriptとしては、

JavaScript

$(window).on('scroll', function() {
  if (window.scrollY > window.innerHeight / 2) {
    $('#box').addClass('picture-in-picture');
  } else {
    $('#box').removeClass('picture-in-picture');
  }
});

ここです。
とりあえず、基本的にはバウンス以外のスクロールが発生しないページなので、画面の半分スクロールしたらという条件でクラスを振ってみました。
本当は、$(window)や$('#box')などは変数に入れた方がブラウザに優しいと思うのですが、コードをすっきりさせるために毎回取得してもらってます。


で、HTMLとCSSはこんな感じです。

HTML

<div id="box">
  <div id="player"></div>
</div>

CSS

#box {
  position: relative;
  width: 100%; height: 0;
}

#player {
  position: fixed;
  top: 0; left: 0;
  z-index: 99;
}

#box.picture-in-picture:after {
  display: block;
  position: fixed;
  width: 100%; height: 100%;
  content: '';
  background: rgba(0, 0, 0, .8);
  z-index: 1;
}

#box.picture-in-picture #player {
  top: auto; bottom: 44px; // footerの高さと揃える
}

picture-in-pictureクラスが振られた際に画面を暗くしたり、動画の位置を下付きにしたりしてます。
なんとなく、ピクチャーインピクチャーというクラスを振ってみました。
あと、SCSSを使うと、footerの高さを変数に入れることで、コメントの部分をもうちょっとすっきり書ける気がします。


結論

つくっていて非常に可能性を感じていたのですが、もうちょっとテストしてみないと実戦投入は厳しそうです。
特に古いAndroidでの挙動が気になります。iOSではキーボードを表示したまま画面をスクロールしたときの挙動に改善の余地がありそうです。
それらを考えると、なんとなくですが、本当はキーボード表示時にbodyのサイズを小さくしてしまうのが良いような気がしています。未検証ですが。

そんなこんなで、引き続き試行錯誤していこうと思います。