CSS
JavaScript
jQuery
React
クソアプリ

スマホ向けサイトをアプリっぽく見せるために半年間頑張ったことをまとめる

この記事は リクルートライフスタイル Advent Calendar2018の20日目の投稿です。

はじめに

この記事では スマホ向け web ページをアプリっぽく見せるための Tips を多く紹介します。
(CSS / JS / jQuery / React / WebGL の事例を紹介します)

(注) React 環境でのサンプルコードが多めですが、実装方法はどの環境でも変わらないと思います。ライブラリも同種のものが存在しているはずです。

最近だと、僕の大好きなアプリで味わった体験を、どうすれば Web で再現できるかなーって考えていました。そうしたネイティブアプリをWebで模倣したときに、知ったTipsやテクニックをまとめていきます。

ppp.mov.gif

この記事に書いてあること

  • アプリっぽい体験はどのようなものがあるか
  • CSS / JS / jQuery / React / GLSL を利用したネイティブアプリっぽさを出す小技
  • その小技を実現するためのライブラリ(React 系統がメイン)

ネイティブアプリっぽいって何だろう

WebViewやPWAに関する仕事をするときは、アプリっぽさの追求をしています。
ただ、正直なところ、「ネイティブアプリっぽいとは、どういうものか」に対する明確な答えは持てていません。
とはいえ、突然レイアウトが変わるなどの、カクついた動きに対して 「web っぽい」、なめらかな動きに対しては「ネイティブっぽい」と感じるように思います。

そこで、どうやればカクついた動きを Web ページから取り除けるのか、どうすればなめらかさを足せるのかを考えながら、色々なアプリの挙動を真似たりしていました。そのときに使ってみたライブラリや、取り組みを以下にまとめます。

目次

  • ページから Web 特有のカクツキを無くそう
  • ページになめらかさを足そう
  • ブラウザの機能やパーツを隠そう(要注意)

ページから Web 特有のカクツキを無くそう

ページ遷移時の transition について

web 上でページを遷移すると、ページ自体が切り替わるため、画面が真っ白になり一瞬だけちらつきます。一方で、ネイティブアプリではそのような遷移の際に、横から画面がプッシュされるなどのトランジションアニメーションが標準で用意されており、ちらつきを軽減させることができます。こういった動きは Web ではどう実現すればいいでしょうか。

jQuery プラグインでトランジション

jQuery に、animsitionという個人的にめちゃくちゃ気に入っているライブラリがあります。
SPA 実装じゃない遷移でもウェブっぽさがない感じを出してくれていい感じに動いてくれます。

anismation.mov.gif

React & CSS で Transition

トランジション 自体は CSS Animation で実現できます。

React で トランジション を行うには, componentDidMount, componentWillUnMount 時に トランジション 用の CSS を適用させます。

App.jsx
function App() {
  const [isAnimated, setAnimation] = useState(false);
  useEffect(() => {
    setAnimation(true);
  });

  return (
    <div className="wrapper">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <style jsx>{`
        .wrapper {
          color: ${isAnimated ? "white" : "black"};
          background-color: ${isAnimated ? "black" : "white"};
          transition: all 0.5s;
          opacity: ${isAnimated ? 1 : 0};
          width: 100w;
          height: 100vh;
        }
      `}</style>
    </div>
  );
}

fade.gif

react transition group

実務で React を利用するとなると, なんらかの routing ライブラリを利用すると思います。
大規模な開発になると、アニメーションの設定を毎回 Component に書くのはめんどくさいので、 routing ライブラリでフックしてしまいましょう。
react-routerを利用して入れば、react-transition-groupを利用すると便利です。
これはラップしたコンポーネントがマウントされた時、もしくはアンマウントされたときに、${transitionName}-enter, ${transitionName}-leaveなどのクラス名をつけてくれるコンポーネントを提供しています。これを Router 直下のコンポーネントにラップすれば、遷移するたびにクラスをつけることができます。あとはそのクラスに対応したトランジション用の css を用意しておけば、自動的ににトランジションアニメーションが走ります

