2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめてWeb開発をする人のためのチュートリアル #2

Last updated at Posted at 2025-10-31

はじめに

まず初めに、第1回を読んでいただいた方、本当にありがとうございました。

前回の第1回のチュートリアル記事が予想以上に好評だったので、第2弾を作りました!
第1回では何もWeb開発の知識がない方でも理解できるように最低限の内容で構成しましたが、今回は前回触れられなかったJavaScriptのコードを中心に扱っていこうと思います。

計算部分が高校数学レベル(文系でも大丈夫・対数のみ理解できればok)になっているので、分からない場合はご自身で調べてみてください。
また、今回も前回と同様にプログラミング初心者の方でも分かるようにできるだけ噛み砕いて説明します。

前回の記事

第1回の内容を理解している前提で説明するので、まだ読んでいない場合や忘れてしまった場合は以下の記事を読んでから読み進めてください。
(公開後、記事の最後に追加した部分があるので、読んでいない方はそちらも読んでおくと良いです。)

今回の目標

前回は税率計算機を実装しましたが、今回は0以上99以下の算用数字に対して日本語での読み方を表示するWebアプリを実装します。

例えば、25と入力したら以下のようになります。

sample.png

テーマ

  • for文
  • switch文
  • 配列
  • addEventListner

本当はDOMの操作やJavaScriptによるCSSの切り替え、JavaScriptのobjectなども扱いたかったんですけど、入れられませんでした。

目次

Step1: とりあえず最低限のパーツを用意する
Step2: ボタンの見た目を調整
Step3: 全体の位置を調整
Step4: 出力予定場所の見た目を調整
Step5: class名と関数名が同じになっていたので修正 & translate_process関数の実装
Step6: 0〜9に対応させる
Step7: for文を使ってみる
Step8: 0〜9ならできるようになった!
Step9: 数字の範囲処理、コードの簡略化など
Step10: 入力フォームの幅を調整
Step11: 出力をid="output"に変更
Step12: id="output"周りを改修
Step13: 桁ごとに分解できるようにする
Step14: 10〜99の表示が行えるようにする
Step15: onclickをaddEventListenerに変更
Step16: ボタンのradiusと入力フォームの幅を修正
Step17: Google Fontsを使ってみる
Step18: cssファイルをフォルダにまとめる

実際に開発する

※ Live Serverなどによる実行の確認は適宜行ってください。
※ 今回は前回とは違い、実際に私が開発したときを再現して行うので途中でミスがありますが最後のステップまでには直していますので気にせず読み進めてください。

Step1: とりあえず最低限のパーツを用意する

好きな場所にフォルダを作成して、そのフォルダをVSCodeで開き以下のファイルを作成してください。(中は何も書かないで大丈夫です。)

(任意のフォルダ)
├─ index.html
├─ script.js
└─ style.css

index.htmlを開き、前回同様に「! + Enter」でテンプレートを作成します。
なお、langは以下の記事に従ってデフォルトがjaになっています。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

今回もBootstrap5.3を使うので、その読み込みとscript.jsとstyle.cssの読み込みができるようにします。また、titleも変えました。(titleはご自由に設定してください。)

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-   <title>Document</title>
+   <title>howToSayInJapanese</title>

+   <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

+   <link rel="stylesheet" href="style.css">
</head>
<body>
+   <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
+   <script src="script.js"></script>
</body>
</html>

読み方を出力させたい数字を入れるための入力フォーム、ボタン、出力場所を用意しておきます。ただ、これは仮なのでこの後で調整します。(translate関数はまだ定義していませんが、先に設定しています。)

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>howToSayInJapanese</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

    <link rel="stylesheet" href="style.css">
</head>
<body>
+   <input type="number" min="0" placeholder="number" id="num">
+   <button type="button" onclick="translate()">translate</button>
+   <p id="output"></p>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>

実行してみると、以下のようになると思います。(まだtranslate関数を実装していないので、ボタンをクリックするとエラーが出ると思います。)

step1_1.png

Step2: ボタンの見た目を調整

前回はBootstrap5.3のボタンをそのまま使ったのですが、今回は自作していこうと思います。

translateクラスに反映させるcssを作ろうと思うので、index.htmlとstyle.cssを以下のように変更します。(cssの差分表示がうまくいかないみたいなので、変更部分が分かりづらいですがそのままコードを載せます。)

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>howToSayInJapanese</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

    <link rel="stylesheet" href="style.css">
</head>
<body>
    <input type="number" min="0" placeholder="number" id="num">
-   <button type="button" onclick="translate()">translate</button>
+   <button type="button" class="translate" onclick="translate()">translate</button>
    <p id="output"></p>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>
style.css
.translate{

}

黒い枠線をつけて角を丸くさせ、背景を水色にしようと思います。

style.css
.translate{
   border: 0.5mm solid black;
   border-radius: 5%;
   background-color: rgb(237, 246, 255);
}

※ 間違えてborder-radiusを5%にしているせいで、角の丸みが不自然になっていますが、Step17で原因の説明と修正を行いますので一旦保留しておいてください。

borderは枠線に関する設定をまとめて行うことができます。(もちろん別々に設定することもできます。例えば、solidはborder-styleで設定できます。)
borderでの設定を詳しく説明します。

1 2
0.5mm 太さを0.5mmにする
solid 実線にする
black 枠線の色を黒にする

solidは以下のように変更することもできます。

1 2
solid 実線
dashed 破線
dotted 点線
double 二重線
none 枠線なし

背景にはrgbで色をつけています。
ここまでで、こんな感じになっていればokです。

step2_1.png

Step3: 全体の位置を調整

入力フォームとボタンが左端にいるので、
step3_1.png
このようにします。

ということで、まずindex.htmlの入力フォームとボタンと出力場所をdivタグで囲んでclassにcontainer-inputを設定します。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>howToSayInJapanese</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

    <link rel="stylesheet" href="style.css">
</head>
<body>
+   <div class="container-input">
        <input type="number" min="0" placeholder="number" id="num">
        <button type="button" class="translate" onclick="translate()">translate</button>
        <p id="output"></p>
+   </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>

style.cssにcontainer-input用の設定を追加します。また、translateに軽微な変更を加えます。

style.css
.translate{
    margin-top: 1%;
    border: 0.5mm solid black;
    border-radius: 5%;
    background-color: rgb(237, 246, 255);
}

