◉RORK LABEN
●EXPO — EAS Observe がネイティブクラッシュも記録(10/07)。SDK 57 は 57.0.21 以降で確認できます●SDK 58 — SDK 58 Beta は 9/15 公開。stable の日付はまだ未確認です●RN 0.88 — React Native 0.88.x の正式リリース予定は 10/12、残り3日●Q&A — expo-widgets が本番ビルドだけ真っ黒になる、という問いが出ています●RORK — 09-29 に GPT-6.1 Sol を追加。Pro・Max プランで利用できます●NEW — クライアントのアプリを作る前に決める、公開アカウントの持ち主●EXPO — EAS Observe がネイティブクラッシュも記録(10/07)。SDK 57 は 57.0.21 以降で確認できます●SDK 58 — SDK 58 Beta は 9/15 公開。stable の日付はまだ未確認です●RN 0.88 — React Native 0.88.x の正式リリース予定は 10/12、残り3日●Q&A — expo-widgets が本番ビルドだけ真っ黒になる、という問いが出ています●RORK — 09-29 に GPT-6.1 Sol を追加。Pro・Max プランで利用できます●NEW — クライアントのアプリを作る前に決める、公開アカウントの持ち主
記事一覧/開発ツール
⬡ 開発ツール/2026-06-24上級

呼吸の波に合わせて静かに震える、瞑想アプリの触覚をCoreHapticsで作る

expo-haptics の決まった振動では呼吸の「波」が作れません。CoreHaptics の連続イベントとパラメータカーブを Expo ネイティブモジュールから呼び出し、4-7-8 呼吸に同期した触覚を作る実装を、Swift と TypeScript のコードとともに整理します。

Rork577CoreHapticsExpo213ネイティブモジュール9個人開発215

✦ プレミアム記事

瞑想と呼吸法のための小さなアプリを個人開発で出したとき、画面の円はゆっくり膨らんだり縮んだりするのに、指先には何も伝わってこないことがずっと気になっていました。目を閉じて使う場面が多いアプリなのに、視覚だけで呼吸を導いていたのです。

そこで expo-haptics を入れて、息を吸う合図と吐く合図に振動を足してみました。けれど、Haptics.impactAsync() で出せるのは一瞬の「トン」という打撃感だけです。私が欲しかったのは、4秒かけてゆっくり強くなり、止まり、また8秒かけて消えていく、息そのものに寄り添う波でした。単発の振動をいくら並べても、その連続した呼吸の感触にはなりません。

iPhone の Taptic Engine は、本当はもっと繊細な表現ができます。それを引き出す仕組みが CoreHaptics です。ここでは、CoreHaptics の連続イベントとパラメータカーブを使って呼吸に同期した触覚を作り、それを Expo ネイティブモジュールとして React Native(Rork で生成したアプリ)から呼べる形にするところまでを、実装コードとともに見ていきます。

なぜ expo-haptics だけでは呼吸の波にならないのか

expo-haptics が公開しているのは、大きく二系統です。impactAsync(style) の打撃感(light / medium / heavy など)と、notificationAsync(type) の成功・警告・失敗フィードバック。どちらも「あらかじめ決められた、ごく短いパターンを一回鳴らす」ためのものです。

呼吸の波に必要なのは、これとは性質が違います。整理すると、足りないのは次の3点です。

  1. 持続: 数秒間、途切れず鳴り続ける連続的な振動
  2. 強さの変化: その数秒の中で、強さがなめらかに上下すること
  3. 位相の制御: 吸う・止める・吐くという局面を、秒単位で指定できること

expo-haptics はこのいずれも提供していません。プリセットを setTimeout で並べても、間に無音の隙間ができ、「波」ではなく「点線」になってしまいます。指先に呼吸を感じてもらうには、振動そのものを連続イベントとして設計し、その強さを時間に沿って曲線で動かす必要があります。それができるのが CoreHaptics です。

やりたいことexpo-hapticsCoreHaptics
一瞬のタップ感得意可能(transient イベント)
数秒続く連続振動不可continuous イベント
強さを時間でなめらかに変える不可パラメータカーブ
導入の手軽さ数行で完了ネイティブモジュールが必要

つまり選択は二者択一ではありません。タップ感は expo-haptics に任せ、呼吸の波だけを CoreHaptics で足す、という併用が現実的です。私自身もこの形に落ち着きました。

CoreHaptics の連続イベントとパラメータカーブ