fade.css
.fade-enter {
    opacity: 0.5;
}

.fade-enter-active {
    opacity: 1;
    transition: opacity 800ms linear;
}
routing.jsx
...

<TransitionGroup>
  <CSSTransition key={currentkey} classNames="fade" timeout={800}>
    <Switch>
      <Route path="/" component={Articles} exact />
      <Route path="/camera" component={Camera} exact />
      <Route path="/camera_roll" component={CameraRoll} exact />
    </Switch>
  </CSSTransition>
</TransitionGroup>;

モバイル Web 特有のトランジションにおける注意点

モバイルにおける横スワイプでのブラウザバックは、コンポーネントの unMount 時のアニメーションが走った後に、再度 componentDidMount時のアニメーションが走り、壊れた感じになってしまいます。
それに横スワイプ自体がプッシュバックしているかのような見え方になります。そのため、僕はモバイルから見られるアプリケーションを開発する際は、ブラウザバックされる際のアニメーションはつけていません。モーダルを閉じるといったような、ブラウザバックされない前提の処理にのみアニメーションをつけています。

もし、横スワイプ自体を封じた上で、ブラウザバックでのアニメーションをつけるのであれば、スクロール位置やスクロール量を計算して、横スワイプによるバックを監視すると可能です。横スワイプの監視には、HammerJSが便利です。もちろん、React用のバインディングも存在しています。react-hammerjs

ページの部分更新を行う

カクつきが生まれてしまう原因はページのロードにもあります。コンテンツの切り替えの瞬間に一瞬だけホワイトアウトしたり、コンテンツのロード後にレイアウトが変わるためです。それらはどうすれば避けられるでしょうか。

ページごとの loading を避ける + loader を表出する

ページが切り替わるたびにそのページ自体をロードすると、一瞬画面が真っ白になり、かくついた印象を与えてしまいます。この問題は Ajaxを利用して、コンテンツだけロードすれば解決できます。 また loading 中は loader を出すことで、ユーザーのコンテンツ要求に対していきなりコンテンツが表出されることを防げます。これは送信中に loading フラグを立て、 コンテンツが帰ってくると loading フラグ を消す、そのフラグでloaderを出し分けるとすれば実現できます。僕は、loader には spinkitをよく使います。

sc.mov.gif

loader としてスケルトンビューを利用する

Ajax で部分的に更新しても、コンテンツを差し込んだだけだとレイアウトがガタっと変わったりして、カクカク感が生まれてしまいます。どうすればなくせるでしょうか。スケルトンビューを使ってみましょう。
スケルトンビューはコンテンツのレイアウトに合わせて表示される、コンテンツの外形です。コンテンツがローディング中でも表示させることができるので、ローディングとしても使えます。ローディングは先にコンテンツが表示される領域の外形だけ作っておけば、コンテンツが差し込まれた時の、レイアウト変更がないので、滑らかさを出せます。 1

スクリーンショット 2018-12-20 15.03.43.png

react-loading-skeletonを利用すると比較的簡単に実装できます。

プルしてリフレッシュ

Twitter のようなアプリでは、コンテンツを再読み込みするために、ページを下に引っ張ることで更新できます。
ページ自体の更新ではなくコンテンツ単位で更新する方法として、感覚にもあっており、自然なコンテンツ読み込みを実現でき、カクツキ感は感じません。

これを Web 上実装するのであれば、 scroll 時にコンテンツの top 位置を監視し、ある閾値を超えたら fetch action を発火させ loading flg を立てるなどして、実装するという方法があります。

とはいえ、要素の位置を監視するのは DOM への依存コードの管理を自分で行う必要があり、ぼくは苦手なので、便利なライブラリ react-pull-to-refresh に任せてしまっています。

sample.jsx
<ReactPullToRefresh
  onRefresh={handleRefresh}
  className="your-own-class-if-you-want"
  style={{
    textAlign: "center"
   }}