.container-input{
    margin-top: 10%;
    display: flex;
    align-items: center; /*.縦 */
    justify-content: center; /* 横 */
    flex-direction: column;
}

ボタンを入力フォームの下に置くので、上側に余白がないとフォームとくっついてしまうのでtranslateにmargin-topを1%に設定しています。実行しているデバイスやウィンドウサイズによって余白が変わると思うので、好みの余白に設定してください。

ちなみにこの1%は何なのかというと、ここではmargin-topで上下方向なのでウィンドウの高さの1%を指します。つまり、margin-leftでは左右方向なのでウィンドウの幅に対する割合になります。
メリットとしてはウィンドウサイズが変わっても、ある程度対応できることですが、デメリットとしてPCとスマホで幅があまりにも違うので余白がほぼなくなることもあります。この場合、pxでサイズを固定させたり、@mediaというcssの機能を使ってウィンドウサイズごとに適用する設定を変えるなど工夫する必要があります。

※ ボタンのサイズやフォームのサイズなどに使うwidthやheightにも%が使えます。

container-inputの説明をします。上側に10%の余白を付け、縦横の配置を自由にできるようにするためにdisplayをflexにします。
displayの設定は以下の通りたくさんあります。(ChatGPT作成)

カテゴリ display の値 説明
ブロック系 block ブロック要素。幅は親の幅いっぱい、改行が入る(例:
inline インライン要素。幅は内容に応じて、改行は入らない(例:)
inline-block インライン要素のように横に並ぶが、ブロックのように幅・高さを指定可能
flow-root 新しいブロックコンテキストを作る。clearfix の代替として使用
フレックス系 flex フレックスコンテナになる。子要素がフレックスアイテムとして横並び(デフォルト)
inline-flex flex と同じだが、インライン要素として扱われる
グリッド系 grid グリッドコンテナになる。行と列でレイアウト
inline-grid grid と同じだが、インライン要素として扱われる
テーブル系 table ブロック要素としてテーブルボックスを生成
table-row テーブル行
table-cell テーブルセル
table-column テーブル列
table-caption テーブルのキャプション
特殊・非表示系 none 表示しない。DOM には残るがレンダリングされない
contents 親要素のボックスは消えるが、子要素は残る
list-item リストアイテムとして表示(例:)
run-in ブロックとインラインを文脈で切り替える(未対応ブラウザあり)

blockとnoneで表示/非表示を切り替えるなど、flex以外にもよく使うものがあります。

今回のflexで作ることができるフレックスアイテムは簡単に縦横の整列ができます。
flexに指定したことでalign-itemsで縦方向に中央、justify-contentで横方向に中央にします。なお、おそらくcontainer-inputの高さを設定していないので実際に表示される場所が縦方向に中央になっていません。container-input内にheightをpxで指定してあげると縦方向に中央になります。(今回、縦方向に中央になっていませんが、ボタンとフォームの幅に影響しているので付けておきます。)

最後、flex-direction: columnでボタンとフォームを縦に並べます。

Step4: 出力予定場所の見た目を調整

出力場所の文字サイズや位置などを調整します。
index.htmlとstyle.cssを以下のように変更します。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>howToSayInJapanese</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container-input">
        <input type="number" min="0" placeholder="number" id="num">
        <button type="button" class="translate" onclick="translate()">translate</button>
-       <p id="output"></p>
+       <h5 id="output">じゅういち</h5>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>
stye.css
.translate{
    margin-top: 1%;
    border: 0.5mm solid black;
    border-radius: 5%;
    background-color: rgb(237, 246, 255);
}

.container-input{
    margin-top: 10%;
    display: flex;
    align-items: center; /*.縦 */
    justify-content: center; /* 横 */
    flex-direction: column;
}

#output{
    margin-top: 3%;
    color: rgb(255, 33, 70);
    font-weight: bold;
}

index.htmlの説明からしますが、pタグだと文字サイズが小さくfont-sizeで設定しても良いんですけど面倒だったのでh5タグにしました。(普通h5はタイトルに使うので、本当はpタグにしてfont-sizeを使う方が良いのかな...)
見た目の調整を行いたいので、適当に「じゅういち」という文字列を表示させています。

style.cssでoutputというid用に設定を行います。上側の余白、文字の色を設定し、太字にするためにfont-weightを使いました。(好きに設定してください。)

ここまでで、以下のようになっているかと思います。

step4_1.png

Step5: class名と関数名が同じになっていたので修正 & translate_process関数の実装

JavaScriptを触っていきます。

最初予定していたtranslate関数はclassにつけたtranslateと名前が同じにしてしまったので、以下のように変えます。(変えないと正常に動作しません。)

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>howToSayInJapanese</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container-input">
        <input type="number" min="0" placeholder="number" id="num">
-       <button type="button" class="translate" onclick="translate()">translate</button>
+       <button type="button" class="translate" onclick="translate_process()">translate</button>
        <h5 id="output">じゅういち</h5>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>

ボタンの動作確認のために以下のようにtranslate_process関数を実装しておきます。

script.js
function translate_process(){
    console.log("translate関数を実行");
}

実行してボタンを押してみると、デベロッパーツールのConsoleにログが出てくると思います。

Step6: 0〜9に対応させる

目標としては「0〜99の読み方を表示させる」なんですけど、急に2桁に対応させるのは難しいので、とりあえず1桁に対応させます。

ここで、前回使わなかった「配列」を使います。
配列とは何かというと、複数のデータを1つの変数にまとめる方法の1つになります。
例えば、

let letters = [1, 2, 3];

みたいな感じです。配列は左の要素から順に0番目, 1番目, 2番目, ...として扱うので、この例の配列で2を取り出す場合は

letters[1]

と書きます。
操作は色々できます。(ChatGPT製の表)

