Help us understand the problem. What is going on with this article?

Ionicで半モーダルを作成する

More than 1 year has passed since last update.

(この記事はIonicアドベントカレンダー2018の9日目の記事です。
この記事はKineca Developer Blogにアップしたものをアドカレ用に修正したものです。
興味ある方KinecaDeveloperBlogもぜひどうぞ!)

本記事は半モーダルをIonicで実装する方法について解説しています(最近急増中のあれです)
半モーダル知らない人は以下のリンクをクリック。
iOSにおける半モーダルビューの解釈

Ionicで半モーダルを実装する場合、以下の方法が考えられます。

  1. ModalControllerを改造して実装する
  2. メインのコンテンツはion-footer内に記述する
  3. ion-contentは透過させるようにする
  4. 背景を黒くさせるbackdropを用意する

これらを実装すると、次のようになります。

※上の画像は弊社のエンタメアプリ「pato」に実装された様子になります。興味ある方は以下のリンクをクリック。
https://pato.today/

本記事では、上のような半モーダルを実装する方法を紹介しています。興味のある方は是非とも続きを読んでみてください。
「ソースコードだけみたいんだ!」って人は一番下にあるのでスクロールしてください。

新しくIonicアプリケーションを作る

新しくIonicアプリケーションを生成します。次のコマンドを実行してください。

ionic start ionic-semi-modal blank

生成が完了したら ionic-semi-modal ディレクトリに移動します。

cd ionic-semi-modal

次は、半モーダル用のページを生成しましょう

ionic g page semi-modal

また、その半モーダルをhome画面から呼び出せるようにする必要があります。

src/pages/home/home.tsを次のように変更します。

import { Component } from "@angular/core";
import { NavController, ModalController } from "ionic-angular";

@Component({
  selector: "page-home",
  templateUrl: "home.html"
})
export class HomePage {
  counter: number = 0;
  constructor(
    public navCtrl: NavController,
    public modalCtrl: ModalController
  ) {}

  showSemiModal() {
    const modal = this.modalCtrl.create("SemiModalPage", {
      counter: this.counter
    });
    modal.onDidDismiss(res => {
      if (res !== null) {
        this.counter = res;
      }
    });
    modal.present();
  }
}