>
  <ListItem.Article />
  <ListItem.Article />
  <ListItem.Article />
  <ListItem.Article />
  <ListItem.Article />
</ReactPullToRefresh>

このライブラリは, pull した領域に

sample.jsx
<div className="loading">
  <span className="loading-ptr-1"></span>
  <span className="loading-ptr-2"></span>
  <span className="loading-ptr-3"></span>
</div>

を挟んでくれます。そのため、ローダーを出したい場合はこれらの要素に loader 用の CSS を当ててあげましょう
たとえば公式サンプルでは「・・・」がアニメーションする loader が作られています。

swmov.gif

無限スクロール

loading の方法として、無限スクロールという手段もあります。
無限スクロールとは、一定量スクロールしたらコンテンツが継ぎ足されていくローディング方法を指しています。
とくにフィードやタイムラインを扱うアプリを利用していると、見覚えがあるのではないでしょうか。このローディングの方法は、ページネーションや画面全体更新がないので、カクツキ感を無くすことができます。

ネイティブアプリ特有の手法ではなく Web 上 でも比較的この体験をすることができます。たとえばjQuery + waypointプラグイン を使った実装は昔からよくある手法として知られています。waypoint自体は画面の指定位置に要素が入ったことを知らせてくれる役割を持つライブラリであり、これの React 版のreact-waypointも存在します。ぼくは無限スクロールの実装には、このreact-waypoint使っていました。

EndlessTodo.jsx
import Waypoint from 'react-waypoint';

_loadMoreTodo = () => {
  // this.state.todosを増やしていく処理
}

...

return <div className="todos">
  {todos.map(todo => <Todo todo={todo} />)}
  <Waypoint onEnter={() => this._loadMoreTodo()} />
</div>

ページになめらかさを足す

UI をなめらかな感じにすると、アプリ感がでてきます(情報量0の日本語ですいません・・・)。さらに、UI に対してだけでなく、ユーザーの視線や思考に対してもなめらかさを意識してあげると良いでしょう。つまり、なめらかさやネイティブアプリっぽさを出すためには、ユーザーの操作一つ一つが一連の動きとして繋げられるように考慮されているインターフェイスを作る必要があると思います。ブラウザの制限の上でどこまで追求できるでしょうか。頑張ってみました。

横スクロールの動きをできるようにしてあげる

一般的に web はページで提供されることが多く、ユーザーには、縦のスクロールを要請します。
縦スクロールだけだとユーザーの動きを一方向に制限してしまっており、横スクロールを採用することで、もっとすっとコンテンツを探せることもできそうです。そこで web でも横スクロールを積極的に取り入れていく方法を考えてみましょう。

overflow-x:scroll を利用する

overflow-xは画面からはみ出た要素 x 軸方向をどう扱うかを扱うことができ、ここでscroll を利用することで、
横スクロールさせることができます。

sample.css
.wrapper {
  width: 90%;
  display: flex;
  overflow-y: scroll;
}

.wrapper::-webkit-scrollbar {
  display: none;
}

.card {
  background-color: red;
  width: 100px;
  height: 50px;
  flex-shrink: 0;
  margin-right: 8px;
}

これは比較的簡単な実装方法ですが、注意点として、親が flexbox の場合は子供にflex-shrink: 0;を持たせないと、スクロール領域外にはみ出してくれません。あと、スクロールバーが出てしまうと Web っぽくなるので可能であれば消してしまいましょう(要注意:PC からアクセスされてしまうと、アクセシビリティは下がってしまいます)。

また、スクロールの強さの取得や、スライドを 1 つずつ固定するといったことは難しいです。そこでライブラリの利用を考えて見ましょう。

swiper を利用する

そこで、Swiperの出番です。
Swiperは横スクロールを支援するライブラリで、スクロールイベントの取得やフォーカスされている要素を取得できます。
Swiper.on('hoge')でさまざまなイベントをハンドルでき、その中でも setTranslate はスワイパブルなアイテムが移動した際に発火し、その位置を取得できるため、移動量を計算できます。