項目 説明
配列の作り方(リテラル) [] を使って配列を作る const fruits = ["りんご", "みかん", "バナナ"];
配列の作り方(コンストラクタ) new Array() を使って作る const nums = new Array(1,2,3);
要素の取得 インデックスでアクセス(0始まり) fruits[0]; // "りんご"
要素の変更 インデックスに代入 fruits[1] = "メロン";
配列の長さ length プロパティ fruits.length; // 3
末尾に追加 push() fruits.push("ぶどう");
先頭に追加 unshift() fruits.unshift("いちご");
末尾を削除 pop() fruits.pop();
先頭を削除 shift() fruits.shift();
任意位置の操作 splice(start, deleteCount, item1, ...) fruits.splice(1, 1, "メロン"); // 1番目を削除して挿入
配列のループ for, for...of, forEach() fruits.forEach(f => console.log(f));
条件で抽出 filter() nums.filter(n => n % 2 === 0); // 偶数だけ
各要素の変換 map() nums.map(n => n*2); // 倍にする
配列を1つにまとめる reduce() nums.reduce((acc, n) => acc+n, 0); // 合計
条件検索 find() nums.find(n => n > 3); // 最初の4
条件判定 some() / every() nums.some(n => n > 4); // true

new Array()を使う際に注意が必要です。数字の要素が1つ(長さが1)の配列を作る場合、例えば要素が5だけの配列を作る場合

new Array(5)

と書いてしまうと、長さ5の空配列が作成されてしまうので、

new Array(1).fill(5)

のように書きます。

表にもありますが、要素数(長さ)はletters.lengthのように取得します。また、C言語などとは異なり型を混在させたり、配列の長さを後から変えることもできます。

要素の位置を表す番号を「インデックス(index)」と言います。

というわけで、話を戻しますと今回は出力する日本語のテキストを配列で管理します。
script.jsを以下のように変更しましょう。

script.js
+ const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう"];

function translate_process(){
-   console.log("translate関数を実行");
+   console.log(parts[0]);
}

これで実行し、ボタンを押すとConsoleに「ゼロ」と出力されると思います。これはpartsの0番目の要素が「ゼロ」で、それを取得してconsole.logで出力しているからですね。

Step7: for文を使ってみる

それでは、今度はparts内のすべての要素を出力してみます。
もちろん、

console.log(parts[0]);
console.log(parts[1]);
console.log(parts[2]);
console.log(parts[3]);
.
.
.

とやっても良いのですが、コードが長くなりますし同じような処理なのにわざわざ何回も書かないといけないのは面倒なので、for文を使って実装します。

for文は簡単に言えばループ処理の一種です。例えば

let num = 0;
for(let i = 1; i < 4; i++){
    num += i;
}

と書くと、初期値が0のnumに対して、1〜3を順に足す処理になります。より詳しく説明しますと、

1 2
let i = 1; iを1として初期化する(iの最初の数を1に設定する)
i < 4; iが4より小さい間だけ繰り返す
i++ for文内の一回分の処理が終わったらiを1足してもう一度実行する

※ なお、このiはfor文内でしか使用できません。

つまり、1回目の処理ではiは1なのでnum += i;num += 1;ということになります。2回目は1回目の処理の後にiが1増えているので、num += i;num += 2;になります。

オブジェクトに対して使う場合は、このようにキーを順番に取ることもできます。
(何言っているのか分からない人は忘れても大丈夫です。いつか使うときに理解してもらえればokです。)

const obj = { a: 1, b: 2, c: 3 };

for (let key in obj) {
  console.log(key, obj[key]);
}

もう一つのループ処理として、while文というのがあります。
while文はある条件が成り立っている間、ずっとループさせるというものでfor文とは使い分ける必要があります。
例えば、

let num = 0;
while(num < 5){
    num += 1;
}

とすると、numが5より小さい間1を足し続けるという処理になります。

(Webに限らずどのプログラミング言語でも言えることですけど、)ループ処理で注意が必要なのは、for文もwhile文も関係なく無限ループに陥ることです。例えば、

for(let i = 0; i >= 0; i++){
    console.log("無限ループしてるよ");
}

const word = 'Hello World!';
while(word == 'Hello World!'){
    console.log("無限ループしてるよ");
}

のようにすると、一生ループから抜けられなくなります。こうなってしまうと、Webサイトがフリーズしてしまいます。

ではfor文でpartsの要素を取ってきます。script.jsを以下のように変更しましょう。

script.js
const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう"];

function translate_process(){
+   for(let i = 0; i < parts.length; i++){
        console.log(parts[i]);
+   }
}

ここではiを0で初期化して、iがpartsの長さ(今回は10)より小さい間はループし続ける(ループするごとにiを増やす)ようにしています。そして、そのiconsole.log(parts[i]);に入れることで、ループするごとにi番目のpartsの要素をログとして出力できるようになっています。

ボタンを押すと、以下のように一気にすべての文字列が出力されると思います。
step7_1.png

Step8: 0〜9ならできるようになった!

それでは入力された数字に対応する文字列を出力するようにコードを変えていきましょう。
今回のparts配列の要素の並びを見てください。配列のインデックスと同じ数字の文字列が入っていると思います。つまり、2番目の要素を取ってくれば「に」になっているということです。つまり、script.jsは

script.js
const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう"];

function translate_process(){
    const num = document.getElementById("num").value;

-   for(let i = 0; i < parts.length; i++){
-        console.log(parts[i]);
-   }

+   if(num){
+       alert(parts[num]);
+   } else {
+       alert("数字を入力してください");
+   }
}

とすれば良いですね。前回の税率計算機のときと同じでidがnumの要素の値を取得してnumに入れます。もしnumがあるならnum番目のpartsの要素をalertで返し、numに何も入っていないなら入力を促すメッセージを出力するようにします。
(console.logだと開発者しか出力を確認できないのでalertにしました。)

Step9: 数字の範囲処理、コードの簡略化など

さぁ、ここで問題が出てきました。皆さんは気付いたでしょうか?

問題点

  • 用意していない数が入力されるとundefinedになる
  • 小数が入力されるとundefinedになる

undefinedは値が未定義だということを指し、nullは値がないこと(空)を指します。似ていますが違うので注意してください。

解決

このステップではscript.jsのみ変更するのですがたくさんのコードを追加することになるので、最後にコード全文を載せておきます。
一緒にコードを書いて記事を追っている場合は、このステップの最後まで読んでからコードを変更することをオススメします。

1つ目の問題点「用意していない数が入力されるとundefinedになる」は入力できる数の最小値と最大値を設定すれば解決できます。
まず、

const min_max = [0, 9];

これで最小値と最大値を管理します。0番目の要素が最小値、1番目の要素が最大値です。ここは後から上書きされたら困るので、constに設定しています。
これを入力フォームのinputタグに設定します。前回はinputタグに直接min="0"と設定したと思いますが、今回はJavaScript側から設定します。

