ロードバイクの「サドル高」をスマホでサクッと計算するアプリを作ってみた話
ロードバイクに乗っている人なら一度は気になったことがあるはずです。「このサドルの高さ、本当に自分に合ってるんだっけ?」
ショップで一度測ってもらったきり、その後は特に見直していない、という人も多いのではないでしょうか。
今回、股下の長さとクランクの長さを入れるだけでサドルの適正な高さを計算してくれる、スマホ向けのシンプルなツールを作ってみました。この記事では、「こんなことができます」という紹介から始めて、後半では実際にどう作ったか、途中でハマった話まで、わりとがっつり書いていこうと思います。
こんなことができます
このアプリでできることを、技術的な話は抜きにしてざっくり紹介します。
① 股下の長さとクランクの長さを入れると、サドルの適正な高さが分かる
計算方法には3つの流派があるので、好きなものを選べるようにしてあります。「ルーム式」「ハンター・アレン式」「109%法」という、それぞれ少しずつ考え方の違う計算式です。どれが正解というものではなく、目安として使い分けられるようにしています。

② 計算結果が、自転車のイラストで見える化される
数字だけ「72.5cm」と言われてもピンと来ないので、簡単な自転車のイラストの上でサドルの位置が実際に動いて見える仕組みにしました。シートポストがニュッと伸びる、ちょっとした演出付きです。

③ 膝の角度を測って、さらに細かく微調整できる
式で出した数値はあくまで目安なので、実際にペダルを漕いだときの膝の曲がり具合を入力すると、「あと何mm上げる/下げるべきか」を提案してくれる機能も付けました。測り方が分かりにくいという声があったので、測定方法を説明するイラストも用意しています。

④ 計算した履歴が残って、タップで呼び戻せる
過去に計算した内容は自動的に保存されていて、一覧からタップするだけで、その時の入力値(股下・クランク長・計算方式・膝角度まで)をまるごと復元できます。「前回の設定、もう一回試したい」というときにゼロから入力し直さなくて済みます。

⑤ 今後、サドルの前後位置を測る機能も追加予定
サドルの高さだけでなく、前後の位置(フォア・アフト)もフィッティングでは重要な要素です。KOPS法という伝統的な測定方法をベースにした計算機能を、現在別ページとして準備中です。ハンバーガーメニューから行き来できる、複数ページの構成に育てているところです。

ここから先は、実際にどう作っていったかという、わりと技術寄りの話になります。興味のある方だけ読み進めてください。
使った技術
最初はAngularのstandaloneコンポーネントとして作り始めました。1コンポーネントで完結する小さな構成です。
@Component({
selector: 'app-saddle-calculator',
standalone: true,
imports: [FormsModule, CommonModule],
templateUrl: './saddle-calculator.html',
styleUrl: './saddle-calculator.css',
})
export class SaddleCalculator {
// ...
}
途中、「せっかくなのでReactの勉強も兼ねて作り直したい」という話になり、全く同じ機能をReactでも実装しました(この経緯は後述します)。今はAngular版をメインに、機能追加を続けています。
計算式の中身
サドル高の計算式は、股下長に係数をかけるだけのシンプルなものです。
| 方式 | 係数 | 特徴 |
|---|---|---|
| ルーム式 | 股下 × 0.883 | ロードバイクで最も一般的な基準 |
| ハンター・アレン式 | 股下 × 0.885 | ルーム式よりわずかに高め |
| 109%法 | 股下 × 1.09 | 別の基準点から算出する方式 |
let height = this.inseam * method.coefficient;
// クランク長補正(170mmを基準に、長い/短い分だけ調整)
height -= (this.crankLength - 170) / 10;
this.saddleHeight = Math.round(height * 10) / 10;
クランク長が標準の170mmより長ければ低めに、短ければ高めに補正しています。
自転車のイラストで「見える化」する
数値をただ表示するだけだと味気ないので、自転車のイラストに反映させることにしました。ここが、今回の開発で一番試行錯誤した部分です。
最初のバージョンは「自転車っぽくない」と言われた
最初に描いたイラストは、正直に言うと不評でした。フラットなハンドル、太いタイヤ、なんとなくぎこちないフレーム形状……率直に「ロードバイクっぽくない」というフィードバックをもらいました。
そこから何度も手直ししました。
- ハンドルを、ステム+曲線パスでドロップハンドルらしい形状に
- 太かったタイヤを、タイヤとリムの二重線にして細身のロードタイヤに
- ホイールサイズを1.5倍に拡大
- ヘッドチューブを短くして、フロント周りを引き締めた印象に
- トップチューブを地面と水平になるよう座標を調整
数値をひたすら微調整しながら「それっぽさ」を詰めていく、地味だけど楽しい作業でした。

SVGの<line>はCSSアニメーションが効かない
もう一つ、ハマった技術的な話があります。計算結果に応じてサドルの位置(シートポスト)がスッと動くアニメーションを付けたかったのですが、SVGの<line>要素はx1/y1/x2/y2をCSSのtransitionでアニメーションさせても、多くのブラウザで瞬時にジャンプしてしまいます。
解決策は、伸縮させたい部分を<line>ではなく<rect>にすることでした。rectのx/y/width/heightはCSSの幾何プロパティとして扱われるため、こちらは問題なく滑らかにアニメーションします。
.seat-post {
fill: #555555;
transition: y 0.6s ease, height 0.6s ease;
}
同じ「線を伸び縮みさせたい」という要望でも、要素の選び方一つでハマるかどうかが変わる、という学びでした。
膝角度による微調整と、測り方の勘違い
式だけだと股下という単一の指標しか見ないので、もう一歩踏み込んだ調整方法として、膝の曲がり角度を使った微調整機能を追加しました。クランクを6時位置(ペダル最下点)にして、股関節・膝・かかとを結んだ線の曲がり具合が25〜35度に収まっているかを見る、という方法です。
const diff = this.kneeAngle - this.KNEE_ANGLE_TARGET;
this.kneeAdjustmentMm = Math.round(diff * this.MM_PER_DEGREE);
「膝角度1度の差 ≈ サドル高1mm」という経験則を使った、簡易的な近似計算です。
この測り方を説明するイラストも作ったのですが、ここで自分でも勘違いに気づく一幕がありました。角度を示す弧を、最初は「太もも」と「すね」の間(=膝関節そのものの内角)に描いていたのですが、これは間違いで、正しくは「脚がまっすぐ伸びたと仮定した場合の点線」と「実際のすね」の間に描く必要がありました。前者だと見た目の角度がかなり広くなってしまい、実際に測っている数値と一致しません。

さらに、自転車全体のイラストが自転車の右側から見た構図になっていたので、膝が曲がる向き(前方=画面の右側に張り出す)も、そちらに合わせて反転修正しました。図を作りながら、自分の理解のズレにも気づけたのは、副産物的に面白い体験でした。

履歴機能と、リリース後に気づいた2つのバグ
計算した内容を保存して、あとで見返せる履歴機能も作りました。ここでも実際に触ってみて初めて気づいたバグが2つありました。
1つ目:履歴が0件のときにセクションごと消えてしまう
履歴セクション全体を*ngIf="history.length > 0"で囲っていたため、初めて使うときや全件削除した直後は、履歴欄そのものが画面から消えてしまい、「機能があることに気づけない」状態になっていました。セクション自体は常に表示し、中身が空のときは案内メッセージを出す形に直しました。
2つ目:膝角度の微調整結果が履歴に保存されていない
履歴をタップして復元する機能を作った直後、「あれ、膝角度の入力欄が空に戻る」と気づきました。履歴のデータ構造に、そもそも膝角度そのものを保存するフィールドがなかったのです。データ構造にkneeAngleとkneeAdjustmentMessageを追加して、復元時にそのまま呼び戻せるようにしました。
どちらも、機能を作っている最中には気づかず、一通り動くようになってから実際に自分で使ってみて初めて発覚したバグでした。「動く」と「使いやすい」の間には、やっぱりギャップがあるものだと実感しました。
デプロイでハマった話(2回も同じ原因で)
作ったアプリは、Xサーバーのレンタルサーバーに置くことにしました。VS Codeで開発しているので、GitHubにpushしたら自動でビルド・デプロイされる仕組み(GitHub Actions)を組んでみることにしました。
ここで最初のエラーに遭遇します。
npm error The `npm ci` command can only install with an existing package-lock.json
原因はシンプルで、package-lock.jsonがリポジトリに含まれていなかったことでした。npm installで生成してコミットすることで解決しました。
さらに、リポジトリの構成が「ルート直下にAngularプロジェクトのフォルダがある」形だったため、GitHub Actionsのワークフロー側でも作業ディレクトリを明示的に指定する必要がありました。
defaults:
run:
working-directory: ./bike-fit-app
無事デプロイできるようになったのですが、後日「せっかくなのでReactでも作ってみたい」という話になり、React版をAWS Amplifyにデプロイしようとしたところ……全く同じnpm ciのエラーに再びぶつかりました。今度はAmplify側でのモノレポ設定(appRootの指定)が必要で、既視感のあるデバッグをもう一度やる羽目になりました。一度ハマった原因は、環境が変わっても形を変えて出てくるものだと痛感した出来事です。
Reactへの移植
Angular版が一通り完成した後、「Reactの勉強も兼ねて、同じものを作ってみよう」ということになり、全機能をReactに移植しました。(実はAWSの無料枠を使い切ったのでこのブログの空きスペースで公開することにしました。😅)
Viteベースの構成で、フックを使った書き方に置き換えています。
変換のポイントはこんな感じでした。
[(ngModel)]→useState+value/onChange*ngIf→{条件 && <div>...}*ngFor→.map()- Angularの属性バインディング
[attr.y]="seatTopY"→ JSXではy={seatTopY}と波括弧に入れるだけ
フレームワークが変わっても、SVGの<line>問題のような「ブラウザ・DOM側の制約」はそのまま引き継がれるので、Angular版で得た教訓がそのまま活きた場面もありました。
複数ページへの拡張、そして次にやること
サドルの前後位置(フォア・アフト)を測る、KOPS法という伝統的な方法を使った機能も計画中です。この機能は今までの単一ページの構成には収まりきらないと判断し、Angular Routerを導入して複数ページ構成に変更しました。ハンバーガーメニューを押すとドロワーが出てきて、ページを切り替えられるようにしています。製作途中なのですが基本機能は実装済みです。
製作中というのはブラッシュアップさせる予定だからです。入力ミスをしたら1つ前に戻るなどの「あったらいいな~」を追加予定です。
KOPS法自体は、膝のお皿の下から下げ振り(重りを付けた紐)を垂らして、ペダル軸とのズレを見るという、これも実際に体を使って測る方式です。膝角度の機能と同じ設計思想で、「測ったズレの量から、調整方向と量を提案する」という形で実装を進めています。
サドル高、膝角度、そして次はサドルの前後位置と、少しずつバイクフィッティングの要素を増やしてきましたが、まだハンドルやステムまわり(リーチ・ドロップ)は手つかずです。このあたりは明確な公式が一つに定まっていない分野なので、どう実装するかはもう少し検討が必要そうです。
おわりに
「サドルの高さを計算するだけの小さなツール」のつもりで作り始めましたが、自転車のイラストを作り込んだり、測り方を説明する図で自分の勘違いに気づいたり、デプロイで同じ罠に2回ハマったりと、振り返ってみると意外と密度の濃い開発になりました。
今後もサドル前後位置、ハンドル・ステムまわりと、機能を少しずつ育てていく予定です。進捗があれば、またこのブログで報告しようと思います。