合わせてsrc/pages/home/home.htmlsrc/pages/home/home.scssを次のように変更しましょう。

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic SemiModal![output.gif](https://qiita-image-store.s3.amazonaws.com/0/165335/84edb598-be68-be68-bc51-7b5e9e612895.gif)
![output.gif](https://qiita-image-store.s3.amazonaws.com/0/165335/a4bfde9d-fc12-fc23-d145-d770e90bffb9.gif)

    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <div class="counter-wrapper">{{counter}}</div>
  <button ion-button (click)="showSemiModal()">Show SemiModal</button>
</ion-content>
page-home {
  ion-content {
    text-align: center;
  }
  .counter-wrapper {
    font-size: 60px;
  }
}

半モーダルを作成する

半モーダル自体の作成は非常に簡単です。Ionicにはion-footerというコンポーネントがありこれを利用すれば簡単に半モーダルのUIを作成することができます。
src/pages/semi-modal/semi-modal.html、semi-modal.scssを以下のように書き換えましょう

<ion-content (click)="close()">
</ion-content>

<ion-footer padding>
  <div class="semi-modal-content">
    <button ion-button icon-only (click)="down()">
      <ion-icon name="remove"></ion-icon>
    </button>
    <div class="counter">
      {{counter}}
    </div>
    <button ion-button icon-only (click)="up()">
      <ion-icon name="add"></ion-icon>
    </button>
  </div>
  <button ion-button full color="secondary" (click)="save()">Save</button>
</ion-footer>
page-semi-modal {
  ion-content {
    opacity: 0;
  }
  ion-footer {
    background-color: #fff;
    .semi-modal-content {
      display: flex;
      justify-content: center;
      align-items: center;
      padding-bottom: 20px;
      .counter {
        font-size: 60px;
      }
      button {
        margin: 0px 12px;
      }
    }
  }
}

各ボタンに対するアクションも記述します。

import { Component } from "@angular/core";
import { IonicPage, ViewController, NavParams } from "ionic-angular";

@IonicPage()
@Component({
  selector: "page-semi-modal",
  templateUrl: "semi-modal.html"
})
export class SemiModalPage {
  counter: number = 0;

  constructor(public viewCtrl: ViewController, public navPrams: NavParams) {
    this.counter = this.navPrams.data.counter;
  }

  ionViewDidLoad() {
    console.log("ionViewDidLoad SemiModalPage");
  }

  up() {
    this.counter++;
  }

  down() {
    this.counter--;
  }

  close() {
    this.viewCtrl.dismiss(null);
  }
  save() {
    this.viewCtrl.dismiss(this.counter);
  }
}

以上、半モーダルのページが完成します。

バックドロップを自作する

半モーダルのページが完成しましたが、今のままだと背景が薄暗くなりません。単純に考えるとion-contentのbackdround-colorを変更すれば良さそうですが、その方法だとモーダルの遷移アニメーションと一緒に背景が移動してしまうという問題があります。

この問題に対応するためにbacdropを自作します。新しくBackdropProviderを作成しましょう。

ionic g provider backdrop

src/providers/backdrop/backdrop.tsを以下のように変更しましょう。

import { Injectable } from "@angular/core";

@Injectable()
export class BackdropProvider {
  constructor() {}

  show() {
    let backdrop = document.createElement("div");
    backdrop.id = "custom-backdrop";
    backdrop.className = "backdrop-fade-in";
    backdrop.style.position = "absolute";
    backdrop.style.top = "0";
    backdrop.style.right = "0";
    backdrop.style.left = "0";
    backdrop.style.bottom = "0";
    backdrop.style.width = "100%";
    backdrop.style.height = "100%";
    backdrop.style.background = "#000000";
    backdrop.style.opacity = "0.4";
    backdrop.style.display = "none";
    backdrop.style.pointerEvents = "none";

    let ionApp: any = document.getElementsByTagName("ion-app")[0];
    if (ionApp) {
      ionApp.appendChild(backdrop);
      backdrop.style.display = "block";
    }
  }

  hide() {
    let backdrop = document.getElementById("custom-backdrop");
    if (backdrop) {
      backdrop.classList.add("backdrop-fade-out");
      setTimeout(() => {
        backdrop.remove();
      }, 300);
    }
  }
}

backdropのfade-in、fade-outのアニメーションをsrc/app/app.scssに追加します。

.backdrop-fade-in {
  animation-name: fadeInOpacity;
  animation-timing-function: ease-in;
  animation-duration: 250ms;
  animation-fill-mode: both;
}

@keyframes fadeInOpacity {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 0.4;
  }
}

.backdrop-fade-out {
  animation-name: fadeOut;
  animation-duration: 200ms;
  animation-timing-function: ease-out;
  animation-fill-mode: both;
}
@keyframes fadeOut {
  0% {
    opacity: 0.4;
  }
  100% {
    opacity: 0;
  }
}

最後に、semi-modal表示時にbackdropを呼び出すようにします。src/pages/semi-modal/semi-modal.tsを以下のように変更しましょう。

import { Component } from "@angular/core";
import { IonicPage, ViewController, NavParams } from "ionic-angular";
+import { BackdropProvider } from "../../providers/backdrop/backdrop";

@IonicPage()
@Component({
  selector: "page-semi-modal",
  templateUrl: "semi-modal.html"
})
export class SemiModalPage {
  counter: number = 0;

+  constructor(public viewCtrl: ViewController, public navPrams: NavParams, public backdrop: BackdropProvider) {
    this.counter = this.navPrams.data.counter;
+    this.backdrop.show();
  }

  ionViewDidLoad() {
    console.log("ionViewDidLoad SemiModalPage");
  }

  up() {
    this.counter++;
  }

  down() {
    this.counter--;
  }

  close() {
+    this.backdrop.hide();
    this.viewCtrl.dismiss(null);
  }
  save() {
+    this.backdrop.hide();
    this.viewCtrl.dismiss(this.counter);
  }
}

完成

以上、半モーダルが完成しました。実際に完成したアプリケーションは下のように動作します。非常に簡単に実装できますね。

ソースコード

https://github.com/scrpgil/ionic-semi-modal
※気に入ったらスターをください

さいごに

この記事で紹介している実装は本番運用のアプリでも動いてます!
改めてアプリのリンクを載せますので興味のある方は触ってみてください!
https://pato.today/

また、patoを運営する株式会社キネカでは他にもIonicアプリを運用しております。
Ionic開発者の採用も積極的に行なっていますので興味のある方是非とも御声がけください。
それでは!

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away