ちょっとその前に...
この後も考えると、document.getElementByIdを何度も呼び出すことになりそうで、document.getElementByIdを何度も書くのは面倒なのでコードを簡略化させようと思います。ということで、

function getEle(id){
    return document.getElementById(id);
}

を実装します。getEle(id)idは何かと言うと「引数」と言って関数に値を渡すことができます。また、returnは関数を呼び出した場所に値を返すものになります。つまり、先ほどの

const num = document.getElementById("num").value;

getEle関数を用いると

const num = getEle("num").value;

と書けるということです。"num"getEle関数に渡すとdocument.getElementById("num")が返ってきます。

これを使うと、入力フォームの最小値・最大値の設定は

const [min, max] = min_max;
getEle("num").min = min;
getEle("num").max = max;

と書けます。const [min, max] = min_max;はどういうことかというと、min_max = [0, 9]に対応するようにminに0を、maxに9を代入します。(左右で対応)
もちろんインデックスで指定しても良いんですけどね。

その値をそれぞれgetEle("num").mingetEle("num").maxに代入します。.minと.maxを使うことで、min="0"max="9"が設定できるということです。

また、このminとmaxはinputタグにマウスポインターを合わせると現れる矢印ボタンで値を変えるときにしか反映されず、直接入力してしまうと想定している範囲外に出ることができるので、最後の出力の前にif文でconst num = getEle("num").value;が範囲内にあることを確認します。

if(num >= min_max[0] && num <= min_max[1]){
    alert(parts[Math.floor(num)]);
} else {
    alert("数値が範囲外です。");
}

2つ目の問題点、「小数が入力されるとundefinedになる」を解決します。これは先ほどより断然簡単で、入力された数値を切り捨てれば解決です。JavaScriptには切り捨てる関数としてMath.floor関数があり、

Math.floor(5.2) // 5

になります。今回だと

parts[Math.floor(num)]

のようにします。

ここまでの変更をすべて反映させると、以下のようになります。

script.js
+ // 対応させる範囲
+ const min_max = [0, 9];
+
+ // 変換データ
const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう"];

+ // getElementByIdの処理を簡略化する
+ function getEle(id){
+     return document.getElementById(id);
+ }
+
+ // 入力フォームの設定
+ const [min, max] = min_max;
+ getEle("num").min = min;
+ getEle("num").max = max;
+
+ // 実際に変換する処理
function translate_process(){
-   const num = document.getElementById("num").value;
+   const num = getEle("num").value;

    if(num){
-       alert(parts[num]);
+       if(num >= min_max[0] && num <= min_max[1]){
+           alert(parts[Math.floor(num)]);
+       } else {
+           alert("数値が範囲外です。");
+       }
+       
+       getEle("num").value = "";
    } else {
        alert("数字を入力してください");
    }
}

Step10: 入力フォームの幅を調整

ここまでのコードを実行してみると、あれ?

step10_1.png

入力フォームの幅、めちゃくちゃ小さくなってない?

そう、実は最小値と最大値を設定したことで、それに合わせて幅が小さくなってしまったのです。1桁分の幅で良いかもしれませんが、placeholderが読めないし、明らかに不自然なので、幅を設定することで修正します。

#numの項目をstyle.cssに追加しました。
※ この幅が気に入らなかったので、Step16で調整しています。

style.css
.translate{
    margin-top: 1%;
    border: 0.5mm solid black;
    border-radius: 5%;
    background-color: rgb(237, 246, 255);
}

.container-input{
    margin-top: 10%;
    display: flex;
    align-items: center; /*.縦 */
    justify-content: center; /* 横 */
    flex-direction: column;
}

#output{
    margin-top: 3%;
    color: rgb(255, 33, 70);
    font-weight: bold;
}

#num {
    width: 100px;
}

Step11: 出力をid="output"に変更

ここまでalertで出力を出していたのですが、ここでようやく画面上に表示させるようと思います。

まず、いらない「じゅういち」を削除しましょう。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>howToSayInJapanese</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container-input">
        <input type="number" min="0" placeholder="number" id="num">
        <button type="button" class="translate" onclick="translate_process()">translate</button>
-       <h5 id="output">じゅういち</h5>
+       <h5 id="output"></h5>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>

また、script.jsを以下のように変更してid="output"に出力を入れるようにします。

script.js
// 対応させる範囲
const min_max = [0, 9];

// 変換データ
const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう"];

// getElementByIdの処理を簡略化する
function getEle(id){
    return document.getElementById(id);
}

// 入力フォームの設定
const [min, max] = min_max;
getEle("num").min = min;
getEle("num").max = max;

// 実際に変換する処理
function translate_process(){
    const num = getEle("num").value;

    if(num){
        if(num >= min_max[0] && num <= min_max[1]){
-           alert(parts[Math.floor(num)]);
+           getEle("output").innerHTML = parts[Math.floor(num)];
        } else {
            alert("数値が範囲外です。");
        }

        getEle("num").value = "";
    } else {
        alert("数字を入力してください");
    }
}

例えば9を入力して出力させると、

step11_1.png

となったと思います。

Step12: id="output"周りを改修

数値が入力されていないときと範囲外になったときのエラーメッセージもalertを使わずに画面上に表示させたいと思います。

と、その前に...

また同じようにgetEle("output").innerHTMLで出力の処理を実装しても良いのですが、毎回この長いコードを書くのは嫌だったので関数化させます。

function output_content(content){
    getEle("output").innerHTML = content;
}

この関数は引数として出力内容を受け取って、それをoutputに表示させる関数になっています。
これを使ってコードを変えると...

script.js
// 対応させる範囲
const min_max = [0, 9];

// 変換データ
const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう"];

// getElementByIdの処理を簡略化する
function getEle(id){
    return document.getElementById(id);
}

+ function output_content(content){
+   getEle("output").innerHTML = content;
+ }

// 入力フォームの設定
const [min, max] = min_max;
getEle("num").min = min;
getEle("num").max = max;

// 実際に変換する処理
function translate_process(){
    const num = getEle("num").value;

    if(num){
        if(num >= min_max[0] && num <= min_max[1]){
-           getEle("output").innerHTML = parts[Math.floor(num)];
+           output_content(parts[Math.floor(num)]);
        } else {
-           alert("数値が範囲外です。");
+           output_content("数値が範囲外です。");
        }

        getEle("num").value = "";
    } else {
-       alert("数字を入力してください");
+       output_content("数字を入力してください。");
    }
}