CoreHaptics で呼吸を表現する鍵は、CHHapticEvent の continuous(連続) タイプと、CHHapticParameterCurve(パラメータカーブ)の組み合わせです。

連続イベントは「ここから何秒間、振動を持続させる」という指定ができます。そしてパラメータカーブは「その持続の間に、強さ(intensity)を時刻ごとにどう変えるか」を制御点で描きます。吸う息なら 0 からゆっくり立ち上げ、吐く息なら最大からゆっくり 0 へ落とす。この曲線こそが、呼吸の波の正体です。

intensity(強さ)と sharpness(鋭さ)はいずれも 0.0〜1.0 の値を取ります。sharpness を低くすると角の取れた、丸く柔らかい振動になります。瞑想アプリでは sharpness を 0.3 前後に抑えると、刺すような感触が消えて呼吸になじみます。

吸う4秒・止める7秒・吐く8秒、いわゆる 4-7-8 呼吸の1サイクルを、Swift で組み立てると次のようになります。

import CoreHaptics
 
func makeBreathPattern(inhale: Double, hold: Double, exhale: Double) throws -> CHHapticPattern {
    let sharp = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
 
    // 吸う: 強さ 0 → 0.8 へ立ち上げる連続イベント
    let inhaleEvent = CHHapticEvent(
        eventType: .hapticContinuous,
        parameters: [
            CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8),
            sharp
        ],
        relativeTime: 0,
        duration: inhale)
 
    let inhaleCurve = CHHapticParameterCurve(
        parameterID: .hapticIntensityControl,
        controlPoints: [
            .init(relativeTime: 0,      value: 0.0),
            .init(relativeTime: inhale, value: 1.0)
        ],
        relativeTime: 0)
 
    // 吐く: 強さ 0.8 → 0 へ落とす連続イベント(holdの分だけ後ろにずらす)
    let exhaleStart = inhale + hold
    let exhaleEvent = CHHapticEvent(
        eventType: .hapticContinuous,
        parameters: [
            CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8),
            sharp
        ],
        relativeTime: exhaleStart,
        duration: exhale)
 
    let exhaleCurve = CHHapticParameterCurve(
        parameterID: .hapticIntensityControl,
        controlPoints: [
            .init(relativeTime: exhaleStart,          value: 1.0),
            .init(relativeTime: exhaleStart + exhale, value: 0.0)
        ],
        relativeTime: 0)
 
    // hold(止める7秒)はイベントを置かない=無音。これが「息を止める」局面になる
    return try CHHapticPattern(
        events: [inhaleEvent, exhaleEvent],
        parameterCurves: [inhaleCurve, exhaleCurve])
}

ポイントは、止める局面に あえてイベントを置かないことです。無音そのものが「息を止めている」という体験になります。吸う・吐くの2つの連続イベントを、強さカーブで包む。これだけで、指先に呼吸の輪郭が現れます。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦expo-haptics の単発フィードバックでは作れない、数秒かけて強さが上下する連続的な触覚を CoreHaptics のパラメータカーブで設計できます
✦CHHapticEngine を Expo Modules API(Swift)で包み、React 側から inhale/hold/exhale の秒数を渡して呼べるネイティブモジュールを実装できます
✦エンジンの自動停止・フォアグラウンド復帰・端末非対応・低電力モードという、本番で必ず踏む4つの落とし穴への対処まで持っていけます
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

関連記事

⬡ 開発ツール2026-09-03
リンクを押してもアプリが開かないとき、疑うのは assetlinks.json の置き場所より先に署名鍵です
Android App Links が検証されずブラウザに吸われる原因の多くは指紋の食い違いです。keytool の実出力と、手元で照合できる小さな検査スクリプトの実行結果を添えて整理しました。
⬡ 開発ツール2026-08-14
ライセンス表示に載せる依存は、lockfile の dev/prod では決まりません
AI ビルダーが書き出したプロジェクトの依存を実際に分類し、ライセンス表示に載せる範囲をどう決めたかの記録です。copyleft の検出結果と配布物の境界の食い違いを、本番プロジェクトの実データで確かめました。
⬡ 開発ツール2026-05-23
Rork 生成 Expo アプリの Privacy Manifest を SDK 連鎖まで監査する — 提出前1日で洗い切る個人開発者の手順
Rork が生成する Expo アプリの依存 SDK を Privacy Manifest 観点で全数監査する手順です。Required Reason API の事前検出、ITMS-91053 と ITMS-91061 の切り分け、npm では見えない Pod を Podfile.lock から拾う照合まで、個人開発の運用としてまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます