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

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


はじめに

この記事では スマホ向け web ページをアプリっぽく見せるための Tips を多く紹介します。

(CSS / JS / jQuery / React / WebGL の事例を紹介します)

(注)この記事は React 環境でのサンプルコードを多く紹介していますが、考え方はどの FW を使っていてもあまり変わらないと思います。ライブラリも同種のものがあるはずです。

最近だと、僕の大好きなアプリで味わった体験を、どうやって web で再現できるかなーって考えていました。こういうことをやっている中で知ったことを紹介します。



ppp.mov.gif



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

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


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

どういうときに「ネイティブアプリっぽいっと感じてくれるのかなー」と考えていました。

とくに根拠はないのですが、突然動きが生まれるなどカクついた動きに対して 「web っぽい」、なめらかな動きに対してネイティブっぽいって思ってくれるのかなーと僕は思っています。そして、なめらかの動きというのは UI に限った話ではなく、ユーザーの動きを適切に導いてあげるサイトはアプリっぽいと思ってくれると思っています。

そこで、どうやればカクツキを web ページから取れるのか・なめらかさを足せるのかで色々アプリの挙動を真似たりしていました。

そのときに使ってみたライブラリや、取り組みをまとめます。


目次

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


ページからカクツキを無くそう


ページ遷移時の transition

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


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

jQuery に、animsitionという個人的にめちゃくちゃ気に入っているライブラリがあります。

SPA 実装じゃない遷移でもウェブっぽさがない感じを出してくれていい感じに動いてくれます。



anismation.mov.gif



React & CSS で Transition

transition 自体は css animation で実現できます。

React で transition を行うには, componentDidMount, componentWillUnMount 時に transition 用の 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-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="/articles" component={Articles} exact /> 
      <Route path="/camera" component={Camera} exact /> 
      <Route path="/" component={Home} exact /> 
      <Route path="/articles" component={Articles} exact /> 
      <Route path="/camera_roll" component={CameraRoll} exact /> 
    </Switch> 
  </CSSTransition> 
</TransitionGroup>; 


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

モバイルにおける横スワイプでのブラウザバックは、コンポーネントの unMount 時のアニメーションが走った後に、再度 componentDidMount時のアニメーションが走り、壊れた感じになってしまいます。

それに横スワイプ自体がプッシュバックしているかのような見え方になります。そのため、僕はブラウザバックされる際のアニメーションはつけていません。モーダルを閉じるといったような、ブラウザバックされない前提の処理にのみアニメーションをつけています。


ページの部分更新を行う

カクツキが生まれてしまう多くの原因はページのロードにあると思います。


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

ページが切り替わるたびにそのページ自体をローディングすると、一瞬画面が真っ白になり、かくついた印象を与えてしまいます。この問題は Ajax でコンテンツだけロードすれば解決できます。 また loading 中は loader を出しましょう。それにより、ユーザーのコンテンツ要求から急にコンテンツが表出されることを防げます。これは送信中に loading フラグを立て、 コンテンツが帰ってくると loading フラグ を消すとすれば実現できます。僕は、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 をなめらかな感じにすると、アプリ感がでてきます。さらに、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



何かに反応してエフェクトをかけてあげる


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 越しに認証画面をひらけばいけるみたいな話を聞いたことがあります。(要調査: 実現可能性&セキュリティリスク)



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


コメント

このブログの人気の投稿

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-04-30 23:37:32 RSSフィード2021-04-30 23:00 分まとめ(42件)

投稿時間:2023-02-05 02:09:04 RSSフィード2023-02-05 02:00 分まとめ(9件)