Step13: 桁ごとに分解できるようにする

それでは2桁に対応させようと思います。

「どうやって2桁に対応できるようにするのか?」と思うかもしれませんが、

入力:18

1と8に分解

出力:じゅうはち

みたいにします。
分解後の処理の前に分解する処理を実装しないといけないので、このステップでは分解処理を実装します。
※ 最後にすべての変更を反映させたコードを載せます。

まず、対応する数字の範囲を0〜9から0〜99に変更します。また、変換先の文字列データに "じゅう" を加えます。

const min_max = [0, 99];
const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう", "じゅう"];

translate_process関数を以下のようにします。

function translate_process(){
    const num = getEle("num").value;

    if(num){
        if(num >= min_max[0] && num <= min_max[1]){
-           output_content(parts[Math.floor(num)]);
+           // output_content(parts[Math.floor(num)]);
+           let num_cache = num;
+           let num_inTheDigit;
+           
+           for(let i = Math.floor(Math.log10(Math.floor(num))); i >= 0; i--){
+               num_inTheDigit = Math.floor(num_cache / 10 ** i);
+               num_cache -= num_inTheDigit * 10 ** i;
+               console.log(`${10 ** i}の位: ${num_inTheDigit}`);
+           }
        } else {
            output_content("数値が範囲外です。");
        }
        
        getEle("num").value = "";
    } else {
        output_content("数字を入力してください。");
    }
}

何か急に複雑そうなコードが追加されたと思うかもしれませんが、そうでもありません。順番に見ていきましょう。

分解処理を実装したいので、表示する部分output_content(parts[Math.floor(num)]);はコメントアウトしておきます。

では処理の部分を見ていきます。

let num_cache = num;
let num_inTheDigit;

num_cacheは次の分解処理で分解させる数字を入れる変数です。例えば、今回は対応させませんが3桁の数「123」を分解する場合、最初の分解処理で「1」と「23」に分け、次の分解処理で「23」を「2」と「3」に分解させます。
(上の位から分解する理由は、出力する文字列を作るときに上の位から作るようにさせたいからです。その方がプログラムを書く側も分かりやすいですしね。例えば、18を「じゅうはち」とする場合、下の位から分解すると、「はち」に「じゅう」を前に繋げる処理を書かないといけなくなりますよね)
分解するときは最初の方に出てきたfor文を使うわけですが、そのときに分解途中の数、今回の例だと「123」の「23」みたいな数を次の分解処理に渡す必要があります。これを担っているのがnum_cacheということになります。

「123」の例の場合は、num_cache
「123」→「23」→「3」
と変化します。

num_inTheDigit変数は今分解した位の数を入れる場所です。「123」を「1」と「23」に分解した場合、num_inTheDigitには「1」が入ります。ただ、処理前はまだ何も調べていないので空のまま定義しています。

次にfor文を説明します。「123」の例でも説明しましたが、上の位から見るためにループに使うiは今から分解する数の桁数を初期値として、i--でiを小さくして1の位まで処理させます。
ただ、ここで注意が必要なのは、分解する数numに底が10の対数を取った数を切り捨てした整数をiの初期値にするので最後のiは0になります。

つまり「123」の場合は $\log_{10}123\approx2.089905111$で、これを切り捨てて2になるので、iの初期値は2になります。そこから1(10の位), 0(1の位)と下げていき、分解していきます。

JavaScriptでは$\log_{10}$がMath.log10として最初から用意されているので、これを使いました。

for文の中を見ていきます。
num_inTheDigitに今分解する対象になっているnum_cacheを10のi乗で割った数を切り捨てた数を格納します。

**は累乗を表すので、10**iは10のi乗になります。

何言っているのか分からない人のために例を使って説明すると、num_cacheに123が入っていた場合、iは2になっているので10のi乗は100になります。123を100で割ると、1.23となってそれを切り捨てると1になるので、今見てる位の数を取り出すことができます。

次に、num_cacheからnum_inTheDigitを10のi乗した数でかけた数を引きます。123の例だと、num_cacheに123、num_inTheDigitに1が入っているので、$123 - 1\times10^2=123-100=23$となります。この数がそのまま次の分解処理の対象になります。

これらの変更を加えたコードがこちらです。

script.js
// 対応させる範囲
- const min_max = [0, 9];
+ const min_max = [0, 99];

// 変換データ
- const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう"];
+ const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう", "じゅう"];

// getElementByIdの処理を簡略化する
function getEle(id){
    return document.getElementById(id);
}

function output_content(content){
    getEle("output").innerHTML = content;
}

// 入力フォームの設定
const [min, max] = min_max;
getEle("num").min = min;
getEle("num").max = max;

// 実際に変換する処理
function translate_process(){
    const num = getEle("num").value;

    if(num){
        if(num >= min_max[0] && num <= min_max[1]){
-           output_content(parts[Math.floor(num)]);
+           // output_content(parts[Math.floor(num)]);
+           let num_cache = num;
+           let num_inTheDigit;
+           
+           for(let i = Math.floor(Math.log10(Math.floor(num))); i >= 0; i--){
+               num_inTheDigit = Math.floor(num_cache / 10 ** i);
+               num_cache -= num_inTheDigit * 10 ** i;
+               console.log(`${10 ** i}の位: ${num_inTheDigit}`);
+           }
        } else {
            output_content("数値が範囲外です。");
        }

        getEle("num").value = "";
    } else {
        output_content("数字を入力してください。");
    }
}

実行すると、Consoleに以下のように出力されると思います。(画像は45で実行したときのもの)

step13_1.png

Step14: 10〜99の表示が行えるようにする

このステップで実装するコードは全ステップの中で一番難易度が高いので、頑張ってついてきてください!(気合い)
※ このステップでもコード全文は最後に載せます。

まず、対数を使ったことで問題が発生。それは0が使えないこと。真数には0を置けないので、0だけ別に処理させます。(本当は他の処理に入れたかったけど断念)

1桁の処理のときは直接output_content関数に直接文字列を入れましたが、今回は複数の文字列を組み合わせた文字列を出力させたいので、一旦output_stringという新しい変数を用意して、ここに出力内容をまとめていきます。(空の文字列で初期化)

let output_string = "";

先ほども言った通り、0の処理は別にします。

if(num == 0){
    output_string = parts[0];
} else {
    ...
}

上のコードのelseの中を書いていきます。

elseの中は先ほどのfor文をベースに以下のように実装します。

for(let i = Math.floor(Math.log10(Math.floor(num))); i >= 0; i--){
    num_inTheDigit = Math.floor(num_cache / 10 ** i);
    num_cache -= num_inTheDigit * 10 ** i;
    console.log(`${10 ** i}の位: ${num_inTheDigit}`);

    if((i == 0 && num_inTheDigit != 0) || (num_inTheDigit != 0 && num_inTheDigit != 1 && i > 0)){
        output_string += parts[num_inTheDigit];
    }

    switch(i){
        case 1:
            output_string += parts[10];
            break;
    }
}

for文の条件と中身の上から3行はStep13と同じなので、説明はしないでその下のコードの説明をします。

「34」を「3」と「4」に分解して、単にそれぞれに対応する文字列をoutput_stringに入れてしまうと本当は「さんじゅうよん」としたいのに、出来上がる出力が「さんよん」となってしまいます。
この問題を解決するために、10の位を分解したときは分解した数に対応する文字列の後に、「じゅう」を入れてあげます。これで

output_string += "さん"
output_string += "じゅう"

が行われ、次のループ処理で

output_string += "よん"

になって、最終的にoutput_stringには「さんじゅうよん」が出来上がります。

ここまでで、すべて上手くいったと思いきやまだ問題が。

例えば「12」を分解する場合、今のアルゴリズムだと出力は「いちじゅうに」になってしまいます。そこで、2桁目を分解しているときにその桁が1の場合は1に対応する文字列を出力内容に含めないようにします。

はぁ、問題も解決できたしこれでokと思いきや...

まだあるんですよね...

ここまでの問題を解決しても「10」を出力させると「じゅうゼロ」になってしまいます。また、今回は関係ないですが「102」を分解した場合も出力の途中に「ゼロ」が入ってきてしまいます。そのため、「0」以外は「ゼロ」を出力させないようにします。

というわけで、まず今分解した桁の数を出力に含めるコードが

if((i == 0 && num_inTheDigit != 0) || (num_inTheDigit != 0 && num_inTheDigit != 1 && i > 0)){
    output_string += parts[num_inTheDigit];
}

となります。

コード内容を日本語で書くと、「今見ている位が1の位かつその桁が0ではない場合」または「今見ている位が10の位以上で、その桁が0ではないかつ1でもない」場合その桁の数に対応する文字列を出力内容に含める、になります。

「じゅう」を入れる部分が

switch(i){
    case 1:
        output_string += parts[10];
        break;
}

です。

ここで初めて出てきたswitch文を説明します。
switch文はif文と同じで条件分岐に使用します。ただ、条件式で分岐するif文と違うのは想定される内容ごとで分解すること。
例えば、

switch(n){
    case 0:
        console.log("はずれ");
        break;
    case 1:
        console.log("あたり");
        break;
    default:
        console.log("想定していないくじが引かれました。");
        break;
}

というコードを考えてみます。この場合、nの内容で分岐します。nが0の場合はconsole.log("はずれ");を処理し、1の場合はconsole.log("あたり");を処理します。defaultはこれまでの条件に当てはまらなかった場合に実行される部分になります。

すべての分岐にあるbreakは何かと言うと、処理後に次のcaseの処理が行われないようにするためのストッパーの役割があります。(ChatGPTによると、case以降のすべての処理が行われることを「フォールスルー(fall-through)」というみたいです。)

つまり、今回の

switch(i){
    case 1:
        output_string += parts[10];
        break;
}

このコードではiが1、つまり10の位の場合はparts[10]("じゅう")を出力内容に加えます。

先ほどのif文の後にこのswitch文を書かないと「にじゅうはち」が「じゅうにはち」みたいになってしまうので順番に気をつけましょう。

script.js
// 対応させる範囲
const min_max = [0, 99];

// 変換データ
const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう", "じゅう"];

// getElementByIdの処理を簡略化する
function getEle(id){
    return document.getElementById(id);
}

function output_content(content){
    getEle("output").innerHTML = content;
}

// 入力フォームの設定
const [min, max] = min_max;
getEle("num").min = min;
getEle("num").max = max;

// 実際に変換する処理
function translate_process(){
    const num = getEle("num").value;

    if(num){
        if(num >= min_max[0] && num <= min_max[1]){
-           // output_content(parts[Math.floor(num)]);
+           // 計算に使用する変数
            let num_cache = num;
            let num_inTheDigit;

+           // 出力内容を保存する変数
+           let output_string = "";
+           
+           if(num == 0){
+               output_string = parts[0];
+           } else {
                for(let i = Math.floor(Math.log10(Math.floor(num))); i >= 0; i--){
                    num_inTheDigit = Math.floor(num_cache / 10 ** i);
                    num_cache -= num_inTheDigit * 10 ** i;
                    console.log(`${10 ** i}の位: ${num_inTheDigit}`);
+
+                   if((i == 0 && num_inTheDigit != 0) || (num_inTheDigit != 0 && num_inTheDigit != 1 && i > 0)){
+                       output_string += parts[num_inTheDigit];
+                   }
+                   
+                   switch(i){
+                       case 1:
+                           output_string += parts[10];
+                           break;
+                   }
                }
+           }      
+           
+           output_content(output_string);
        } else {
            output_content("数値が範囲外です。");
        }

        getEle("num").value = "";
    } else {
        output_content("数字を入力してください。");
    }
}

Step15: onclickをaddEventListenerに変更

index.htmlにonclickがあったと思うんですけど、onclickを使わずにJavaScriptの方でクリックを検知してtranslate_process関数を実行させたいと思います。

そのときに使うのがaddEventListenerになります。クリック以外にも様々な用途で用いられるJavaScript側が用意してくれている関数で、何かを検知するために使用します。
例えば今回実装するクリック処理でいうと、idbtnのボタンに対して実装する場合は

document.getElementById("btn").addEventListener("click", 実行する関数名);

のようにします。
今回だとgetEle関数も用意しているので、

getEle("translate_btn").addEventListener("click", translate_process);

のようになります。
また、アロー関数というものを使うと以下のように複数の処理を書くこともできます。

document.getElementById("btn").addEventListener("click", () => {
    console.log("クリックされた。");
})