swipe.js
swiper.on('setTranslate', function onSliderMove() {
  console.log(this.translate);
});

FYI: https://stackoverflow.com/questions/48375955/swiper-how-to-get-translatex-real-time

また React 上で利用できるreact-id-swipeなんてものもあります。

swipable なタブ

ニュース系アプリには横スワイプがあります。これを Web で実装するにはどうしたらいいでしょうか。
検討段階では Swiper を利用してコンテンツごとスワイプさせようと考えていました。。
しかし、実装コストの兼ね合いからreact-swipeable-viewsを利用しました。めちゃくちゃ楽でした。

swaipable.js
<SwipeableViews>
  <ViewA />
  <ViewB />
  <ViewC />
</SwipeableViews>

実際のニュースアプリでは、スワイパブルなビューの上に Tab が付いていると思います。
そのタブをクリックしたらビューがスワイプ、ビューをスワイプしたらタブも切り替わるという挙動になります。
それも作りましょう。

には onChangeIndex という関数があり、ここでどのタブに切り替えられたかを取得できるので、どこを切り替えたのかを state に保存しましょう。そして、そのタブが state に応じて切り替わるようになって入ればそれで上の要件は満たせます。(これはあらかじめタブを自作するか、タブライブラリを入れておく必要が有)

yokoo.gif

modal / action sheet

iOS 開発においては JS でいう alert がデフォルトでそこそこかっこいいです。 (https://qiita.com/Simmon/items/319b738e2a667d6a6b3d)
これは、actionsheet や modal として利用することができ、アニメーションもついておりなかなかずるいくらいにかっこいいです。
負けていられません、web 実装するために自前実装です。

半透明な view を fixed する

モーダルは画面全体を覆う、半透明な背景を用意し、その上にコンテンツの描画領域を乗せれば作れます。ここでポイントは、コンテンツのアニメーションです。モーダルを表出するだけでは、画面の表示非表示をきりかえるだけでちらつき感が出てしまいます。なめらか感のために、コンテンツは何かアニメーションをつけてあげましょう。

app.jsx
import React, { useState, useEffect } from "react";

const Modal = (props) => {
  const [isAnimated, setAnimation] = useState(false);
  useEffect(() => {
    setAnimation(true);
  });
  return (
    <div className="wrapper">
      背景背景背景背景背景背景背景背景背景背景背景背景背景背景背景背景背景背景背景背景
      <div className="innter" /> {/* componentとしてexportするなら, {children}になる */}
      <style jsx>{`
        .wrapper {
          background-color: rgb(0, 0, 0, 0.7);
          width: 100vw;
          height: 100vh;
          position: fixed;
        }
        .innter {
          background-color: white;
          transition: all 1s;
          position: absolute;
          width: 80%;
          height: 80%;
          bottom: ${isAnimated ? "10%" : 0};
          left: 10%;
        }
      `}</style>
    </div>
  );
}

action sheet を作るには

先のモーダルコンポーネントを応用して、選択肢を 2 つ用意するだけですね!  (これは古いコードそのまま持ってきたので、s-c 実装です)

modal.jsx
return (
<BackGround>
  <Container>
    <TitleWrapper>
      <Text.SmallText>編集中です。削除しますか?</Text.SmallText>
    </TitleWrapper>
    <Choice onClick={onProceed}>
      <Text.Default color={COLOR.danger}>編集を削除する</Text.Default>
    </Choice>
    <Choice onClick={onCancel}>
      <Text.Default>戻る</Text.Default>
    </Choice>
  </Container>
</BackGround>);

const BackGround = styled.div`
  position: fixed;
  width: 100%;
  height: 100vh;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  top: 0;
  left: 0;
`;

const Show = keyframes`
0% {
    bottom:0%;
}
100% {
    bottom:5%;
}
`;

const Container = styled.div`
  position: absolute;
  width: 90%;
  height: 25%;
  bottom: 5%;
  left: 5%;
  background-color: rgba(255, 255, 255, 0.9);
  z-index: 1;
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  animation: ${Show} 0.2s linear;
`;

const TitleWrapper = styled.div`
  width: 100%;
  height: 20%;
  display: flex;
  align-items: center;
  justify-content: center;
  border-bottom: solid 1px ${COLOR.placeholder};
`;

const Choice = styled.div`
  width: 100%;
  height: 40%;
  display: flex;
  align-items: center;
  justify-content: center;
  :not(:last-child) {
    border-bottom: solid 1px ${COLOR.placeholder};
  }
`;

as.gif

スプラッシュ画面

スプラッシュ画面とは、アプリを起動したときに表示される画面です(例えばアイコンやタイトルなど)。一般的にバックグラウンドで起動していないときに起動すると表示されます。
これを web で原始的に実装しようとすれば、スプラッシュ用の画面とルーティングを用意し、ユーザーの来訪頻度に合わせて出す方法をとると思います。

localStorage で来訪頻度を管理してスプラッシュを表示する

この手法で開発したときはreduxを利用しました。redux には store の情報をそのまま localStorage に保存してくれるミドルウェアライブラリ redux-persist があります。これを導入するとアプリ起動時に REHYDRATE するアクションが走るので, そのアクションを reducersaga で監視し、前回表出させた期間と現在の時間の差分に応じて、 SHOW_SPLASH のようなアクションを発火し、 スプラッシュ画面を表出しています。 表出後はその表出した日時を store に保存し, redux-persist を使って永続化させています。

icon を設定する(PWA 限定)

もし、 PWA 化している場合は iconmanifest.jsonの設定をすることでスプラッシュ画面を出すことができます。
さらに同時に背景色を指定すると、スプラッシュが表示されるまでの間に、画面にその色を先に出せます。
背景色にスプラッシュイメージのメインカラーを指定しておくと、スプラッシュ画面への遷移もなめらかになります。

manifest.json
{
  "short_name": "fs",
  "name": "fullscreen splash",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
    {
      "src": "icon.png",
      "type": "image/png",
      "sizes": "96x96"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#000000"
}

モバイル safari に対するスプラッシュ

とはいえ、実は上の方法では mobile safari だとえません。その場合は HTMLheader

index.html
<link rel="apple-touch-startup-image" href="path/to/image">

を追加してください。しかしこれでも、各 iOS 端末の画像サイズに合わせて登録しないと、読み込んでくれません。その結果

index.html
<link rel="apple-touch-startup-image" href="path/to/image640x1136.png" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="path/to/image750x1334.png" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="path/to/image1242x2208.png" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="path/to/image1125x2436.png" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
...

と header 部が肥大化してしまいます。そこで、Google が用意しているPWACompatという仕組みをを利用しましょう。これでモバイル Safari における煩雑なアイコン登録が勝手にしてもらえます。これは、scriptタグを 1 つ挟むと、manifest.json を完全に解釈してくれないブラウザに対しても、自動で関係する linkタグを挿入してくれるものです。

FYI: https://github.com/GoogleChromeLabs/pwacompat

IMG_0705.MOV.gif

ユーザーの操作に反応してUI上にエフェクトをかけてあげる

CSS in JS で動的に CSS の値を変える

CSS in JSライブラリを利用してCSSを書いている場合、CSSJSから変数を渡すことができます。
そうすることでユーザーの挙動や経過時間などに比例してスタイルを変えることができます。
これはユーザーに対してフィードバックを与えたり、なめらかさを演出を作れたり、アプリっぽさを出すためには、とても重宝します。
しかし CSS 自体はなかなか理解が難しく、また人間が扱える範囲も限られています。(意:人の力ではCSSの力は完全には引き出せない)

そこで、CSS の限界を超えたエフェクトやフィードバックを作りたければ、canvasの利用が視野に入ってきます。

動的に canvas を動かす

JSにはcanvas要素を扱うためのライブラリがあります。

2D を扱いたいなら Pixijs

PixiJS は 2D のグラフィックを作ることが得意なライブラリです。

最近だと、 アイドルマスターのブラウザゲームで採用されたことにより、盛り上がりがあったかと思います。

公式のサンプルを適当に動かすだけでもそれなりの、なめらか感を出すことができてオススメです。僕はサンプルをコピペする以上のことは知らないです。(それでも十分に動いてくれます)

3D を扱いたいなら Three.js

Three.js は 3D のグラフィックを作ることができるライブラリです。3D なので、奥行きがあります。

公式のサンプルがとても充実しています。こちらも、公式のサンプルを適当に動かすだけでもそれなりの、なめらか感を出すことができます。これも公式サンプルをコピペする以上のことは知らないです。(それでも十分に動いてくれます)

プリミティブに行くなら WebGL

canvas を操作する方法としてシェーダーを書くという手段もあります。ブラウザは WebGL を利用できるのでGLSLで書いたシェーダープログラムを実行できます。そして React の propscanvas に流すと、アプリケーションの状態に応じて動作する WebGL コンテンツを開発することができ、リッチなフィードバックを実現できます。しかし WebGL を React から動かすためには、propscanvas に渡す方法を考えなければならず、そのマッピングを自前で頑張るか、マップしてくれるなんらかのライブラリを利用します。

ライブラリで行うには gl-reactがオススメです。
これは GLSL を実行するコンポーネントを提供してくれます。さらにそのコンポーネントに流したuniformsというpropsにはshaderプログラムの中で利用できる値を詰められます。

GL.jsx
const shaders = Shaders.create({
  helloGL: {
    frag: GLSL`
      precision highp float;
      varying vec2 uv;
      uniform float blue, red;
      void main() {
        gl_FragColor = vec4(clamp(uv.x, 0.2, 0.7), clamp(uv.x, 0.3, red), clamp(blue, 0.3,0.8), 1.0);
      }`
  }
});
class HelloGL extends React.Component {
  render() {
    const { blue } = this.props;
    return (
      <Node shader={shaders.helloGL} uniforms={{ blue:value, red: value / 2 }} />
    );
  }
}

...

return (
 <Surface width={window.innerWidth} height={350}>
  <HelloGL value={value} />
</Surface>
);

ブラウザの機能やパーツを隠そう(要注意)

⚠️ アクセシビリティは犠牲になります。あなたのアプリケーションが、アクセシビリティを考慮しないといけない場合は利用しないでください。⚠️

user に選択させない

フルルクリーンモードや WebView 内でコンテンツを提供した時、ネイティブっぽさを出すことができます。
しかしユーザーが画像やテキストを長押しするとヘルパーが立ち上がるので、「あ、ブラウザだ」って気づいてしまいます。
選択できないようにしてしまいましょう。

sample.css
.hoge {
  user-select: none;
}

スクロールバーを消す

PC で使われない前提ならスクロールバーも消してしまいましょう。PC ユーザーがいるならダメですよ。
とくに横スクロールだとデスクトップユーザーはスクロールできなくなってしまいます。(怒られた経験有)

App.css
body::-webkit-scrollbar {
  display: none;
}

フルスクリーン

アドレスバーやツールバーが画面に出ていると、web っぽさが出てしまいます。また、ユーザーの体験のために表示領域を大きくしたいというニーズもあるかもしれません。ブラウザにはアドレスバーを消す機能が一応存在しています。しかし、確実に実行可能であるというわけではないこと、URL バーを消すことで返ってユーザビリティを下げることもあるので、注意してこの機能を使ってみましょう。

android

Android 端末かつ Chrome を利用している場合は PWA 化していない場合でも、FullScreenAPI が利用して、フルスクリーンでコンテンツを提供できます。

safari

昔は minimal-ui の設定を書けば可能でしたが、今は利用できません。(方法を調べているのこの情報に出会いますが、今はもう使えません)

index.html
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">

代わりに、 ユーザーがホーム画面へ追加したときのみ限定の挙動になりますが メタタグに次のことを書けば可能です。

index.html
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

つまり、Safari の場合は、PWA 化されていなければ、フルスクリーンでコンテンツを提供することができません。

フルスクリーンと OAuth2(要調査)

PWA 化している状態で、フルスクリーンモードで OAuth 認証してみましょう。Redirect で PWA に戻れません。そのため、元のブラウザのページに戻しています。そのせいで、いつまでたっても認証できないという辛いことが起きています。これに対する対応策は、正直知らないです。試したことはないのですが、iframe 越しに認証画面をひらけばいけるみたいな話を聞いたことがあります。(要調査: 実現可能性&セキュリティリスク)

(加筆)どういうときにアプリっぽさを出せばいいか

ネイティブアプリっぽく見せることの是非に対しての言及があったため、記事の提供者としての立ち位置を表明させてください。

まず、そのWebページがアプリとして開かれる場合(例えば、ネイティブアプリのWebViewから開かれる場合、もしくはPWAから開かれる場合)は、ネイティブアプリっぽさが有っても良いと思います。むしろ持たせた方が、アプリに慣れているユーザーにとってはフレンドリーになります。

では、普通にブラウザから開かれるページはどうあるべきかでいうと、このような記事を書いておいて恐縮なのですが、ネイティブアプリっぽく見せる必要はあまりないと思います。Webページの開発においては、まず不要とも言えるでしょう。ただ、Webアプリケーションの開発においては、ネイティブっぽさがユーザーのためになり、採用した方が良いユースケースもあります。

WebページとWebアプリケーションの違いですが、僕の認識ではWebページは、インターネットから情報を探しに来るユーザーのために公開してあるページです。一方でWebアプリケーションは、ユーザーが何らかの問題解決を行うためのWeb上のプラットフォームだと思っています。

Webページにおいては、ユーザーにとって情報を探しやすいUIを作るべきであり、一覧性やユーザーの慣れ親しんだ操作方法を提供すべきだと思います。そのため、ネイティブアプリっぽいUIは、ユーザーにとって不便であり取り入れない方が良いケースがほとんどだと思っています。

しかし、Webアプリケーションにおいては、ユーザーが自身のニーズや悩みを解消できるように、ユーザーをサポートする仕組みを作って行った方が良いでしょう。そのため、ユーザーに寄り添えるような設計を目指すべきで、ユーザーからの入力や、ユーザーへフィードバックを与えるイベントが多く発生する設計になるでしょう。

このとき、ネイティブアプリっぽい動き(特にアニメーションと横方向への移動)は、ユーザー体験を向上させるために必要になってくると思います。具体例をあげると、ユーザーからのインプットにおいては、あるinputでバリデーションエラーにひっかかったときに、なんらかのアニメーションを持たせてユーザーにエラーを知らせることで、エラーに対する気づきやフィードバックを、わかりやすく伝えることができます。他にも、業務支援系のモバイルアプリにおいては、店舗の売り上げなどを日別で確認するとき、前後日で比較した時は右左のスワイプで切り替えると、直感的に切り替えることができ、ユーザーの思考を阻害することなくコンテンツを提供することができます。このようにアプリっぽさを追求した方がユーザーのためになるケースも存在します。

また装飾をするだけのアニメーションや表現については、 ユーザーを疲れさせない範囲ではありますが 、ユーザーをわくわくさせる、アプリの世界観を演出するといった目的においては使ってもいいのではないかと思っています。特に世界観を作りあげることはユーザーがそのサービスを愛し、ファンになってくれる要因の一つでもあります。

まとめると、Webページの見栄えをアプリっぽくするだけの行為にはあまり意味はなく、ユーザーにとって迷惑にもなるとは思うのですが、Webアプリケーション開発において、ユーザーのユースケースを想定し、ユーザーの体験や業務にとってfitするUIの実現方法として、アプリっぽさが必要となるケースもあるので、アプリっぽくみせるための方法を頭の片隅に入れておくと良いかもしれません。


  1. スケルトンじゃないローダーでも当てはまる話です。ローダーはコンテンツ表示領域と同じ領域を持たせて起きましょう