これはどうなっているかというと、

() => {

}

が名前のない関数(無名関数)を短く書いた形です。無名関数を本来の形で書くと

function() {

}

という感じになり、これを矢印のような=>(アロー演算子)を使って書いたものが先ほどの形になります。つまり名前のない関数をaddEventListenerで呼び、その関数の内容を一緒に書いているということになります。

アロー演算子は=>だけでなく->もあり、プログラミング言語によって役割がかなり違うので注意しましょう。

ということで、onclickaddEventListenerに置き換えると

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>howToSayInJapanese</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container-input">
        <input type="number" min="0" placeholder="number" id="num">
-       <button type="button" class="translate" onclick="translate_process()">translate</button>
+       <button type="button" class="translate" id="translate_btn">translate</button>
        <h5 id="output"></h5>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>
script.js
// 対応させる範囲
const min_max = [0, 99];

// 変換データ
const parts = ["ゼロ", "いち", "", "さん", "よん", "", "ろく", "なな", "はち", "きゅう", "じゅう"];

// getElementByIdの処理を簡略化する
function getEle(id){
    return document.getElementById(id);
}

function output_content(content){
    getEle("output").innerHTML = content;
}

// 入力フォームの設定
const [min, max] = min_max;
getEle("num").min = min;
getEle("num").max = max;

+ // ボタンのクリック判定
+ getEle("translate_btn").addEventListener("click", translate_process);
+ // getEle("translate_btn").addEventListener("click", () => {
+ //   translate_process();
+ // });

// 実際に変換する処理
function translate_process(){
    const num = getEle("num").value;

    if(num){
        if(num >= min_max[0] && num <= min_max[1]){
            // 計算に使用する変数
            let num_cache = num;
            let num_inTheDigit;

            // 出力内容を保存する変数
            let output_string = "";

            if(num == 0){
                output_string = parts[0];
            } else {
                for(let i = Math.floor(Math.log10(Math.floor(num))); i >= 0; i--){
                    num_inTheDigit = Math.floor(num_cache / 10 ** i);
                    num_cache -= num_inTheDigit * 10 ** i;
                    console.log(`${10 ** i}の位: ${num_inTheDigit}`);

                    if((i == 0 && num_inTheDigit != 0) || (num_inTheDigit != 0 && num_inTheDigit != 1 && i > 0)){
                        output_string += parts[num_inTheDigit];
                    }

                    switch(i){
                        case 1:
                            output_string += parts[10];
                            break;
                    }
                }
            }      

            output_content(output_string);
        } else {
            output_content("数値が範囲外です。");
        }

        getEle("num").value = "";
    } else {
        output_content("数字を入力してください。");
    }
}

index.htmlではonclickを削除した代わりにidを設定して、script.jsではaddEventListenerのコードを追加しています。addEventListenerに関しては、アロー関数を使用したものをコメントアウトで用意しています。アロー関数を使っていない方でも使った方でも処理は同じなのでお好きな方を使ってみてください。

Step16: ボタンのradiusの修正と入力フォームの幅の調整

ボタンのradiusを修正

step16_1.png

Step2でtranslateborder-radiusを設定したのに、なんかキモくね?

私がこのプログラムを書いているときに普通に凡ミスした部分になるんですけど、これも勉強になるのかなと思い、あえて最初から修正したものにせずに別のステップで修正作業を入れることにしました。

裏話はこの辺にしておいて、こうなってしまった原因を教えましょう。...っと、実はもうここまで読んでくださった皆さんには理由が分かるはず!
答えはStep3にあります。そう、犯人は%です。(まぁ、このミスしたコードを書いたときに私が原因は言ってますけどね...)
border-radiusでは角の丸みを半径がどれくらいの円にするのかというのを設定するのですが、%の場合は縦方向の半径と横方向の半径でそれぞれ画面の高さと幅に対して計算されてしまうので、半径が均一にならず綺麗に丸くなりません。

これを修正するためには半径を一定にすればいいので、pxを使って

style.css
.translate{
    margin-top: 1%;
    border: 0.5mm solid black;
-   border-radius: 5%;
+   border-radius: 5px;
    background-color: rgb(237, 246, 255);
}

.container-input{
    margin-top: 10%;
    display: flex;
    align-items: center; /*.縦 */
    justify-content: center; /* 横 */
    flex-direction: column;
}

#output{
    margin-top: 3%;
    color: rgb(255, 33, 70);
    font-weight: bold;
}

#num {
    width: 100px;
}

となります。

最初の方にも言いましたが、css用のカラーリングで差分を表示できないので白黒になってしまいました。見づらくてすみません。コードを写す場合は赤色のマイナスと緑色のプラスは書かないようにご注意。

実行してみると、美しい丸みに!

step16_2.png

入力フォームの幅を調整

入力フォームの幅を100pxから180pxに伸ばしました。
※ これは私のこだわりなので、やってもやらなくても良いです。

style.css
.translate{
    margin-top: 1%;
    border: 0.5mm solid black;
    border-radius: 5px;
    background-color: rgb(237, 246, 255);
}

.container-input{
    margin-top: 10%;
    display: flex;
    align-items: center; /*.縦 */
    justify-content: center; /* 横 */
    flex-direction: column;
}

#output{
    margin-top: 3%;
    color: rgb(255, 33, 70);
    font-weight: bold;
}

#num {
-   width: 100px;
+   width: 180px;
}

Step17: Google Fontsを使ってみる

このままデフォルトのフォントを使用しても良いんですが、せっかくなら出力部分だけでもフォントを変えようと思います。
実はブラウザ側にTimes New Romanなどが用意されています。
(OSとブラウザによって用意されているフォントは異なるので、開発環境ではフォントが変わってもそのフォントが用意されていない環境で開くとフォントを設定していないときのデフォルトのフォントが使われてしまうので注意)

今回はGoogle Fontsを使おうと思います。

上のリンクからGoogle Fontsのサイトを開きましょう。Google Fontsは何もアカウント登録せずに無料でフォントやアイコンを使用できるので便利です。

1.png

使用するフォントを選ぶ

ではFiltersと書かれているボタンを押してみてください。
Type somethingに入力すると、フォントごとにその文字列がそれぞれどのように表示されるのかを確認することができます。実際に表示する予定の文字列を入れるのをオススメします。
また、LanguageタブのWriting systemでは対応している文字の種類、Languageでは言語を選択できます。今回はType somethingに「じゅうご」を、LanguageにJapaneseを設定しました。
2.png

私と同じようにフィルターを設定すると、多分Noto Sans Japaneseというフォントが一番上に来ると思います。私はこれを使いたいと思います。(好きなフォントを選んでください。どれを選んでも使い方は同じなのでご安心を)

CSSのコードを取得するまで

Noto Sans Japaneseの項目をクリックすると以下のようになると思います。

3.png

ここでは用意されている文字の太さや斜体、文字の大きさなどを確認することができます。(いわゆるプレビュー)

右上のGet Fontをクリックするとその上にあるバッグにフォントが入ります。

ではバッグをクリックしましょう。

4.png

Get embed codeをクリックしましょう。すると

5.png

という画面になると思います。この3つのlinkタグのHTMLのコードとその下のCSSを自分のHTMLファイルとCSSファイルに
コピペします。

linkタグの方はこのGoogle Fontsのシステムからフォントを取ってくるためのコードで、CSSの方は使用するフォントの設定になります。

複数のフォントを選んだ場合でも上2つのlinkタグは変わりません。ただし、一番下のlinkタグはバッグに入れたフォントをすべて取得するものになっています。
つまり、途中でフォントを追加する可能性がある場合はバッグの中身をリセットしないでおいて、必要になったときにフォントを追加することで一番下のlinkタグだけ上書きすれば使えるようになるので便利です。
(リセットした場合は新しく一番下のlinkタグが作成されるので、そのコードをコピペして追記しましょう。)

取得したCSSのコードを反映する

style.cssにこのフォントのCSSのコードを書いても良いんですが、自作した部分とGoogle Fontsのコードで分けようと思うので、新しくfont.cssを作成して、index.htmlで読み込みます。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>howToSayInJapanese</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

    <link rel="stylesheet" href="style.css">
+   <link rel="stylesheet" href="font.css">

+   <!-- Google Fonts -->
+   <link rel="preconnect" href="https://fonts.googleapis.com">
+   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+   <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
</head>
<body>
    <div class="container-input">
        <input type="number" min="0" placeholder="number" id="num">
        <button type="button" class="translate" id="translate_btn">translate</button>
        <h5 id="output"></h5>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>

font.cssにGoogle Fontsのものをコピペしましょう。

style.css
.noto-sans-jp-<uniquifier> {
  font-family: "Noto Sans JP", sans-serif;
  font-optical-sizing: auto;
  font-weight: <weight>;
  font-style: normal;
}

ただ、これだと<uniquifier><weight>で文法エラーになります。実はこの部分は同じフォントの中でも太さや斜体などで複数のパターンを用意している場合によくあるもので、パターンによって分けられるように「一意になるようにここに何か書いて」みたいなものになります。(多分)

私はいつもfont-weight(太さ)に設定した数字をuniquifierにも書くようにしているので、

style.css
.noto-sans-jp-400 {
  font-family: "Noto Sans JP", sans-serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-style: normal;
}

としました。(今回は400に設定)

フォントのプレビューページになかったfont-weightfont-styleは使用できないのでご注意ください。(使えるのは用意されているものに限ります。)

このコードを見れば分かると思いますが、classの表記になっているので、index.htmlのid="output"のh5タグに追加します。
(class表記ということはこの名前は好きな名前に変更しても問題ないということです。)

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>howToSayInJapanese</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

    <link rel="stylesheet" href="style.css">
    <link rel="stylesheet" href="font.css">

    <!-- Google Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
</head>
<body>
    <div class="container-input">
        <input type="number" min="0" placeholder="number" id="num">
        <button type="button" class="translate" id="translate_btn">translate</button>
-       <h5 id="output"></h5>
+       <h5 class="noto-sans-jp-400" id="output"></h5>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>

これで実行すると、以下のようにフォントが変わっていることが分かります。(「き」を見ると、違いが分かりやすいと思います。)

  • フォント変更前
    fontなし.png

  • フォント変更後
    fontあり.png

Step18: cssファイルをフォルダにまとめる

style.cssとfont.cssという2つのCSSファイルができたのですが、これをcssという名前のフォルダにまとめようと思います。

index.htmlとscript.jsと同じ階層にcssフォルダ、cssフォルダの中にstyle.cssとfont.cssがあるようにしてください。

style.cssとfont.cssの場所を変えてしまったので、このままではindex.htmlでの読み込み処理が上手くいきません。ということで、以下のように修正しましょう。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>howToSayInJapanese</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

-   <link rel="stylesheet" href="style.css">
-   <link rel="stylesheet" href="font.css">
+   <link rel="stylesheet" href="css/style.css">
+   <link rel="stylesheet" href="css/font.css">

    <!-- Google Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
</head>
<body>
    <div class="container-input">
        <input type="number" min="0" placeholder="number" id="num">
        <button type="button" class="translate" id="translate_btn">translate</button>
        <h5 class="noto-sans-jp-400" id="output"></h5>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
    <script src="script.js"></script>
</body>
</html>

「パス」が分かる人はこの書き方が分かると思うんですけど、簡単に言えばファイル構造を階層のように書くイメージで "css/style.css"はcssの中にあるstyle.cssということになります。(相対パスで書きます)

小ネタ(思いついたら追記するかも)

CSSの;について

実はCSSで書く;は最後の1行だけ省略可能です。そもそも;は前のコードと区切る役割をしているので必要になっています。

p {
  color: red;
  font-size: 16px /* ;なし */
}

ただし、下にコードを追加ときに;を入れることを忘れないようにしてください。

こんな小ネタを紹介しましたが、普通にすべての行に;を書くのがオススメです。
(コードを追記したときのエラーを防ぐため)

おわりに

ここまで読んでいただき、ありがとうございました。第1回では基礎中の基礎を扱いましたが、今回のチュートリアルではより実践に近い感じになったのかなと思います。(まだ基本の部分になりますけどね。ちょっとずつ慣れていけばいいんですよ、プログラミングは。プログラミングは実際に書いていく中で勉強するのが一番だと思うので)

また、良い感じのチュートリアルが思いついたら第3弾も書くかもしれません。(あまり期待しないようにお願いします。)

質問や誤り、アドバイスなどありましたら遠慮なくコメントをお願いします。

ではまた、どこかの記事で。

※ 追記、修正をする場合があります。

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?