From 6ef1fa4618e08426b874529619a66adbd3d1fcf0 Mon Sep 17 00:00:00 2001 From: Florian Merz Date: Thu, 11 Feb 2021 12:07:59 +0100 Subject: unslug ja: move --- .../learn/forms/advanced_form_styling/index.html | 556 +++++++++++++ .../advanced_styling_for_html_forms/index.html | 556 ------------- .../forms/basic_native_form_controls/index.html | 339 ++++++++ .../example_1/index.html | 415 ++++++++++ .../example_2/index.html | 212 +++++ .../example_3/index.html | 246 ++++++ .../example_4/index.html | 296 +++++++ .../example_5/index.html | 289 +++++++ .../how_to_build_custom_form_controls/index.html | 901 +++++++++++++++++++++ .../example_1/index.html | 415 ---------- .../example_2/index.html | 212 ----- .../example_3/index.html | 246 ------ .../example_4/index.html | 296 ------- .../example_5/index.html | 289 ------- .../how_to_build_custom_form_widgets/index.html | 901 --------------------- .../how_to_structure_a_web_form/example/index.html | 164 ++++ .../forms/how_to_structure_a_web_form/index.html | 329 ++++++++ .../example/index.html | 164 ---- .../forms/how_to_structure_an_html_form/index.html | 329 -------- files/ja/learn/forms/styling_html_forms/index.html | 399 --------- files/ja/learn/forms/styling_web_forms/index.html | 399 +++++++++ .../learn/forms/the_native_form_widgets/index.html | 339 -------- 22 files changed, 4146 insertions(+), 4146 deletions(-) create mode 100644 files/ja/learn/forms/advanced_form_styling/index.html delete mode 100644 files/ja/learn/forms/advanced_styling_for_html_forms/index.html create mode 100644 files/ja/learn/forms/basic_native_form_controls/index.html create mode 100644 files/ja/learn/forms/how_to_build_custom_form_controls/example_1/index.html create mode 100644 files/ja/learn/forms/how_to_build_custom_form_controls/example_2/index.html create mode 100644 files/ja/learn/forms/how_to_build_custom_form_controls/example_3/index.html create mode 100644 files/ja/learn/forms/how_to_build_custom_form_controls/example_4/index.html create mode 100644 files/ja/learn/forms/how_to_build_custom_form_controls/example_5/index.html create mode 100644 files/ja/learn/forms/how_to_build_custom_form_controls/index.html delete mode 100644 files/ja/learn/forms/how_to_build_custom_form_widgets/example_1/index.html delete mode 100644 files/ja/learn/forms/how_to_build_custom_form_widgets/example_2/index.html delete mode 100644 files/ja/learn/forms/how_to_build_custom_form_widgets/example_3/index.html delete mode 100644 files/ja/learn/forms/how_to_build_custom_form_widgets/example_4/index.html delete mode 100644 files/ja/learn/forms/how_to_build_custom_form_widgets/example_5/index.html delete mode 100644 files/ja/learn/forms/how_to_build_custom_form_widgets/index.html create mode 100644 files/ja/learn/forms/how_to_structure_a_web_form/example/index.html create mode 100644 files/ja/learn/forms/how_to_structure_a_web_form/index.html delete mode 100644 files/ja/learn/forms/how_to_structure_an_html_form/example/index.html delete mode 100644 files/ja/learn/forms/how_to_structure_an_html_form/index.html delete mode 100644 files/ja/learn/forms/styling_html_forms/index.html create mode 100644 files/ja/learn/forms/styling_web_forms/index.html delete mode 100644 files/ja/learn/forms/the_native_form_widgets/index.html (limited to 'files/ja/learn/forms') diff --git a/files/ja/learn/forms/advanced_form_styling/index.html b/files/ja/learn/forms/advanced_form_styling/index.html new file mode 100644 index 0000000000..9e6cf26d94 --- /dev/null +++ b/files/ja/learn/forms/advanced_form_styling/index.html @@ -0,0 +1,556 @@ +--- +title: HTML フォームへの高度なスタイル設定 +slug: Learn/Forms/Advanced_styling_for_HTML_forms +tags: + - Advanced + - CSS + - Forms + - HTML + - Web + - ガイド + - 例 +translation_of: Learn/Forms/Advanced_form_styling +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Forms/Styling_web_forms", "Learn/Forms/UI_pseudo-classes", "Learn/Forms")}}
+ +

本記事では、スタイル設定が難しい一部の(不良なものと劣悪なもの)フォームコントロールで CSS を使用する方法を見ていきます。前の記事で見たように、テキストフィールドやボタンでの CSS 使用はまったく問題がありません。ここからは、HTML フォームへのスタイル設定の闇の部分を見ていきます。

+ + + + + + + + + + + + +
前提条件:基本的なコンピューターリテラシーと、HTMLCSS の基本的な理解。
目的:フォームのどの部分をスタイル設定するのが難しいのか、またなぜなのかを理解する。そこをカスタマイズするのに何ができるかを学ぶ。
+ +

始める前に、2 種類の HTML フォームウィジェットについておさらいしましょう:

+ +

不良: スタイルの設定が難しく複雑なトリックが必要であり、時に CSS3 の高度な知識が必要である要素:

+ + + +

劣悪: いくつかの要素は CSS でスタイル設定できません。これらが含まれます:

+ + + +

見た目: OS レベルのスタイル制御

+ +

前の記事では歴史的に、ウェブフォームのスタイル設定は OS で行われており、それがこのコントロールの見た目のカスタマイズの問題の一部となっていました。

+ +

{{cssxref("appearance")}} プロパティは OS やシステムレベルでウェブフォームのスタイル設定を制御する方法として作成されました。不運なことに、そのプロパティの元々の実装の動作はブラウザー間で大きく異なっており、そんなに便利ではありませんでした。最近の実装ではもっと動作が一貫してきており、十分興味深く、それは Chromium-ベースのブラウザー (Chrome, Opera, Edge), Safari, Firefox のいずれも -webkit-  のプレフィックスつきバージョン (-webkit-appearance)をサポートしています。Firefox は、ウェブ開発者の大半が -webkit- プレフィックスバージョンをサポート使っていて互換性が良いため、このように決めました。

+ +

リファレンスページを見ると -webkit-appearance の多くの値がありますが、最も便利で、おそらく唯一使うのは none です。これはあらゆるコントロールがシステムレベルのスタイル設定するのをできるだけ防止して、CSS を用いた独自のスタイル設定をできるようにします。

+ +

例えば、次のコントロールを見てみます:

+ +
<form>
+  <p>
+    <label for="search">search: </label>
+    <input id="search" name="search" type="search">
+  </p>
+  <p>
+    <label for="text">text: </label>
+    <input id="text" name="text" type="text">
+  </p>
+  <p>
+    <label for="date">date: </label>
+    <input id="date" name="date" type="datetime-local">
+  </p>
+  <p>
+    <label for="radio">radio: </label>
+    <input id="radio" name="radio" type="radio">
+  </p>
+  <p>
+    <label for="checkbox">checkbox: </label>
+    <input id="checkbox" name="checkbox" type="checkbox">
+  </p>
+  <p><input type="submit" value="submit"></p>
+  <p><input type="button" value="button"></p>
+</form>
+ +

次の CSS を適用してシステムレベルのスタイル設定を削除します。

+ +
input {
+  -webkit-appearance: none;
+  appearance: none;
+}
+ +
+

: プレフィックスつきのプロパティを使っているときでも、両方の定義を常に入れておくのが良いです — プレフィックス付きとなしと。プレフィックスつきのものは通常は"作業中"を意味するため、将来のブラウザーベンダーはプレフィックスを落とすよう合意することもあるでしょう。上記のコードではそのような結末となった将来でも耐用できます。

+
+ +

下記の例ではあなたのシステムでどのように見えるかを示します — デフォルトでは左で、上記の CSS が適用されると右です (その他のシステムでテストしたい場合はここも探してください)。

+ +

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/appearence-tester.html", '100%', 400)}}

+ +

たいていの場合、効果は枠線を除去し、CSS でのスタイル設定を少し簡単にしますが、それは本質できありません。いくつかの場合 — 検索やラジオボタン/チェックボックスでは、もっと便利です。これを見ていきましょう。

+ +

検索ボックスを変更する

+ +

<input type="search"> は基本的に単なるテキスト入力のため、なぜ appearance: none; が便利なのでしょうか? 答えは macOS での Chromium ベースのブラウザーでは、検索ボックスはスタイル設定の制限があります — 例えば heightfont-size を自在に調整できません。この理由は macOS以外の Chrome ブラウザーはもう WebKit レンダリングエンジンを使っていません 、これは既定では、いくつかのフォームコントロールで Aqua の見た目が有効です。Aqua が有効だと、いくつかのフォームコントロールは scalable となりません。

+ +

これは デフォルトの Aqua を無効にする appearance: none;で修正できます:

+ +
input[type="search"] {
+    -webkit-appearance: none;
+    appearance: none;
+}
+ +

下記の例では、2 つのスタイル設定された検索ボックスが見えます。右のものは appearance: none; が適用され、左はそうでありません。macOS Chrome で見ると左のものは正しいサイズでないように見えます。

+ +

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/search-appearence.html", '100%', 200)}}

+ +

興味深いことに、検索フィールドで border/background をセットしてもこの問題を解決できます、なぜならそれも Aqua を無効化や "破壊する" からです。下記のスタイル設定された検索ボックスは appearance: none; が適用されていませんが、前の例と同じ macOS Chrome の問題に悩まされていません。

+ +

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/styled-search.html", '100%', 200)}}

+ +
+

: 検索フィールドでは、Edge と Chrome で入力がフォーカスされないときに "x" の削除アイコンが消えるが、Safari では残ることに気づくこともあるでしょう。CSS で消すには、input[type="search"]::-webkit-search-cancel-button { display: none; }を使用できますが、フォーカス時のアイコンも取り除き、見た目が元に戻らないようです。

+
+ +

チェックボックスとラジオボタン

+ +

チェックボックスやラジオボタンのスタイリングは難しい場合があります。たとえば、チェックボックスやラジオボタンのサイズはデフォルトのデザインの変更が意図されておらず、ブラウザーで試してみると非常に異なる反応を示します。

+ +

例えば、シンプルなテストケースを考えてみます:

+ +
<span><input type="checkbox"></span>
+ +
span {
+    display: inline-block;
+    background: red;
+}
+
+input[type=checkbox] {
+    width : 100px;
+    height: 100px;
+}
+ +

さまざまなブラウザーでの処理方法は以下のとおりです:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ブラウザー描画結果
Firefox 71 (macOS)
Firefox 57 (Windows 10)
Chrome 77 (macOS), Safari 13, Opera
Chrome 63 (Windows 10)
Internet Explorer 11 (Windows 10)
Edge 16 (Windows 10)
+ +

ラジオボタン/チェックボックスで appearance: none を使う

+ +

これまで見てきたように、チェックボックスやラジオボタンのデフォルトの見た目を {{cssxref('appearance')}}:none; で削除できます。この HTML の例を見てみましょう:

+ +
<form>
+  <fieldset>
+    <legend>Fruit preferences</legend>
+
+    <p>
+      <label>
+        <input type="checkbox" name="fruit-1" value="cherry">
+        I like cherry
+      </label>
+    </p>
+    <p>
+      <label>
+        <input type="checkbox" name="fruit-2" value="banana" disabled>
+        I can't like banana
+      </label>
+    </p>
+    <p>
+      <label>
+        <input type="checkbox" name="fruit-3" value="strawberry">
+        I like strawberry
+      </label>
+    </p>
+  </fieldset>
+</form>
+ +

さて、カスタムチェックボックスデザインでこれらのスタイルを設定しましょう。元のチェックボックスを解除することから始めましょう:

+ +
input[type=checkbox] {
+  -webkit-appearance: none;
+  appearance: none;
+}
+ +

{{cssxref(":checked")}} と {{cssxref(":disabled")}} 擬似クラスを使用して、状態の変化に合わせてカスタムチェックボックスの外観を変更します:

+ +
input[type=checkbox] {
+  position: relative;
+  width: 1em;
+  height: 1em;
+  border: 1px solid gray;
+  /* Adjusts the position of the checkboxes on the text baseline */
+  vertical-align: -2px;
+  /* Set here so that Windows' High-Contrast Mode can override */
+  color: green;
+}
+
+input[type=checkbox]::before {
+  content: "✔";
+  position: absolute;
+  font-size: 1.2em;
+  right: 0;
+  top: -0.3em;
+  visibility: hidden;
+}
+
+input[type=checkbox]:checked::before {
+  /* Use `visibility` instead of `display` to avoid recalculating layout */
+  visibility: visible;
+}
+
+input[type=checkbox]:disabled {
+  border-color: black;
+  background: #ddd;
+  color: gray;
+}
+ +

こうした疑似クラスなどは、次の記事で見つけられますが、上記は次のことをしています:

+ + + +

実際の結果が表示されます。

+ +

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/checkboxes-styled.html", '100%', 200)}}

+ +

もっと理解できるよう別の例もあります:

+ + + +

{{cssxref("appearance")}} や をサポートしていないブラウザーでこれらのチェックボックスを表示できます。カスタムデザインは失われますが、チェックボックスのままに見えて使用できます。

+ +
+

注記: Internet Explorer はどのバージョンの appearance もサポートしませんが、input[type=checkbox]::-ms-check にて IE のみチェックボックスをターゲットにできます。この手法は、-ms-check という名前にもかかわらず、ラジオボタンでも機能します。

+
+ +

"劣悪な"要素に何ができるか?

+ +

今度は"劣悪な"コントロールに注目しましょう — これは完全にスタイル設定するのが本当に難しいものです。簡単にいうと、これはドロップダウンボックス、colordatetime-local のような複合コントロールタイプ、フィードバック —  {{HTMLElement("progress")}} や {{HTMLElement("meter")}}のような指向性コントロールです。

+ +

問題は、要素はブラウザー同士でいろいろな既定の見た目があって、それにスタイル設定できても、内部のいくつかはスタイル設定できないことです。

+ +

ルック&フィールの違いを受け入れる覚悟があれば、サイズ変更を一貫したものにするためのシンプルなスタイル設定や、background-colors のような単一スタイル設定、システムレベルのスタイル設定を除去できる appearance の使用などで逃げることもできます。

+ +

例を見てみましょう、たくさんの"劣悪な" フォーム機能をつぎつぎに表示しています:

+ +

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/ugly-controls.html", '100%', 750)}}

+ +

この例では下記の CSS を適用しています:

+ +
body {
+  font-family: 'Josefin Sans', sans-serif;
+  margin: 20px auto;
+  max-width: 400px;
+}
+
+form > div {
+  margin-bottom: 20px;
+}
+
+select {
+  -webkit-appearance: none;
+  appearance: none;
+}
+
+.select-wrapper {
+  position: relative;
+}
+
+.select-wrapper::after {
+  content: "▼";
+  font-size: 1rem;
+  top: 6px;
+  right: 10px;
+  position: absolute;
+}
+
+button, label, input, select, progress, meter {
+  display: block;
+  font-family: inherit;
+  font-size: 100%;
+  padding: 0;
+  margin: 0;
+  box-sizing: border-box;
+  width: 100%;
+  padding: 5px;
+  height: 30px;
+}
+
+input[type="text"], input[type="datetime-local"], input[type="color"], select {
+  box-shadow: inset 1px 1px 3px #ccc;
+  border-radius: 5px;
+}
+
+label {
+  margin-bottom: 5px;
+}
+
+button {
+  width: 60%;
+  margin: 0 auto;
+}
+ +
+

注記: 多数のブラウザーで同時にこの例をテストしたい場合、live版をここで見つけてください (ソースコードも見てください)。

+ +

また JavaScript をページに追加してコントロール自身の下にあるファイルピッカー自身で選択されたファイルを一覧しているのを心に留めておいてください。これは <input type="file"> リファレンスページの例を簡単にしたバージョンです。

+
+ +

ご覧のとおり、これらをモダンブラウザー全体で均一に見せることはかなりうまくいっています。

+ +

すべてのコントロールとそのラベルに対してグローバルな CSS の正規化を適用し、サイズを同様にして、親のフォントを適用するなどを行っています。これは前の記事で述べたようなことです:

+ +
button, label, input, select, progress, meter {
+  display: block;
+  font-family: inherit;
+  font-size: 100%;
+  padding: 0;
+  margin: 0;
+  box-sizing: border-box;
+  width: 100%;
+  padding: 5px;
+  height: 30px;
+}
+ +

コントロールに統一した影と角丸も与えて、意味のあるようにします:

+ +
input[type="text"], input[type="datetime-local"], input[type="color"], select {
+  box-shadow: inset 1px 1px 3px #ccc;
+  border-radius: 5px;
+}
+ +

範囲や、プログレスバーや、メーターはコントロールエリアの周りに美しくないボックスができるだけなので、意味はありません。

+ +

これらのコントロールタイプそれぞれの仕様と、ハイライトの難しさをこれからお話ししましょう。

+ +

Select とデータリスト

+ +

モダンブラウザーでは、select とデータリストは一般的に、デフォルトからルック&フィールを大きく変えたくないようにスタイル設定する場合、悪くないものです。

+ +

これまで、ボックスの基本的な見た目をかなり均一で一貫性のあるものにしてきました。とにかくデータリストコントロールは <input type="text"> なので、問題にならないことがわかっています。

+ +

2 つのものが多少問題をはらんでいます。まず最初に、select がドロップダウンであることを示す "矢印" アイコンは、ブラウザーによって異なります。また select ボックスのサイズを増やしたり、変更したりすると、醜くなりがちです。これを修正するため、例では最初に旧友の appearance: none を使ってアイコンを除去しています:

+ +
select {
+  -webkit-appearance: none;
+  appearance: none;
+}
+ +

次に生成されたコンテンツを使って独自のアイコンを作成します。コントロールの周りに特別なラッパーを置いていて、その理由は::before/::after<select> 要素では動作しなしためです (これは生成されたコンテンツが要素がフォーマットするボックスに対し相対的に配置されますが、フォーム入力は置換された要素 — 表示がブラウザーによって生成されて順番に配置されるもの — として動作するので、1 つもないためです):

+ +
<div class="select-wrapper"><select id="select" name="select">
+  <option>Banana</option>
+  <option>Cherry</option>
+  <option>Lemon</option>
+</select></div>
+ +

次に生成されたコンテンツを使って小さな下向き矢印を作り、正しい場所にポジショニングします:

+ +
.select-wrapper {
+  position: relative;
+}
+
+.select-wrapper::after {
+  content: "▼";
+  font-size: 1rem;
+  top: 6px;
+  right: 10px;
+  position: absolute;
+}
+ +

2 つ目のもう少し有名な問題は、<select> ボックスをクリックして開いたときに出てくる、オプションを含んだボックスを制御できないことです。オプションが親でセットされたフォントを継承していないのに気づくでしょう。また余白スペースや色のようなものも一貫してセットできません。例えば、Firefox では<option> 要素に colorbackground-color を適用できますが、Chrome ではそうなりません。どちらも余白スペース (例 padding)を適用できません。同じことはデータリスト用に出てくるオートコンプリートのリストにも当てはまります。

+ +

本当にオプションをスタイル設定する必要がある場合、カスタムコントロールを生成するライブラリを使用するか、独自のカスタムコントロールを作成するかのどちらかが必要で、あるいは multiple属性を使う select の場合、これはページ上に出てくるすべてのオプションを作成し、この特殊な問題を避けることができます:

+ +
<select id="select" name="select" multiple>
+  ...
+</select>
+ +

もちろんこれでも進めているデザインにはフィットしないこともありますが、注目に値します!

+ +

日付入力タイプ

+ +

日付/時間の入力タイプ (datetime-local, time, week, month) にはすべて同じ重大な関連した問題があります。実際のボックスはテキスト入力と同じくスタイル設定が容易であり、デモ内で得たものの見た目は良いです。

+ +

しかし、コントロールの内部パーツ (例 日付をピックアップするのに使うポップアップカレンダー、値を増減するスピナー) はまったくスタイル設定できず、appearance: none;を使ってスタイルを除去できません。スタイル設定に完全なコントロールが必要な場合、カスタムコントロールを生成するライブラリを使うか、自分で作らねばなりません。

+ +
+

: ここでも<input type="number"> は触れる価値があります — これにも値を増減するスピナーがあるので、同じ問題に悩まさされます。しかし、number タイプを使った場合にデータは簡単に集められて、単に text 入力を (あるいはモバイルブラウザーで数字キーパッドを表示するのに tel を) 代わりに使うのも簡単です

+
+ +

Range 入力タイプ

+ +

<input type="range"> はスタイル設定が煩わしいです。下記のようなものを使ってデフォルトのスライダートラックを完全に削除してカスタムスタイル (ここでは、薄い赤のトラック)に置き換えます:

+ +
input[type="range"] {
+  appearance: none;
+  -webkit-appearance: none;
+  background: red;
+  height: 2px;
+  padding: 0;
+  outline: 1px solid transparent;
+}
+ +

しかし、range コントロールのドラッグハンドルをカスタマイズするのはとても難しいです — range のスタイル設定を完全完全にコントロールするには、その中に複数の非標準な、ブラウザー固有の疑似要素も含んだ、複雑な CSS コードが必要です。Styling Cross-Browser Compatible Range Inputs with CSS で必要なものを細かく書くための CSS トリックを確認します。

+ +

Color 入力タイプ

+ +

color 入力タイプはそこまでひどくないです。サポートされたブラウザーでは、単に小さな枠のある単色のブロックを提供します。

+ +

枠を削除して、色のブロックだけにするには、次のようにします:

+ +
input[type="color"] {
+  border: 0;
+  padding: 0;
+}
+ +

しかし、カスタムソリューションは大きく異るようにする唯一の方法です。

+ +

ファイル入力タイプ

+ +

ファイル入力タイプは通常は OK です — 例で見てきたように、ページの残りの部分に問題なくフィットする何かを作るのはとても簡単です — コントロールの一部分でもある出力行は、入力にそう指示している場合は親のフォントを継承して、カスタムリストに名前やサイズをお好みでスタイル設定できます; 結局は作っています。

+ +

ファイルピッカーの唯一の問題は、ファイルピッカーを開くボタンは完全にスタイル設定できないことです— サイズや色設定は不可能で、別のフォントすら指定できません。

+ +

この回避法は、フォームコントロールに関連するラベルがある場合、ラベルをクリックするとコントロールがアクティブになるという事実を利用します。つまり実際のフォーム入力を次のように隠します:

+ +
input[type="file"] {
+  height: 0;
+  padding: 0;
+  opacity: 0;
+}
+ +

次にボタンのように動作するラベルをスタイル設定し、そのラベルが押された時にファイルピッカーが期待通り開くようにします:

+ +
label[for="file"] {
+  box-shadow: 1px 1px 3px #ccc;
+  background: linear-gradient(to bottom, #eee, #ccc);
+  border: 1px solid rgb(169, 169, 169);
+  border-radius: 5px;
+  text-align: center;
+  line-height: 1.5;
+}
+
+label[for="file"]:hover {
+  background: linear-gradient(to bottom, #fff, #ddd);
+}
+
+label[for="file"]:active {
+  box-shadow: inset 1px 1px 3px #ccc;
+}
+ +

上記の CSS スタイル設定の結果は、下記のライブ実行の例で見ることができます (styled-file-picker.html のライブと、ソースコードも見てください)。

+ +

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/styled-file-picker.html", '100%', 200)}}

+ +

メーターとプログレスバー

+ +

<meter><progress> は多くの中で最悪かもしれません。前の例で見たように、希望する幅にだいたい正確に設定できました。しかしそれを超えると、どんな方法でもスタイル設定が本当に難しいです。高さの設定をお互いに、ブラウザー間で一貫して処理できず、背景は色付けできるものの、前面のバーはできず、appearance: none を設定すると良くならず、より悪くなります。

+ +

スタイル設定を制御したい場合は、この機能のカスタムソリューションを作ったり、progressbar.js のようなサードパーティのソリューションを使うのは簡単です。

+ +

よりよいフォームへの道: 役に立つライブラリとポリフィル

+ +

すでにいくつか見てきたように、"劣悪な"コントロールを完全にコントロールしたい場合は、JavaScript に頼るしかありません。カスタムフォームウィジェットをビルドする方法の記事では、独自のフォームウィジェットを作成する方法を見ていきますが、そこには役に立つ便利なライブラリがいくつかあります。

+ + + +

次のライブラリはフォームだけではありませんが、HTML フォームを処理するための非常に興味深い機能を備えています:

+ + + +

CSS と JavaScript には副作用があることに注意してください。したがって、それらのライブラリのいずれかを使用することを選択した場合は、スクリプトが失敗した場合に備えて、堅牢なフォールバック HTML を用意する必要があります。スクリプトが失敗する理由はたくさんあります。特にモバイル環境では、これらのケースを可能な限り最善に処理するようにウェブサイトやアプリケーションを設計する必要があります。

+ +

スキルをテストしましょう!

+ +

この記事の最後に到達しましたが、重要な情報を覚えていますか?次に進む前に、この情報を保持しているか検証するテストがあります — Test your skills: Advanced styling を見てください。心に留めておくこととして、この評価シリーズでの質問は、次の記事の知識も前提していてるので、試す前にその記事に取り組むとよいかもしれません。

+ +

まとめ

+ +

HTML フォームで CSS を使用するのはまだ困難ですが、しばしばそれらを回避する方法があります。クリーンでユニバーサルな解決方法はありませんが、最新のブラウザーでは新しい可能性があります。今のところ、最良の解決策は、HTML フォームウィジェットに適用されたときに異なるブラウザーが CSS をサポートする方法の詳細を学ぶことです。

+ +

このガイドの次の記事では、モダンブラウザーでさまざまな状態のフォームをスタイル設定できるさまざまな UI 疑似クラスを見ていきます。

+ +

{{PreviousMenuNext("Learn/Forms/Styling_web_forms", "Learn/Forms/UI_pseudo-classes", "Learn/Forms")}} 

+ +

このモジュール

+ + + +

Advanced Topics

+ + diff --git a/files/ja/learn/forms/advanced_styling_for_html_forms/index.html b/files/ja/learn/forms/advanced_styling_for_html_forms/index.html deleted file mode 100644 index 9e6cf26d94..0000000000 --- a/files/ja/learn/forms/advanced_styling_for_html_forms/index.html +++ /dev/null @@ -1,556 +0,0 @@ ---- -title: HTML フォームへの高度なスタイル設定 -slug: Learn/Forms/Advanced_styling_for_HTML_forms -tags: - - Advanced - - CSS - - Forms - - HTML - - Web - - ガイド - - 例 -translation_of: Learn/Forms/Advanced_form_styling ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/Forms/Styling_web_forms", "Learn/Forms/UI_pseudo-classes", "Learn/Forms")}}
- -

本記事では、スタイル設定が難しい一部の(不良なものと劣悪なもの)フォームコントロールで CSS を使用する方法を見ていきます。前の記事で見たように、テキストフィールドやボタンでの CSS 使用はまったく問題がありません。ここからは、HTML フォームへのスタイル設定の闇の部分を見ていきます。

- - - - - - - - - - - - -
前提条件:基本的なコンピューターリテラシーと、HTMLCSS の基本的な理解。
目的:フォームのどの部分をスタイル設定するのが難しいのか、またなぜなのかを理解する。そこをカスタマイズするのに何ができるかを学ぶ。
- -

始める前に、2 種類の HTML フォームウィジェットについておさらいしましょう:

- -

不良: スタイルの設定が難しく複雑なトリックが必要であり、時に CSS3 の高度な知識が必要である要素:

- - - -

劣悪: いくつかの要素は CSS でスタイル設定できません。これらが含まれます:

- - - -

見た目: OS レベルのスタイル制御

- -

前の記事では歴史的に、ウェブフォームのスタイル設定は OS で行われており、それがこのコントロールの見た目のカスタマイズの問題の一部となっていました。

- -

{{cssxref("appearance")}} プロパティは OS やシステムレベルでウェブフォームのスタイル設定を制御する方法として作成されました。不運なことに、そのプロパティの元々の実装の動作はブラウザー間で大きく異なっており、そんなに便利ではありませんでした。最近の実装ではもっと動作が一貫してきており、十分興味深く、それは Chromium-ベースのブラウザー (Chrome, Opera, Edge), Safari, Firefox のいずれも -webkit-  のプレフィックスつきバージョン (-webkit-appearance)をサポートしています。Firefox は、ウェブ開発者の大半が -webkit- プレフィックスバージョンをサポート使っていて互換性が良いため、このように決めました。

- -

リファレンスページを見ると -webkit-appearance の多くの値がありますが、最も便利で、おそらく唯一使うのは none です。これはあらゆるコントロールがシステムレベルのスタイル設定するのをできるだけ防止して、CSS を用いた独自のスタイル設定をできるようにします。

- -

例えば、次のコントロールを見てみます:

- -
<form>
-  <p>
-    <label for="search">search: </label>
-    <input id="search" name="search" type="search">
-  </p>
-  <p>
-    <label for="text">text: </label>
-    <input id="text" name="text" type="text">
-  </p>
-  <p>
-    <label for="date">date: </label>
-    <input id="date" name="date" type="datetime-local">
-  </p>
-  <p>
-    <label for="radio">radio: </label>
-    <input id="radio" name="radio" type="radio">
-  </p>
-  <p>
-    <label for="checkbox">checkbox: </label>
-    <input id="checkbox" name="checkbox" type="checkbox">
-  </p>
-  <p><input type="submit" value="submit"></p>
-  <p><input type="button" value="button"></p>
-</form>
- -

次の CSS を適用してシステムレベルのスタイル設定を削除します。

- -
input {
-  -webkit-appearance: none;
-  appearance: none;
-}
- -
-

: プレフィックスつきのプロパティを使っているときでも、両方の定義を常に入れておくのが良いです — プレフィックス付きとなしと。プレフィックスつきのものは通常は"作業中"を意味するため、将来のブラウザーベンダーはプレフィックスを落とすよう合意することもあるでしょう。上記のコードではそのような結末となった将来でも耐用できます。

-
- -

下記の例ではあなたのシステムでどのように見えるかを示します — デフォルトでは左で、上記の CSS が適用されると右です (その他のシステムでテストしたい場合はここも探してください)。

- -

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/appearence-tester.html", '100%', 400)}}

- -

たいていの場合、効果は枠線を除去し、CSS でのスタイル設定を少し簡単にしますが、それは本質できありません。いくつかの場合 — 検索やラジオボタン/チェックボックスでは、もっと便利です。これを見ていきましょう。

- -

検索ボックスを変更する

- -

<input type="search"> は基本的に単なるテキスト入力のため、なぜ appearance: none; が便利なのでしょうか? 答えは macOS での Chromium ベースのブラウザーでは、検索ボックスはスタイル設定の制限があります — 例えば heightfont-size を自在に調整できません。この理由は macOS以外の Chrome ブラウザーはもう WebKit レンダリングエンジンを使っていません 、これは既定では、いくつかのフォームコントロールで Aqua の見た目が有効です。Aqua が有効だと、いくつかのフォームコントロールは scalable となりません。

- -

これは デフォルトの Aqua を無効にする appearance: none;で修正できます:

- -
input[type="search"] {
-    -webkit-appearance: none;
-    appearance: none;
-}
- -

下記の例では、2 つのスタイル設定された検索ボックスが見えます。右のものは appearance: none; が適用され、左はそうでありません。macOS Chrome で見ると左のものは正しいサイズでないように見えます。

- -

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/search-appearence.html", '100%', 200)}}

- -

興味深いことに、検索フィールドで border/background をセットしてもこの問題を解決できます、なぜならそれも Aqua を無効化や "破壊する" からです。下記のスタイル設定された検索ボックスは appearance: none; が適用されていませんが、前の例と同じ macOS Chrome の問題に悩まされていません。

- -

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/styled-search.html", '100%', 200)}}

- -
-

: 検索フィールドでは、Edge と Chrome で入力がフォーカスされないときに "x" の削除アイコンが消えるが、Safari では残ることに気づくこともあるでしょう。CSS で消すには、input[type="search"]::-webkit-search-cancel-button { display: none; }を使用できますが、フォーカス時のアイコンも取り除き、見た目が元に戻らないようです。

-
- -

チェックボックスとラジオボタン

- -

チェックボックスやラジオボタンのスタイリングは難しい場合があります。たとえば、チェックボックスやラジオボタンのサイズはデフォルトのデザインの変更が意図されておらず、ブラウザーで試してみると非常に異なる反応を示します。

- -

例えば、シンプルなテストケースを考えてみます:

- -
<span><input type="checkbox"></span>
- -
span {
-    display: inline-block;
-    background: red;
-}
-
-input[type=checkbox] {
-    width : 100px;
-    height: 100px;
-}
- -

さまざまなブラウザーでの処理方法は以下のとおりです:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ブラウザー描画結果
Firefox 71 (macOS)
Firefox 57 (Windows 10)
Chrome 77 (macOS), Safari 13, Opera
Chrome 63 (Windows 10)
Internet Explorer 11 (Windows 10)
Edge 16 (Windows 10)
- -

ラジオボタン/チェックボックスで appearance: none を使う

- -

これまで見てきたように、チェックボックスやラジオボタンのデフォルトの見た目を {{cssxref('appearance')}}:none; で削除できます。この HTML の例を見てみましょう:

- -
<form>
-  <fieldset>
-    <legend>Fruit preferences</legend>
-
-    <p>
-      <label>
-        <input type="checkbox" name="fruit-1" value="cherry">
-        I like cherry
-      </label>
-    </p>
-    <p>
-      <label>
-        <input type="checkbox" name="fruit-2" value="banana" disabled>
-        I can't like banana
-      </label>
-    </p>
-    <p>
-      <label>
-        <input type="checkbox" name="fruit-3" value="strawberry">
-        I like strawberry
-      </label>
-    </p>
-  </fieldset>
-</form>
- -

さて、カスタムチェックボックスデザインでこれらのスタイルを設定しましょう。元のチェックボックスを解除することから始めましょう:

- -
input[type=checkbox] {
-  -webkit-appearance: none;
-  appearance: none;
-}
- -

{{cssxref(":checked")}} と {{cssxref(":disabled")}} 擬似クラスを使用して、状態の変化に合わせてカスタムチェックボックスの外観を変更します:

- -
input[type=checkbox] {
-  position: relative;
-  width: 1em;
-  height: 1em;
-  border: 1px solid gray;
-  /* Adjusts the position of the checkboxes on the text baseline */
-  vertical-align: -2px;
-  /* Set here so that Windows' High-Contrast Mode can override */
-  color: green;
-}
-
-input[type=checkbox]::before {
-  content: "✔";
-  position: absolute;
-  font-size: 1.2em;
-  right: 0;
-  top: -0.3em;
-  visibility: hidden;
-}
-
-input[type=checkbox]:checked::before {
-  /* Use `visibility` instead of `display` to avoid recalculating layout */
-  visibility: visible;
-}
-
-input[type=checkbox]:disabled {
-  border-color: black;
-  background: #ddd;
-  color: gray;
-}
- -

こうした疑似クラスなどは、次の記事で見つけられますが、上記は次のことをしています:

- - - -

実際の結果が表示されます。

- -

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/checkboxes-styled.html", '100%', 200)}}

- -

もっと理解できるよう別の例もあります:

- - - -

{{cssxref("appearance")}} や をサポートしていないブラウザーでこれらのチェックボックスを表示できます。カスタムデザインは失われますが、チェックボックスのままに見えて使用できます。

- -
-

注記: Internet Explorer はどのバージョンの appearance もサポートしませんが、input[type=checkbox]::-ms-check にて IE のみチェックボックスをターゲットにできます。この手法は、-ms-check という名前にもかかわらず、ラジオボタンでも機能します。

-
- -

"劣悪な"要素に何ができるか?

- -

今度は"劣悪な"コントロールに注目しましょう — これは完全にスタイル設定するのが本当に難しいものです。簡単にいうと、これはドロップダウンボックス、colordatetime-local のような複合コントロールタイプ、フィードバック —  {{HTMLElement("progress")}} や {{HTMLElement("meter")}}のような指向性コントロールです。

- -

問題は、要素はブラウザー同士でいろいろな既定の見た目があって、それにスタイル設定できても、内部のいくつかはスタイル設定できないことです。

- -

ルック&フィールの違いを受け入れる覚悟があれば、サイズ変更を一貫したものにするためのシンプルなスタイル設定や、background-colors のような単一スタイル設定、システムレベルのスタイル設定を除去できる appearance の使用などで逃げることもできます。

- -

例を見てみましょう、たくさんの"劣悪な" フォーム機能をつぎつぎに表示しています:

- -

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/ugly-controls.html", '100%', 750)}}

- -

この例では下記の CSS を適用しています:

- -
body {
-  font-family: 'Josefin Sans', sans-serif;
-  margin: 20px auto;
-  max-width: 400px;
-}
-
-form > div {
-  margin-bottom: 20px;
-}
-
-select {
-  -webkit-appearance: none;
-  appearance: none;
-}
-
-.select-wrapper {
-  position: relative;
-}
-
-.select-wrapper::after {
-  content: "▼";
-  font-size: 1rem;
-  top: 6px;
-  right: 10px;
-  position: absolute;
-}
-
-button, label, input, select, progress, meter {
-  display: block;
-  font-family: inherit;
-  font-size: 100%;
-  padding: 0;
-  margin: 0;
-  box-sizing: border-box;
-  width: 100%;
-  padding: 5px;
-  height: 30px;
-}
-
-input[type="text"], input[type="datetime-local"], input[type="color"], select {
-  box-shadow: inset 1px 1px 3px #ccc;
-  border-radius: 5px;
-}
-
-label {
-  margin-bottom: 5px;
-}
-
-button {
-  width: 60%;
-  margin: 0 auto;
-}
- -
-

注記: 多数のブラウザーで同時にこの例をテストしたい場合、live版をここで見つけてください (ソースコードも見てください)。

- -

また JavaScript をページに追加してコントロール自身の下にあるファイルピッカー自身で選択されたファイルを一覧しているのを心に留めておいてください。これは <input type="file"> リファレンスページの例を簡単にしたバージョンです。

-
- -

ご覧のとおり、これらをモダンブラウザー全体で均一に見せることはかなりうまくいっています。

- -

すべてのコントロールとそのラベルに対してグローバルな CSS の正規化を適用し、サイズを同様にして、親のフォントを適用するなどを行っています。これは前の記事で述べたようなことです:

- -
button, label, input, select, progress, meter {
-  display: block;
-  font-family: inherit;
-  font-size: 100%;
-  padding: 0;
-  margin: 0;
-  box-sizing: border-box;
-  width: 100%;
-  padding: 5px;
-  height: 30px;
-}
- -

コントロールに統一した影と角丸も与えて、意味のあるようにします:

- -
input[type="text"], input[type="datetime-local"], input[type="color"], select {
-  box-shadow: inset 1px 1px 3px #ccc;
-  border-radius: 5px;
-}
- -

範囲や、プログレスバーや、メーターはコントロールエリアの周りに美しくないボックスができるだけなので、意味はありません。

- -

これらのコントロールタイプそれぞれの仕様と、ハイライトの難しさをこれからお話ししましょう。

- -

Select とデータリスト

- -

モダンブラウザーでは、select とデータリストは一般的に、デフォルトからルック&フィールを大きく変えたくないようにスタイル設定する場合、悪くないものです。

- -

これまで、ボックスの基本的な見た目をかなり均一で一貫性のあるものにしてきました。とにかくデータリストコントロールは <input type="text"> なので、問題にならないことがわかっています。

- -

2 つのものが多少問題をはらんでいます。まず最初に、select がドロップダウンであることを示す "矢印" アイコンは、ブラウザーによって異なります。また select ボックスのサイズを増やしたり、変更したりすると、醜くなりがちです。これを修正するため、例では最初に旧友の appearance: none を使ってアイコンを除去しています:

- -
select {
-  -webkit-appearance: none;
-  appearance: none;
-}
- -

次に生成されたコンテンツを使って独自のアイコンを作成します。コントロールの周りに特別なラッパーを置いていて、その理由は::before/::after<select> 要素では動作しなしためです (これは生成されたコンテンツが要素がフォーマットするボックスに対し相対的に配置されますが、フォーム入力は置換された要素 — 表示がブラウザーによって生成されて順番に配置されるもの — として動作するので、1 つもないためです):

- -
<div class="select-wrapper"><select id="select" name="select">
-  <option>Banana</option>
-  <option>Cherry</option>
-  <option>Lemon</option>
-</select></div>
- -

次に生成されたコンテンツを使って小さな下向き矢印を作り、正しい場所にポジショニングします:

- -
.select-wrapper {
-  position: relative;
-}
-
-.select-wrapper::after {
-  content: "▼";
-  font-size: 1rem;
-  top: 6px;
-  right: 10px;
-  position: absolute;
-}
- -

2 つ目のもう少し有名な問題は、<select> ボックスをクリックして開いたときに出てくる、オプションを含んだボックスを制御できないことです。オプションが親でセットされたフォントを継承していないのに気づくでしょう。また余白スペースや色のようなものも一貫してセットできません。例えば、Firefox では<option> 要素に colorbackground-color を適用できますが、Chrome ではそうなりません。どちらも余白スペース (例 padding)を適用できません。同じことはデータリスト用に出てくるオートコンプリートのリストにも当てはまります。

- -

本当にオプションをスタイル設定する必要がある場合、カスタムコントロールを生成するライブラリを使用するか、独自のカスタムコントロールを作成するかのどちらかが必要で、あるいは multiple属性を使う select の場合、これはページ上に出てくるすべてのオプションを作成し、この特殊な問題を避けることができます:

- -
<select id="select" name="select" multiple>
-  ...
-</select>
- -

もちろんこれでも進めているデザインにはフィットしないこともありますが、注目に値します!

- -

日付入力タイプ

- -

日付/時間の入力タイプ (datetime-local, time, week, month) にはすべて同じ重大な関連した問題があります。実際のボックスはテキスト入力と同じくスタイル設定が容易であり、デモ内で得たものの見た目は良いです。

- -

しかし、コントロールの内部パーツ (例 日付をピックアップするのに使うポップアップカレンダー、値を増減するスピナー) はまったくスタイル設定できず、appearance: none;を使ってスタイルを除去できません。スタイル設定に完全なコントロールが必要な場合、カスタムコントロールを生成するライブラリを使うか、自分で作らねばなりません。

- -
-

: ここでも<input type="number"> は触れる価値があります — これにも値を増減するスピナーがあるので、同じ問題に悩まさされます。しかし、number タイプを使った場合にデータは簡単に集められて、単に text 入力を (あるいはモバイルブラウザーで数字キーパッドを表示するのに tel を) 代わりに使うのも簡単です

-
- -

Range 入力タイプ

- -

<input type="range"> はスタイル設定が煩わしいです。下記のようなものを使ってデフォルトのスライダートラックを完全に削除してカスタムスタイル (ここでは、薄い赤のトラック)に置き換えます:

- -
input[type="range"] {
-  appearance: none;
-  -webkit-appearance: none;
-  background: red;
-  height: 2px;
-  padding: 0;
-  outline: 1px solid transparent;
-}
- -

しかし、range コントロールのドラッグハンドルをカスタマイズするのはとても難しいです — range のスタイル設定を完全完全にコントロールするには、その中に複数の非標準な、ブラウザー固有の疑似要素も含んだ、複雑な CSS コードが必要です。Styling Cross-Browser Compatible Range Inputs with CSS で必要なものを細かく書くための CSS トリックを確認します。

- -

Color 入力タイプ

- -

color 入力タイプはそこまでひどくないです。サポートされたブラウザーでは、単に小さな枠のある単色のブロックを提供します。

- -

枠を削除して、色のブロックだけにするには、次のようにします:

- -
input[type="color"] {
-  border: 0;
-  padding: 0;
-}
- -

しかし、カスタムソリューションは大きく異るようにする唯一の方法です。

- -

ファイル入力タイプ

- -

ファイル入力タイプは通常は OK です — 例で見てきたように、ページの残りの部分に問題なくフィットする何かを作るのはとても簡単です — コントロールの一部分でもある出力行は、入力にそう指示している場合は親のフォントを継承して、カスタムリストに名前やサイズをお好みでスタイル設定できます; 結局は作っています。

- -

ファイルピッカーの唯一の問題は、ファイルピッカーを開くボタンは完全にスタイル設定できないことです— サイズや色設定は不可能で、別のフォントすら指定できません。

- -

この回避法は、フォームコントロールに関連するラベルがある場合、ラベルをクリックするとコントロールがアクティブになるという事実を利用します。つまり実際のフォーム入力を次のように隠します:

- -
input[type="file"] {
-  height: 0;
-  padding: 0;
-  opacity: 0;
-}
- -

次にボタンのように動作するラベルをスタイル設定し、そのラベルが押された時にファイルピッカーが期待通り開くようにします:

- -
label[for="file"] {
-  box-shadow: 1px 1px 3px #ccc;
-  background: linear-gradient(to bottom, #eee, #ccc);
-  border: 1px solid rgb(169, 169, 169);
-  border-radius: 5px;
-  text-align: center;
-  line-height: 1.5;
-}
-
-label[for="file"]:hover {
-  background: linear-gradient(to bottom, #fff, #ddd);
-}
-
-label[for="file"]:active {
-  box-shadow: inset 1px 1px 3px #ccc;
-}
- -

上記の CSS スタイル設定の結果は、下記のライブ実行の例で見ることができます (styled-file-picker.html のライブと、ソースコードも見てください)。

- -

{{EmbedGHLiveSample("learning-area/html/forms/styling-examples/styled-file-picker.html", '100%', 200)}}

- -

メーターとプログレスバー

- -

<meter><progress> は多くの中で最悪かもしれません。前の例で見たように、希望する幅にだいたい正確に設定できました。しかしそれを超えると、どんな方法でもスタイル設定が本当に難しいです。高さの設定をお互いに、ブラウザー間で一貫して処理できず、背景は色付けできるものの、前面のバーはできず、appearance: none を設定すると良くならず、より悪くなります。

- -

スタイル設定を制御したい場合は、この機能のカスタムソリューションを作ったり、progressbar.js のようなサードパーティのソリューションを使うのは簡単です。

- -

よりよいフォームへの道: 役に立つライブラリとポリフィル

- -

すでにいくつか見てきたように、"劣悪な"コントロールを完全にコントロールしたい場合は、JavaScript に頼るしかありません。カスタムフォームウィジェットをビルドする方法の記事では、独自のフォームウィジェットを作成する方法を見ていきますが、そこには役に立つ便利なライブラリがいくつかあります。

- - - -

次のライブラリはフォームだけではありませんが、HTML フォームを処理するための非常に興味深い機能を備えています:

- - - -

CSS と JavaScript には副作用があることに注意してください。したがって、それらのライブラリのいずれかを使用することを選択した場合は、スクリプトが失敗した場合に備えて、堅牢なフォールバック HTML を用意する必要があります。スクリプトが失敗する理由はたくさんあります。特にモバイル環境では、これらのケースを可能な限り最善に処理するようにウェブサイトやアプリケーションを設計する必要があります。

- -

スキルをテストしましょう!

- -

この記事の最後に到達しましたが、重要な情報を覚えていますか?次に進む前に、この情報を保持しているか検証するテストがあります — Test your skills: Advanced styling を見てください。心に留めておくこととして、この評価シリーズでの質問は、次の記事の知識も前提していてるので、試す前にその記事に取り組むとよいかもしれません。

- -

まとめ

- -

HTML フォームで CSS を使用するのはまだ困難ですが、しばしばそれらを回避する方法があります。クリーンでユニバーサルな解決方法はありませんが、最新のブラウザーでは新しい可能性があります。今のところ、最良の解決策は、HTML フォームウィジェットに適用されたときに異なるブラウザーが CSS をサポートする方法の詳細を学ぶことです。

- -

このガイドの次の記事では、モダンブラウザーでさまざまな状態のフォームをスタイル設定できるさまざまな UI 疑似クラスを見ていきます。

- -

{{PreviousMenuNext("Learn/Forms/Styling_web_forms", "Learn/Forms/UI_pseudo-classes", "Learn/Forms")}} 

- -

このモジュール

- - - -

Advanced Topics

- - diff --git a/files/ja/learn/forms/basic_native_form_controls/index.html b/files/ja/learn/forms/basic_native_form_controls/index.html new file mode 100644 index 0000000000..f0ddcdc09d --- /dev/null +++ b/files/ja/learn/forms/basic_native_form_controls/index.html @@ -0,0 +1,339 @@ +--- +title: 基本的なネイティブフォームコントロール +slug: Learn/Forms/The_native_form_widgets +tags: + - Example + - Forms + - Guide + - HTML + - Intermediate + - Web +translation_of: Learn/Forms/Basic_native_form_controls +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Forms/How_to_structure_a_web_form", "Learn/Forms/HTML5_input_types", "Learn/Forms")}}
+ +

直前の記事では、機能的なウェブフォームの例をマークアップし、いくつかのフォームコントロールとよくある構造要素を導入し、アクセシビリティのベストプラクティスを見てきました。次にさまざまなフォームコントロールやウィジェットの機能を詳しく見ていきます — 色々な種類のデータを集めるのにどんなオプションが使えるのかを見ていきます。とりわけこの記事では、ウェブの初期からある全てのブラウザーで利用できる、オリジナルのフォームコントロールを見ていきます。

+ + + + + + + + + + + + +
前提条件:基本的なコンピューターリテラシーと、基本的な HTML の理解
目的:データを収集するためにブラウザーで使用できるネイティブフォームウィジェットの種類と、それらを HTML を使用して実装する方法を理解する。
+ +

{{HTMLelement('form')}}, {{HTMLelement('fieldset')}}, {{HTMLelement('legend')}}, {{HTMLelement('textarea')}}, {{HTMLelement('label')}}, {{HTMLelement('button')}},  {{HTMLelement('input')}}といったフォーム要素については既に見てきました。この記事では次を網羅します:

+ + + +
+

: この記事で説明されている機能のほとんどは、ブラウザー間で幅広くサポートされています。これに対する例外に注意しましょう。より正確な詳細が必要な場合は、HTML フォーム要素のリファレンス、特に広範囲にわたる <input> 型の参照を参照してください。

+
+ +

テキスト入力フィールド

+ +

テキスト {{htmlelement("input", "入力")}} フィールドは最も基本的なフォームウィジェットです。これらはユーザーがあらゆる種類のデータを入力できるとても便利な方法です。

+ +
+

: HTML フォームのテキストフィールドは単純なプレーンテキストの入力コントロールです。つまり、これらを使ってリッチエディット (太字、斜体など) を実行することはできません。見かけるすべてのリッチテキストエディタは、HTML、CSS、および JavaScript で作成されたカスタムウィジェットです。

+
+ +

すべてのテキストフィールドに共通する動作があります:

+ + + +
+

: {{htmlelement("input")}} 要素は、type 属性によってさまざまなフォームとなるため、、HTML要素の中でも特別です。単一行のテキストフィールド、テキスト入力のないコントロール、時間と日付のコントロール、チェックボックス、カラーピッカー、ボタンといったテキスト入力のないコントロールなど、ほとんどのタイプのフォームウィジェットの作成に使用されます。

+
+ +

単一行のテキストフィールド

+ +

単一行のテキストフィールドは、{{htmlattrxref("type","input")}} 属性値が text に設定されている {{HTMLElement("input")}} 要素を使用するか、{{htmlattrxref("type","input")}} 属性を指定しない場合( text がデフォルト値になり)に作成されます。{{htmlattrxref("type","input")}} 属性に指定した値がブラウザーに認識されない場合 (たとえば type="color" を指定してブラウザーがネイティブの色ピッカーをサポートしていない場合)、この属性の値のテキストは代替値になります。

+ +
+

: GitHub の single-line-text-fields.html に、すべての単一行テキストフィールドタイプの例があります (こちらも参照してください)。

+
+ +

これは基本的な単一行のテキストフィールドの例です。

+ +
<input type="text" id="comment" name="comment" value="I'm a text field">
+ +

単一行のテキストフィールドは、ひとつだけ厳密な制約があります: 改行を含むテキストを入力した場合、ブラウザーはデータを送信する前に改行を取り除きます。

+ +

下記のスクリーンショットは macOS での Firefox 71 と Safari と Windows 10 の Chrome 79 と Edge 18 にて、既定の、フォーカスされた、無効にされたテキスト入力を示しています。

+ +

Screenshot of the disabled attribute and default :focus styles on a text input in Firefox, Safari, Chrome and Edge.

+ +
+

HTML5 では {{htmlattrxref("type","input")}} 属性に専用の値を追加することで、基本的な単一行のテキストフィールドを拡張しています。これらの値もやはり {{HTMLElement("input")}} 要素を単一行のテキストフィールドにしますが、フィールドに対して追加の制約や機能を付加します。

+
+ +

パスワードフィールド

+ +

このタイプのフィールドは、{{htmlattrxref("type","input")}} 属性の値 password を使用して設定できます:

+ +
<input type="password" id="pwd" name="pwd">
+ +

password の値は入力したテキストに対する特別な制約は付加しませんが、フィールドの値を隠します(例、ドットやアスタリスク)ので読むことができません。

+ +

これはユーザーインターフェイスの機能でしかないことに注意してください。テキストは JavaScript を使用してあなた自身でエンコードしなければ、平文で送信されてしまい、セキュリティには良くありません — 悪い組織がデータを遮ってパスワードや、クレジットカードデータや、送信したあらゆるものを盗むことがあります。ユーザーからこれを保護するためにはフォームを含むあらゆるページをセキュア通信でホストし (つまり https:// ... アドレスにて) 、データ送信前に暗号化することです。

+ +

最近のブラウザーは、安全でない接続を介してフォームデータを送信することによるセキュリティへの影響を認識しており、ユーザーが安全でないフォームを使用しないように警告を実装しています。Firefox が実装しているものの詳細については、安全でないパスワードをご覧ください。

+ +

隠しコンテンツ

+ +

もう1つのオリジナルなテキストコントロールは hidden 入力タイプです。これは他のフォームデータとともにサーバー送信されるがユーザーからは見えないデータを持つのに使われています — 例えば命令を発行するときにサーバーにタイムスタンプを送りたい場合。これは隠れているので、ユーザーが見ることも、意図せずに値を編集することもなく、フォーカスを得ることもないしスクリーンリーダーが気づくこともありません。

+ +
<input type="hidden" id="timestamp" name="timestamp" value="1286705410">
+
+ +

このような要素を作成する場合は、name 属性と value 属性の設定が必要です。この値は JavaScript にて動的にセットできます。hidden 入力タイプには関連したラベルはありません。

+ +

その他のテキストタイプ、{{HTMLElement("input/search", "search")}}, {{HTMLElement("input/url", "url")}}, と{{HTMLElement("input/tel", "tel")}}, は HTML5 で追加されました。これは次のチュートリアルの「HTML5 入力タイプ」にて網羅されます。

+ +

チェック可能アイテム:チェックボックスとラジオボタン

+ +

チェック可能アイテムは、そのものや、関連したラベルをクリックすることで状態を変更できるコントロールです。チェック可能アイテムは 2 種類あります: チェックボックスとラジオボタンです。どちらもデフォルトでチェックするかを示すために、checked 属性を使用します。

+ +

これらのウィジェットは、他のフォームウィジェットと同じようには動作しない点が特筆されます。ほとんどのフォームウィジェットではフォームを送信すると、name 属性を持つすべてのウィジェットは値がなくても送信します。チェック可能アイテムでは、それらがチェックされている場合にのみ値を送信します。チェックされていない場合は、name も含めて何も送信しません。チェックされているが値がない場合、name が on という値で送信されます。

+ +
+

: このセクションの例は、checkable-items.html として GitHub にあります (こちらも参照してください)。

+
+ +

最大限のユーザービリティ/アクセシビリティを実現するために、関連項目の各リストを {{htmlelement("fieldset")}} で囲み、リストの全体的な説明を示す {{htmlelement("legend")}} で囲むことをお勧めします。{{htmlelement("label")}}/{{htmlelement("input")}} 要素の個々のペアは、それぞれ独自のリスト項目 (または同様のもの) に含める必要があります。関連した {{htmlelement('label')}} はラジオボタンやチェックボックスの直後に、{{htmlelement("legend")}}の中身にラジオボタンやチェックボックスのグループの説明が置かれます。これは上の例に示されています。

+ +

チェックボックス

+ +

チェックボックスは、type 属性を {{HTMLElement("input/checkbox", "checkbox")}} に設定した {{HTMLElement("input")}} 要素で作成します。

+ +
<input type="checkbox" checked id="carrots" name="carrots" value="carrots">
+ +

checked 属性を含んだチェックボックスはページ読み込み時に自動的にチェックされます。チェックボックスまたはその関連ラベルをチェックするとチェックボックスのオン/オフがトグルされます。

+ +

下記のスクリーンショットは macOS での Firefox 71 と Safari と Windows 10 の Chrome 79 と Edge 18 にて、既定の、フォーカスされた、無効にされたチェックボックスを示しています。

+ +

Default, focused and disabled Checkboxes in Firefox 71 and Safari 13 on Mac and Chrome 79 and Edge 18 on Windows 10

+ +
+

: checked 属性のあるあらゆるチェックボックスやラジオボタンには、チェックされていない場合でも、対応する {{cssxref(':default')}} 仮想クラスがあります。現在チェックされているものには{{cssxref(':checked')}} 仮想クラスがあります。

+
+ +

チェックボックスのオンオフ性質により、チェックボックスは、規定のチェックボックスを拡張してトグルスイッチのように見えるボタンを作っている開発者やデザイナーにとって、トグルボタンとして考えられます。ここで動作する例を 見ることができます(ソースコードも見られます)。

+ +

ラジオボタン

+ +

ラジオボタンは、{{htmlattrxref("type","input")}} 属性を radio に設定した {{HTMLElement("input")}} 要素で作成します。

+ +
<input type="radio" checked id="soup" name="meal">
+ +

いくつかのラジオボタンをまとめることができます。{{htmlattrxref("name","input")}} 属性で同じ値を共有すると、それらのラジオボタンは同じボタングループに属するとみなされます。グループ内でボタンは同時に 1 つだけチェックできます。つまり、あるラジオボタンをチェックすると、他のラジオボタンは自動的にチェックが外れます。フォームを送信するときは、チェックしているラジオボタンのみの値を送信します。何もチェックしていない場合はラジオボタンの集まり全体が未知の状態であるとみなし、フォーム送信時は値を送信しません。

+ +
<fieldset>
+  <legend>What is your favorite meal?</legend>
+  <ul>
+    <li>
+      <label for="soup">Soup</label>
+      <input type="radio" checked id="soup" name="meal" value="soup">
+    </li>
+    <li>
+      <label for="curry">Curry</label>
+      <input type="radio" id="curry" name="meal" value="curry">
+    </li>
+    <li>
+      <label for="pizza">Pizza</label>
+      <input type="radio" id="pizza" name="meal" value="pizza">
+    </li>
+  </ul>
+</fieldset>
+ +

下記のスクリーンショットは macOS での Firefox 71 と Safari と Windows 10 の Chrome 79 と Edge 18 にて、チェックなしとチェックされたラジオボタン、フォーカスされた、また無効でチェックなしとチェックされたラジオボタンを示しています。

+ +

Radio buttons on Firefox 71 and Safari 13 on Mac and Chrome 79 and Edge 18 on Windows 10

+ +

ボタン

+ +

ラジオボタンはその名に反して、実際のボタンではありません。実際のボタンを見てみましょう! ボタンを生成するには、3 種類の入力タイプがあります:

+ +
+
{{原語併記("送信", "Submit")}}
+
フォームデータをサーバーに送信します。{{HTMLElement("button")}} 要素の場合、type 属性 (または type の無効な値) を省略すると、送信ボタンが表示されます。
+
{{原語併記("リセット", "Reset")}}
+
すべてのフォームウィジェットをデフォルト値にリセットします。
+
button
+
自動的な効果のないボタンで、JavaScript コードを用いてカスタマイズできるもの。
+
+ +

それから、{{htmlelement("button")}} 要素それ自体もあります。これは値が submitreset または button である type 属性をとり、上記の 3 つの <input> 種別を模倣できます。この 2 つの主な違いは実際の <button> 要素の方が多くのスタイル設定できることです。

+ +
+

: image 入力タイプもボタンとしてレンダリングされます。それはあとで見ます。

+
+ +
+

: このセクションの例は button-examples.html として GitHub にあります (こちらも参照してください)。

+
+ +

ボタンは {{HTMLElement("button")}} 要素か {{HTMLElement("input")}} 要素で作成します。どの種類のボタンを表示するかを指定するのは、{{htmlattrxref("type","input")}} 属性の値です:

+ +

送信

+ +
<button type="submit">
+    This a <br><strong>submit button</strong>
+</button>
+
+<input type="submit" value="This is a submit button">
+ +

リセット

+ +
<button type="reset">
+    This a <br><strong>reset button</strong>
+</button>
+
+<input type="reset" value="This is a reset button">
+ +

無名

+ +
<button type="button">
+    This an <br><strong>anonymous button</strong>
+</button>
+
+<input type="button" value="This is an anonymous button">
+ +

ボタンは {{HTMLElement("button")}} 要素でも {{HTMLElement("input")}} 要素でも、常に同じ動作になります。上記のサンプルでわかるように、{{HTMLElement("button")}} 要素はラベルとして HTML コンテンツを使用できて、これは開始と終了の<button>タグの間に挿入されます。一方で{{HTMLElement("input")}} 要素は空要素です。つまり value 属性の中にラベルが挿入され、このためプレーンテキストのコンテンツのみ使用できます。

+ +

下記の例は macOS での Firefox 71 と Safari と Windows 10 の Chrome 79 と Edge 18 にて、既定の、フォーカスされた、無効なボタンを示しています。

+ +

Default, focused and disabled button input types in Firefox 71 and Safari 13 on Mac and Chrome 79 and Edge 18 on Windows 10

+ +

画像ボタン

+ +

画像ボタンコントロールは {{HTMLElement("img")}} 要素とまったく同じように表示されますが、ユーザーがクリックすると送信ボタン (前述) のように動作します。

+ +

画像ボタンは、{{htmlattrxref("type","input")}} 属性を image に設定した {{HTMLElement("input")}} 要素で作成します。

+ +

この要素は {{HTMLElement("img")}} 要素とまったく同じ属性をサポートして、さらにフォームボタンがサポートする属性もすべてサポートします。

+ +
<input type="image" alt="Click me!" src="my-img.png" width="80" height="30" />
+ +

画像ボタンをフォームの送信に使用する際にこのウィジェットは自身の値を送信しませんが、代わりに画像上でクリックした位置の X 座標と Y 座標を送信します (座標は画像に対して相対的、つまり画像の左上隅が座標 0, 0 になります)。座標は 2 つのキーと値の組として送信されます。

+ + + +

サンプルをご覧ください。フォームの画像上の座標 (123, 456) でクリックすると、 get メソッド経由で送信されて、以下のような値の追加された URL が送信されます:

+ +
http://foo.com?pos.x=123&pos.y=456
+ +

これは "hot map" を作成するためにとても便利な手段です。これらの値がどのように送信あるいは取得されるかについては、フォームデータの送信の記事で詳しく説明します。

+ +

ファイルピッカー

+ +

初期のHTMLであった最後の <input> タイプがあります: ファイル入力タイプです。フォームで、ファイルをサーバーに送信できます。この特定操作については以下の記事で詳しく説明します: フォームデータの送信。ファイルピッカーウィジェットで、ユーザーは送信するファイルを 1 つ以上選択できます。

+ +

ファイルピッカーウィジェットを作成するには、{{htmlattrxref("type","input")}} 属性を file に設定した {{HTMLElement("input")}} 要素を使用します。{{htmlattrxref("accept","input")}} 属性を使用して、受け入れるファイルの種類を制限できます。加えて、ユーザーが複数のファイルを選択できるようにしたい場合は、{{htmlattrxref("multiple","input")}} 属性を付加します。

+ +

+ +

以下の例では、画像ファイルを要求するファイルピッカーを作成しています。ユーザーは複数のファイルを指定できます。

+ +
<input type="file" name="file" id="file" accept="image/*" multiple>
+ +

いくつかのモバイルデバイスでは、ファイルピッカーは、次のようにキャプチャー情報を accept 属性に追加することで、端末のカメラやマイクでキャプチャーされた写真、動画、オーディオにアクセスできます:

+ +
<input type="file" accept="image/*;capture=camera">
+<input type="file" accept="video/*;capture=camcorder">
+<input type="file" accept="audio/*;capture=microphone">
+ +

共通属性

+ +

フォームウィジェットを定義するために使用される要素の多くは、独自の属性をいくつか持っています。ただし、すべてのフォーム要素に共通の一連の属性があり、それによりウィジェットをある程度制御できます。共通属性のリストは以下のとおりです。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
属性名既定値説明
autofocusfalseこの真偽値属性を使用すると、ユーザーがページをロードするときに、たとえば別のコントロールを入力して上書きしない限り、要素に自動的に入力フォーカスするように指定できます。この属性を指定できるのは、文書内の 1 つのフォーム関連要素だけです。
disabledfalseこの真偽値属性は、ユーザーが要素と対話できないことを示します。この属性が指定されていない場合、要素はそれを含む要素 (例えば {{HTMLElement("fieldset")}}) からその設定を継承します。disabled 属性が設定されている包含要素がない場合は、その要素が有効になります。
formウィジェットが関連付けられている <form> 要素。属性の値は、同じ文書内の {{HTMLElement("form")}} 要素の id 属性でなければなりません。理論的には、フォームウィジェットを {{HTMLElement("form")}} 要素の外側に設定できます。しかし実際には、その機能をサポートするブラウザーはありません。
name要素の名前。これはフォームデータとともに送信されます。
value要素の初期値
+ +

スキルをテストしましょう!

+ +

この記事の最後に到着しましたが、最も大事な情報を覚えていますか? 次に進む前に、この情報を保持しているか検証するテストがあります — Test your skills: Basic controls を見てください。

+ +

まとめ

+ +

上で見たように、利用可能なフォーム要素には多くの異なるタイプがあります。一度にこれらの詳細の全てを覚えておく必要はありません。詳細について調べるために好きなだけこの記事に戻ることができます 。

+ +

この記事では古い入力タイプをカバーしてきました — これは HTML の初期の頃に導入されたオリジナルで、すべてのブラウザーでよくサポートされます。次のセクションでは、HTML 5 で追加された新しい type 属性の値を見ていきます。

+ +

{{PreviousMenuNext("Learn/Forms/How_to_structure_a_web_form", "Learn/Forms/HTML5_input_types", "Learn/Forms")}}

+ +

このモジュール

+ + + +

上級トピック

+ + diff --git a/files/ja/learn/forms/how_to_build_custom_form_controls/example_1/index.html b/files/ja/learn/forms/how_to_build_custom_form_controls/example_1/index.html new file mode 100644 index 0000000000..1515dc573f --- /dev/null +++ b/files/ja/learn/forms/how_to_build_custom_form_controls/example_1/index.html @@ -0,0 +1,415 @@ +--- +title: 例 1 +slug: Learn/Forms/How_to_build_custom_form_widgets/Example_1 +translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_1 +--- +

これは、カスタムフォームウィジェットの作成方法を説明する最初のコード例です。

+ +

Basic state

+ +

HTML

+ +
<div class="select">
+  <span class="value">Cherry</span>
+  <ul class="optList hidden">
+    <li class="option">Cherry</li>
+    <li class="option">Lemon</li>
+    <li class="option">Banana</li>
+    <li class="option">Strawberry</li>
+    <li class="option">Apple</li>
+  </ul>
+</div>
+ +

CSS

+ +
/* --------------- */
+/* Required Styles */
+/* --------------- */
+
+.select {
+  position: relative;
+  display : inline-block;
+}
+
+.select.active,
+.select:focus {
+  box-shadow: 0 0 3px 1px #227755;
+  outline: none;
+}
+
+.select .optList {
+  position: absolute;
+  top     : 100%;
+  left    : 0;
+}
+
+.select .optList.hidden {
+  max-height: 0;
+  visibility: hidden;
+}
+
+/* ------------ */
+/* Fancy Styles */
+/* ------------ */
+
+.select {
+  font-size   : 0.625em; /* 10px */
+  font-family : Verdana, Arial, sans-serif;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
+  width   : 10em; /* 100px */
+
+  border        : 0.2em solid #000; /* 2px */
+  border-radius : 0.4em; /* 4px */
+
+  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
+
+  background : #F0F0F0;
+  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+}
+
+.select .value {
+  display  : inline-block;
+  width    : 100%;
+  overflow : hidden;
+
+  white-space   : nowrap;
+  text-overflow : ellipsis;
+  vertical-align: top;
+}
+
+.select:after {
+  content : "▼";
+  position: absolute;
+  z-index : 1;
+  height  : 100%;
+  width   : 2em; /* 20px */
+  top     : 0;
+  right   : 0;
+
+  padding-top : .1em;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  text-align : center;
+
+  border-left  : .2em solid #000;
+  border-radius: 0 .1em .1em 0;
+
+  background-color : #000;
+  color : #FFF;
+}
+
+.select .optList {
+  z-index : 2;
+
+  list-style: none;
+  margin : 0;
+  padding: 0;
+
+  background: #f0f0f0;
+  border: .2em solid #000;
+  border-top-width : .1em;
+  border-radius: 0 0 .4em .4em;
+
+  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  min-width : 100%;
+  max-height: 10em; /* 100px */
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.select .option {
+  padding: .2em .3em;
+}
+
+.select .highlight {
+  background: #000;
+  color: #FFFFFF;
+}
+
+ +

Result for basic state

+ +
{{ EmbedLiveSample('Basic_state', 120, 130) }}
+ +

Active state

+ +

HTML

+ +
<div class="select active">
+  <span class="value">Cherry</span>
+  <ul class="optList hidden">
+    <li class="option">Cherry</li>
+    <li class="option">Lemon</li>
+    <li class="option">Banana</li>
+    <li class="option">Strawberry</li>
+    <li class="option">Apple</li>
+  </ul>
+</div>
+ +

CSS

+ +
/* --------------- */
+/* Required Styles */
+/* --------------- */
+
+.select {
+  position: relative;
+  display : inline-block;
+}
+
+.select.active,
+.select:focus {
+  box-shadow: 0 0 3px 1px #227755;
+  outline: none;
+}
+
+.select .optList {
+  position: absolute;
+  top     : 100%;
+  left    : 0;
+}
+
+.select .optList.hidden {
+  max-height: 0;
+  visibility: hidden;
+}
+
+/* ------------ */
+/* Fancy Styles */
+/* ------------ */
+
+.select {
+  font-size   : 0.625em; /* 10px */
+  font-family : Verdana, Arial, sans-serif;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
+  width   : 10em; /* 100px */
+
+  border        : 0.2em solid #000; /* 2px */
+  border-radius : 0.4em; /* 4px */
+
+  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
+
+  background : #F0F0F0;
+  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+}
+
+.select .value {
+  display  : inline-block;
+  width    : 100%;
+  overflow : hidden;
+
+  white-space   : nowrap;
+  text-overflow : ellipsis;
+  vertical-align: top;
+}
+
+.select:after {
+  content : "▼";
+  position: absolute;
+  z-index : 1;
+  height  : 100%;
+  width   : 2em; /* 20px */
+  top     : 0;
+  right   : 0;
+
+  padding-top : .1em;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  text-align : center;
+
+  border-left  : .2em solid #000;
+  border-radius: 0 .1em .1em 0;
+
+  background-color : #000;
+  color : #FFF;
+}
+
+.select .optList {
+  z-index : 2;
+
+  list-style: none;
+  margin : 0;
+  padding: 0;
+
+  background: #f0f0f0;
+  border: .2em solid #000;
+  border-top-width : .1em;
+  border-radius: 0 0 .4em .4em;
+
+  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  min-width : 100%;
+  max-height: 10em; /* 100px */
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.select .option {
+  padding: .2em .3em;
+}
+
+.select .highlight {
+  background: #000;
+  color: #FFFFFF;
+}
+ +

Result for active state

+ +
{{ EmbedLiveSample('Active_state', 120, 130) }}
+ +

Open state

+ +

HTML

+ +
<div class="select active">
+  <span class="value">Cherry</span>
+  <ul class="optList">
+    <li class="option highlight">Cherry</li>
+    <li class="option">Lemon</li>
+    <li class="option">Banana</li>
+    <li class="option">Strawberry</li>
+    <li class="option">Apple</li>
+  </ul>
+</div>
+ +

CSS

+ +
/* --------------- */
+/* Required Styles */
+/* --------------- */
+
+.select {
+  position: relative;
+  display : inline-block;
+}
+
+.select.active,
+.select:focus {
+  box-shadow: 0 0 3px 1px #227755;
+  outline: none;
+}
+
+.select .optList {
+  position: absolute;
+  top     : 100%;
+  left    : 0;
+}
+
+.select .optList.hidden {
+  max-height: 0;
+  visibility: hidden;
+}
+
+/* ------------ */
+/* Fancy Styles */
+/* ------------ */
+
+.select {
+  font-size   : 0.625em; /* 10px */
+  font-family : Verdana, Arial, sans-serif;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
+  width   : 10em; /* 100px */
+
+  border        : 0.2em solid #000; /* 2px */
+  border-radius : 0.4em; /* 4px */
+
+  box-shadow : 0 0.1em 0.2em rgba(0, 0, 0, .45); /* 0 1px 2px */
+
+  background : #F0F0F0;
+  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+}
+
+.select .value {
+  display  : inline-block;
+  width    : 100%;
+  overflow : hidden;
+
+  white-space   : nowrap;
+  text-overflow : ellipsis;
+  vertical-align: top;
+}
+
+.select:after {
+  content : "▼";
+  position: absolute;
+  z-index : 1;
+  height  : 100%;
+  width   : 2em; /* 20px */
+  top     : 0;
+  right   : 0;
+
+  padding-top : .1em;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  text-align : center;
+
+  border-left  : .2em solid #000;
+  border-radius: 0 .1em .1em 0;
+
+  background-color : #000;
+  color : #FFF;
+}
+
+.select .optList {
+  z-index : 2;
+
+  list-style: none;
+  margin : 0;
+  padding: 0;
+
+  background: #f0f0f0;
+  border: .2em solid #000;
+  border-top-width : .1em;
+  border-radius: 0 0 .4em .4em;
+
+  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  min-width : 100%;
+  max-height: 10em; /* 100px */
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.select .option {
+  padding: .2em .3em;
+}
+
+.select .highlight {
+  background: #000;
+  color: #FFF;
+}
+ +

Result for open state

+ +
{{ EmbedLiveSample('Open_state', 120, 130) }}
diff --git a/files/ja/learn/forms/how_to_build_custom_form_controls/example_2/index.html b/files/ja/learn/forms/how_to_build_custom_form_controls/example_2/index.html new file mode 100644 index 0000000000..7a547909ce --- /dev/null +++ b/files/ja/learn/forms/how_to_build_custom_form_controls/example_2/index.html @@ -0,0 +1,212 @@ +--- +title: 例 2 +slug: Learn/Forms/How_to_build_custom_form_widgets/Example_2 +tags: + - Forms + - HTML +translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_2 +--- +

これは、カスタムフォームウィジェットの作成方法を説明する2番目の例です。

+ +

JS

+ +

HTML コンテンツ

+ +
<form class="no-widget">
+  <select name="myFruit">
+      <option>Cherry</option>
+      <option>Lemon</option>
+      <option>Banana</option>
+      <option>Strawberry</option>
+      <option>Apple</option>
+  </select>
+
+  <div class="select">
+    <span class="value">Cherry</span>
+    <ul class="optList hidden">
+      <li class="option">Cherry</li>
+      <li class="option">Lemon</li>
+      <li class="option">Banana</li>
+      <li class="option">Strawberry</li>
+      <li class="option">Apple</li>
+    </ul>
+  </div>
+<form>
+
+ +

CSS コンテンツ

+ +
.widget select,
+.no-widget .select {
+  position : absolute;
+  left     : -5000em;
+  height   : 0;
+  overflow : hidden;
+}
+
+/* --------------- */
+/* Required Styles */
+/* --------------- */
+
+.select {
+  position: relative;
+  display : inline-block;
+}
+
+.select.active,
+.select:focus {
+  box-shadow: 0 0 3px 1px #227755;
+  outline: none;
+}
+
+.select .optList {
+  position: absolute;
+  top     : 100%;
+  left    : 0;
+}
+
+.select .optList.hidden {
+  max-height: 0;
+  visibility: hidden;
+}
+
+/* ------------ */
+/* Fancy Styles */
+/* ------------ */
+
+.select {
+  font-size   : 0.625em; /* 10px */
+  font-family : Verdana, Arial, sans-serif;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
+  width   : 10em; /* 100px */
+
+  border        : 0.2em solid #000; /* 2px */
+  border-radius : 0.4em; /* 4px */
+
+  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
+
+  background : #F0F0F0;
+  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+}
+
+.select .value {
+  display  : inline-block;
+  width    : 100%;
+  overflow : hidden;
+
+  white-space   : nowrap;
+  text-overflow : ellipsis;
+  vertical-align: top;
+}
+
+.select:after {
+  content : "▼";
+  position: absolute;
+  z-index : 1;
+  height  : 100%;
+  width   : 2em; /* 20px */
+  top     : 0;
+  right   : 0;
+
+  padding-top : .1em;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  text-align : center;
+
+  border-left  : .2em solid #000;
+  border-radius: 0 .1em .1em 0;
+
+  background-color : #000;
+  color : #FFF;
+}
+
+.select .optList {
+  z-index : 2;
+
+  list-style: none;
+  margin : 0;
+  padding: 0;
+
+  background: #f0f0f0;
+  border: .2em solid #000;
+  border-top-width : .1em;
+  border-radius: 0 0 .4em .4em;
+
+  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  min-width : 100%;
+  max-height: 10em; /* 100px */
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.select .option {
+  padding: .2em .3em;
+}
+
+.select .highlight {
+  background: #000;
+  color: #FFFFFF;
+}
+ +

JavaScript コンテンツ

+ +
window.addEventListener("load", function () {
+  var form = document.querySelector('form');
+
+  form.classList.remove("no-widget");
+  form.classList.add("widget");
+});
+ +

JS の結果

+ +

{{ EmbedLiveSample('JS', 120, 130) }}

+ +

No JS

+ +

HTML コンテンツ

+ +
<form class="no-widget">
+  <select name="myFruit">
+      <option>Cherry</option>
+      <option>Lemon</option>
+      <option>Banana</option>
+      <option>Strawberry</option>
+      <option>Apple</option>
+  </select>
+
+  <div class="select">
+    <span class="value">Cherry</span>
+    <ul class="optList hidden">
+      <li class="option">Cherry</li>
+      <li class="option">Lemon</li>
+      <li class="option">Banana</li>
+      <li class="option">Strawberry</li>
+      <li class="option">Apple</li>
+    </ul>
+  </div>
+<form>
+ +

CSS コンテンツ

+ +
.widget select,
+.no-widget .select {
+  position : absolute;
+  left     : -5000em;
+  height   : 0;
+  overflow : hidden;
+}
+ +

No JS の結果

+ +

{{ EmbedLiveSample('No_JS', 120, 130) }}

diff --git a/files/ja/learn/forms/how_to_build_custom_form_controls/example_3/index.html b/files/ja/learn/forms/how_to_build_custom_form_controls/example_3/index.html new file mode 100644 index 0000000000..ac3763cb80 --- /dev/null +++ b/files/ja/learn/forms/how_to_build_custom_form_controls/example_3/index.html @@ -0,0 +1,246 @@ +--- +title: 例 3 +slug: Learn/Forms/How_to_build_custom_form_widgets/Example_3 +tags: + - Forms + - HTML +translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_3 +--- +

これは、カスタムフォームウィジェットの作成方法を説明する3番目の例です。

+ +

状態を変更する

+ +

HTML コンテンツ

+ +
<form class="no-widget">
+  <select name="myFruit" tabindex="-1">
+      <option>Cherry</option>
+      <option>Lemon</option>
+      <option>Banana</option>
+      <option>Strawberry</option>
+      <option>Apple</option>
+  </select>
+
+  <div class="select" tabindex="0">
+    <span class="value">Cherry</span>
+    <ul class="optList hidden">
+      <li class="option">Cherry</li>
+      <li class="option">Lemon</li>
+      <li class="option">Banana</li>
+      <li class="option">Strawberry</li>
+      <li class="option">Apple</li>
+    </ul>
+  </div>
+</form>
+ +

CSS コンテンツ

+ +
.widget select,
+.no-widget .select {
+  position : absolute;
+  left     : -5000em;
+  height   : 0;
+  overflow : hidden;
+}
+
+/* --------------- */
+/* Required Styles */
+/* --------------- */
+
+.select {
+  position: relative;
+  display : inline-block;
+}
+
+.select.active,
+.select:focus {
+  box-shadow: 0 0 3px 1px #227755;
+  outline: none;
+}
+
+.select .optList {
+  position: absolute;
+  top     : 100%;
+  left    : 0;
+}
+
+.select .optList.hidden {
+  max-height: 0;
+  visibility: hidden;
+}
+
+/* ------------ */
+/* Fancy Styles */
+/* ------------ */
+
+.select {
+  font-size   : 0.625em; /* 10px */
+  font-family : Verdana, Arial, sans-serif;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
+  width   : 10em; /* 100px */
+
+  border        : 0.2em solid #000; /* 2px */
+  border-radius : 0.4em; /* 4px */
+
+  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
+
+  background : #F0F0F0;
+  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+}
+
+.select .value {
+  display  : inline-block;
+  width    : 100%;
+  overflow : hidden;
+
+  white-space   : nowrap;
+  text-overflow : ellipsis;
+  vertical-align: top;
+}
+
+.select:after {
+  content : "▼";
+  position: absolute;
+  z-index : 1;
+  height  : 100%;
+  width   : 2em; /* 20px */
+  top     : 0;
+  right   : 0;
+
+  padding-top : .1em;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  text-align : center;
+
+  border-left  : .2em solid #000;
+  border-radius: 0 .1em .1em 0;
+
+  background-color : #000;
+  color : #FFF;
+}
+
+.select .optList {
+  z-index : 2;
+
+  list-style: none;
+  margin : 0;
+  padding: 0;
+
+  background: #f0f0f0;
+  border: .2em solid #000;
+  border-top-width : .1em;
+  border-radius: 0 0 .4em .4em;
+
+  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  min-width : 100%;
+  max-height: 10em; /* 100px */
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.select .option {
+  padding: .2em .3em;
+}
+
+.select .highlight {
+  background: #000;
+  color: #FFFFFF;
+}
+ +

JavaScript コンテンツ

+ +
// ------- //
+// HELPERS //
+// ------- //
+
+NodeList.prototype.forEach = function (callback) {
+  Array.prototype.forEach.call(this, callback);
+}
+
+// -------------------- //
+// Function definitions //
+// -------------------- //
+
+function deactivateSelect(select) {
+  if (!select.classList.contains('active')) return;
+
+  var optList = select.querySelector('.optList');
+
+  optList.classList.add('hidden');
+  select.classList.remove('active');
+}
+
+function activeSelect(select, selectList) {
+  if (select.classList.contains('active')) return;
+
+  selectList.forEach(deactivateSelect);
+  select.classList.add('active');
+};
+
+function toggleOptList(select, show) {
+  var optList = select.querySelector('.optList');
+
+  optList.classList.toggle('hidden');
+}
+
+function highlightOption(select, option) {
+  var optionList = select.querySelectorAll('.option');
+
+  optionList.forEach(function (other) {
+    other.classList.remove('highlight');
+  });
+
+  option.classList.add('highlight');
+};
+
+// ------------- //
+// Event binding //
+// ------------- //
+
+window.addEventListener("load", function () {
+  var form = document.querySelector('form');
+
+  form.classList.remove("no-widget");
+  form.classList.add("widget");
+});
+
+window.addEventListener('load', function () {
+  var selectList = document.querySelectorAll('.select');
+
+  selectList.forEach(function (select) {
+    var optionList = select.querySelectorAll('.option');
+
+    optionList.forEach(function (option) {
+      option.addEventListener('mouseover', function () {
+        highlightOption(select, option);
+      });
+    });
+
+    select.addEventListener('click', function (event) {
+      toggleOptList(select);
+    },  false);
+
+    select.addEventListener('focus', function (event) {
+      activeSelect(select, selectList);
+    });
+
+    select.addEventListener('blur', function (event) {
+      deactivateSelect(select);
+    });
+  });
+});
+ +

結果

+ +

{{ EmbedLiveSample('Change_states') }}

diff --git a/files/ja/learn/forms/how_to_build_custom_form_controls/example_4/index.html b/files/ja/learn/forms/how_to_build_custom_form_controls/example_4/index.html new file mode 100644 index 0000000000..51fa53c27a --- /dev/null +++ b/files/ja/learn/forms/how_to_build_custom_form_controls/example_4/index.html @@ -0,0 +1,296 @@ +--- +title: 例 4 +slug: Learn/Forms/How_to_build_custom_form_widgets/Example_4 +tags: + - Advanced + - Example + - Forms + - Guide + - HTML + - Web +translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_4 +--- +

これは、カスタムフォームウィジェットの作成方法を説明する4番目の例です。

+ +

状態を変更する

+ +

HTML コンテンツ

+ +
<form class="no-widget">
+  <select name="myFruit">
+    <option>Cherry</option>
+    <option>Lemon</option>
+    <option>Banana</option>
+    <option>Strawberry</option>
+    <option>Apple</option>
+  </select>
+
+  <div class="select">
+    <span class="value">Cherry</span>
+    <ul class="optList hidden">
+      <li class="option">Cherry</li>
+      <li class="option">Lemon</li>
+      <li class="option">Banana</li>
+      <li class="option">Strawberry</li>
+      <li class="option">Apple</li>
+    </ul>
+  </div>
+</form>
+ +

CSS コンテンツ

+ +
.widget select,
+.no-widget .select {
+  position : absolute;
+  left     : -5000em;
+  height   : 0;
+  overflow : hidden;
+}
+
+/* --------------- */
+/* Required Styles */
+/* --------------- */
+
+.select {
+  position: relative;
+  display : inline-block;
+}
+
+.select.active,
+.select:focus {
+  box-shadow: 0 0 3px 1px #227755;
+  outline: none;
+}
+
+.select .optList {
+  position: absolute;
+  top     : 100%;
+  left    : 0;
+}
+
+.select .optList.hidden {
+  max-height: 0;
+  visibility: hidden;
+}
+
+/* ------------ */
+/* Fancy Styles */
+/* ------------ */
+
+.select {
+  font-size   : 0.625em; /* 10px */
+  font-family : Verdana, Arial, sans-serif;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
+  width   : 10em; /* 100px */
+
+  border        : 0.2em solid #000; /* 2px */
+  border-radius : 0.4em; /* 4px */
+
+  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
+
+  background : #F0F0F0;
+  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+}
+
+.select .value {
+  display  : inline-block;
+  width    : 100%;
+  overflow : hidden;
+
+  white-space   : nowrap;
+  text-overflow : ellipsis;
+  vertical-align: top;
+}
+
+.select:after {
+  content : "▼";
+  position: absolute;
+  z-index : 1;
+  height  : 100%;
+  width   : 2em; /* 20px */
+  top     : 0;
+  right   : 0;
+
+  padding-top : .1em;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  text-align : center;
+
+  border-left  : .2em solid #000;
+  border-radius: 0 .1em .1em 0;
+
+  background-color : #000;
+  color : #FFF;
+}
+
+.select .optList {
+  z-index : 2;
+
+  list-style: none;
+  margin : 0;
+  padding: 0;
+
+  background: #f0f0f0;
+  border: .2em solid #000;
+  border-top-width : .1em;
+  border-radius: 0 0 .4em .4em;
+
+  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  min-width : 100%;
+  max-height: 10em; /* 100px */
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.select .option {
+  padding: .2em .3em;
+}
+
+.select .highlight {
+  background: #000;
+  color: #FFFFFF;
+}
+ +

JavaScript コンテンツ

+ +
// ------- //
+// HELPERS //
+// ------- //
+
+NodeList.prototype.forEach = function (callback) {
+  Array.prototype.forEach.call(this, callback);
+}
+
+// -------------------- //
+// Function definitions //
+// -------------------- //
+
+function deactivateSelect(select) {
+  if (!select.classList.contains('active')) return;
+
+  var optList = select.querySelector('.optList');
+
+  optList.classList.add('hidden');
+  select.classList.remove('active');
+}
+
+function activeSelect(select, selectList) {
+  if (select.classList.contains('active')) return;
+
+  selectList.forEach(deactivateSelect);
+  select.classList.add('active');
+};
+
+function toggleOptList(select, show) {
+  var optList = select.querySelector('.optList');
+
+  optList.classList.toggle('hidden');
+}
+
+function highlightOption(select, option) {
+  var optionList = select.querySelectorAll('.option');
+
+  optionList.forEach(function (other) {
+    other.classList.remove('highlight');
+  });
+
+  option.classList.add('highlight');
+};
+
+function updateValue(select, index) {
+  var nativeWidget = select.previousElementSibling;
+  var value = select.querySelector('.value');
+  var optionList = select.querySelectorAll('.option');
+
+  nativeWidget.selectedIndex = index;
+  value.innerHTML = optionList[index].innerHTML;
+  highlightOption(select, optionList[index]);
+};
+
+function getIndex(select) {
+  var nativeWidget = select.previousElementSibling;
+
+  return nativeWidget.selectedIndex;
+};
+
+// ------------- //
+// Event binding //
+// ------------- //
+
+window.addEventListener("load", function () {
+  var form = document.querySelector('form');
+
+  form.classList.remove("no-widget");
+  form.classList.add("widget");
+});
+
+window.addEventListener('load', function () {
+  var selectList = document.querySelectorAll('.select');
+
+  selectList.forEach(function (select) {
+    var optionList = select.querySelectorAll('.option');
+
+    optionList.forEach(function (option) {
+      option.addEventListener('mouseover', function () {
+        highlightOption(select, option);
+      });
+    });
+
+    select.addEventListener('click', function (event) {
+      toggleOptList(select);
+    });
+
+    select.addEventListener('focus', function (event) {
+      activeSelect(select, selectList);
+    });
+
+    select.addEventListener('blur', function (event) {
+      deactivateSelect(select);
+    });
+  });
+});
+
+window.addEventListener('load', function () {
+  var selectList = document.querySelectorAll('.select');
+
+  selectList.forEach(function (select) {
+    var optionList = select.querySelectorAll('.option'),
+        selectedIndex = getIndex(select);
+
+    select.tabIndex = 0;
+    select.previousElementSibling.tabIndex = -1;
+
+    updateValue(select, selectedIndex);
+
+    optionList.forEach(function (option, index) {
+      option.addEventListener('click', function (event) {
+        updateValue(select, index);
+      });
+    });
+
+    select.addEventListener('keyup', function (event) {
+      var length = optionList.length,
+          index  = getIndex(select);
+
+      if (event.keyCode === 40 && index < length - 1) { index++; }
+      if (event.keyCode === 38 && index > 0) { index--; }
+
+      updateValue(select, index);
+    });
+  });
+});
+ +

結果

+ +

{{ EmbedLiveSample('Change_states') }}

diff --git a/files/ja/learn/forms/how_to_build_custom_form_controls/example_5/index.html b/files/ja/learn/forms/how_to_build_custom_form_controls/example_5/index.html new file mode 100644 index 0000000000..4bad8016bb --- /dev/null +++ b/files/ja/learn/forms/how_to_build_custom_form_controls/example_5/index.html @@ -0,0 +1,289 @@ +--- +title: 例 5 +slug: Learn/Forms/How_to_build_custom_form_widgets/Example_5 +tags: + - Forms + - HTML +translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_5 +--- +

これが、カスタムフォームウィジェットの作成方法を説明する最後の例です。

+ +

状態を変更する

+ +

HTML コンテンツ

+ +
<form class="no-widget">
+  <select name="myFruit">
+    <option>Cherry</option>
+    <option>Lemon</option>
+    <option>Banana</option>
+    <option>Strawberry</option>
+    <option>Apple</option>
+  </select>
+
+  <div class="select" role="listbox">
+    <span class="value">Cherry</span>
+    <ul class="optList hidden" role="presentation">
+      <li class="option" role="option" aria-selected="true">Cherry</li>
+      <li class="option" role="option">Lemon</li>
+      <li class="option" role="option">Banana</li>
+      <li class="option" role="option">Strawberry</li>
+      <li class="option" role="option">Apple</li>
+    </ul>
+  </div>
+</form>
+ +

CSS コンテンツ

+ +
.widget select,
+.no-widget .select {
+  position : absolute;
+  left     : -5000em;
+  height   : 0;
+  overflow : hidden;
+}
+
+/* --------------- */
+/* Required Styles */
+/* --------------- */
+
+.select {
+  position: relative;
+  display : inline-block;
+}
+
+.select.active,
+.select:focus {
+  box-shadow: 0 0 3px 1px #227755;
+  outline: none;
+}
+
+.select .optList {
+  position: absolute;
+  top     : 100%;
+  left    : 0;
+}
+
+.select .optList.hidden {
+  max-height: 0;
+  visibility: hidden;
+}
+
+/* ------------ */
+/* Fancy Styles */
+/* ------------ */
+
+.select {
+  font-size   : 0.625em; /* 10px */
+  font-family : Verdana, Arial, sans-serif;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
+  width   : 10em; /* 100px */
+
+  border        : 0.2em solid #000; /* 2px */
+  border-radius : 0.4em; /* 4px */
+
+  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
+
+  background : #F0F0F0;
+  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+}
+
+.select .value {
+  display  : inline-block;
+  width    : 100%;
+  overflow : hidden;
+
+  white-space   : nowrap;
+  text-overflow : ellipsis;
+  vertical-align: top;
+}
+
+.select:after {
+  content : "▼";
+  position: absolute;
+  z-index : 1;
+  height  : 100%;
+  width   : 2em; /* 20px */
+  top     : 0;
+  right   : 0;
+
+  padding-top : .1em;
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  text-align : center;
+
+  border-left  : .2em solid #000;
+  border-radius: 0 .1em .1em 0;
+
+  background-color : #000;
+  color : #FFF;
+}
+
+.select .optList {
+  z-index : 2;
+
+  list-style: none;
+  margin : 0;
+  padding: 0;
+
+  background: #f0f0f0;
+  border: .2em solid #000;
+  border-top-width : .1em;
+  border-radius: 0 0 .4em .4em;
+
+  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
+
+  -moz-box-sizing : border-box;
+  box-sizing : border-box;
+
+  min-width : 100%;
+  max-height: 10em; /* 100px */
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.select .option {
+  padding: .2em .3em;
+}
+
+.select .highlight {
+  background: #000;
+  color: #FFFFFF;
+}
+ +

JavaScript コンテンツ

+ +
// ------- //
+// HELPERS //
+// ------- //
+
+NodeList.prototype.forEach = function (callback) {
+  Array.prototype.forEach.call(this, callback);
+}
+
+// -------------------- //
+// Function definitions //
+// -------------------- //
+
+function deactivateSelect(select) {
+  if (!select.classList.contains('active')) return;
+
+  var optList = select.querySelector('.optList');
+
+  optList.classList.add('hidden');
+  select.classList.remove('active');
+}
+
+function activeSelect(select, selectList) {
+  if (select.classList.contains('active')) return;
+
+  selectList.forEach(deactivateSelect);
+  select.classList.add('active');
+};
+
+function toggleOptList(select, show) {
+  var optList = select.querySelector('.optList');
+
+  optList.classList.toggle('hidden');
+}
+
+function highlightOption(select, option) {
+  var optionList = select.querySelectorAll('.option');
+
+  optionList.forEach(function (other) {
+    other.classList.remove('highlight');
+  });
+
+  option.classList.add('highlight');
+};
+
+function updateValue(select, index) {
+  var nativeWidget = select.previousElementSibling;
+  var value = select.querySelector('.value');
+  var optionList = select.querySelectorAll('.option');
+
+  optionList.forEach(function (other) {
+    other.setAttribute('aria-selected', 'false');
+  });
+
+  optionList[index].setAttribute('aria-selected', 'true');
+
+  nativeWidget.selectedIndex = index;
+  value.innerHTML = optionList[index].innerHTML;
+  highlightOption(select, optionList[index]);
+};
+
+function getIndex(select) {
+  var nativeWidget = select.previousElementSibling;
+
+  return nativeWidget.selectedIndex;
+};
+
+// ------------- //
+// Event binding //
+// ------------- //
+
+window.addEventListener("load", function () {
+  var form = document.querySelector('form');
+
+  form.classList.remove("no-widget");
+  form.classList.add("widget");
+});
+
+window.addEventListener('load', function () {
+  var selectList = document.querySelectorAll('.select');
+
+  selectList.forEach(function (select) {
+    var optionList = select.querySelectorAll('.option'),
+        selectedIndex = getIndex(select);
+
+    select.tabIndex = 0;
+    select.previousElementSibling.tabIndex = -1;
+
+    updateValue(select, selectedIndex);
+
+    optionList.forEach(function (option, index) {
+      option.addEventListener('mouseover', function () {
+        highlightOption(select, option);
+      });
+
+      option.addEventListener('click', function (event) {
+        updateValue(select, index);
+      });
+    });
+
+    select.addEventListener('click', function (event) {
+      toggleOptList(select);
+    });
+
+    select.addEventListener('focus', function (event) {
+      activeSelect(select, selectList);
+    });
+
+    select.addEventListener('blur', function (event) {
+      deactivateSelect(select);
+    });
+
+    select.addEventListener('keyup', function (event) {
+      var length = optionList.length,
+          index  = getIndex(select);
+
+      if (event.keyCode === 40 && index < length - 1) { index++; }
+      if (event.keyCode === 38 && index > 0) { index--; }
+
+      updateValue(select, index);
+    });
+  });
+});
+
+ +

結果

+ +

{{ EmbedLiveSample('Change_states') }}

diff --git a/files/ja/learn/forms/how_to_build_custom_form_controls/index.html b/files/ja/learn/forms/how_to_build_custom_form_controls/index.html new file mode 100644 index 0000000000..7bbd20b511 --- /dev/null +++ b/files/ja/learn/forms/how_to_build_custom_form_controls/index.html @@ -0,0 +1,901 @@ +--- +title: カスタムフォームコントロールの作成方法 +slug: Learn/Forms/How_to_build_custom_form_widgets +tags: + - Advanced + - Example + - Forms + - Guide + - HTML + - Web +translation_of: Learn/Forms/How_to_build_custom_form_controls +--- +
{{LearnSidebar}}
+ +

HTML フォームで使用可能なコントロールだけでは十分でない場合が多くあります。例えば、{{HTMLElement("select")}} 要素のようなコントロールに高度なスタイル設定を行いたい場合や、独自の動作を提供したい場合は、あなた独自のコントロールを作成するしかありません。

+ +

本記事では、そのようなコントロールの作り方を見ていきます。その目的のため、次の例に取り組みます: {{HTMLElement("select")}} 要素の再構築です。このほかに独自コントロール作成をどういう方法でするか、いつするか、またそれが意味をなすのかや、コントロール作成が必須なときに何に気をつけるべきかを扱います。

+ +
+

注記: ここではコントロールの構築に注目しており、汎用かつ再利用可能なコードの作成法は見ていきません。それには JavaScript の重要なコードや未知のコンテキストでの DOM 操作が組み合わされており、本記事の対象から外れます。

+
+ +

デザイン、構造、セマンティクス

+ +

カスタムコントロールを作成する前に、何をしたいかをはっきりと理解することから始めるべきです。これはあなたの貴重な時間を節約するでしょう。特に、コントロールの全状態を明確に定義することが重要です。これを行うには、状態や動作がよく知られている既存のコントロールからスタートするとよいでしょう。この結果、簡単に可能な限りの模倣を行えます。

+ +

本記事の例では、{{HTMLElement("select")}} 要素を再構築します。以下が、私たちが実現したい成果です:

+ +

The three states of a select box

+ +

このスクリーンショットでは、コントロールの主要な状態 3 つを示しています: 通常状態 (左)、アクティブ状態 (中央)、そして開いた状態 (右) です。

+ +

動作の点では、ネイティブな HTML用要素を再作成します。このため、ネイティブ HTML要素と同様な動作や意味を持たせるべきです。独自のコントロールもネイティブコントロールと同様に、キーボードだけでなくマウスでも使用できるように、またスクリーンリーダーに理解できるようにしたいと考えます。コントロールがどのように各状態に達するかを定義することから始めましょう:

+ +
+
コントロールは以下のときに通常状態になります:
+
+
    +
  • ページを読み込む
  • +
  • コントロールはアクティブであったが、ユーザーがコントロール以外のどこかをクリックした
  • +
  • コントロールはアクティブであったが、キーボードを使用して別のコントロールにフォーカスを移した (例 Tab キー)
  • +
+
+
コントロールは以下のときにアクティブ状態になります:
+
+
    +
  • ユーザーがコントロール上でクリックする
  • +
  • ユーザーが Tab キーを押下して、コントロールがフォーカスを得る
  • +
  • コントロールが開いた状態で、ユーザーがコントロールをクリックする
  • +
+
+
コントロールは以下のときに開いた状態になります:
+
+
    +
  • コントロールが開いた状態ではないときに、ユーザーがコントロールをクリックした
  • +
+
+
+ +

状態をどのように変えるかを理解したら、コントロールの値をどのように変えるかの定義が重要になります:

+ +
+
以下のときに値が変わります:
+
+
    +
  • コントロールが開いた状態であるときに、ユーザーが選択肢をクリックする
  • +
  • コントロールがアクティブ状態であるときに、ユーザーが上下矢印キーを押下する
  • +
+
+
+ +
+
以下のときには値は変わりません:
+
+
    +
  • 最初のオプションが選択済みのときに、ユーザーが上矢印をクリックする
  • +
  • 最後のオプションが選択済みのときに、ユーザーが下矢印をクリックする
  • +
+
+
+ +

最後に、コントロールの選択肢がどのように動作するかを定義しましょう:

+ + + +

この例の用途としては、ここまでです。しかし注意深い読者の方は、いくつかの動作が欠けていることに気づくでしょう。例えば、コントロールが開いた状態であるときにユーザーが Tab キーを押すと何が起きると考えますか? その答えは... 何も起きません。正しい動作は明らかでしょうが、実際は私たちの仕様で定義されていないため、とても見逃されやすいのです。これは、コントロールの動作を設計する人と実装する人が異なるチーム環境で特に当てはまります。

+ +

別のおもしろい例です: コントロールが開いた状態であるときに上下矢印キーを押すと何が起きるのでしょうか? こちらはやや難しくなります。アクティブ状態と開いた状態をまったく別のものと考えるなら、その答えはやはり "何も起きません" です。これは、開いた状態でのキーボードの作用を定義していないためです。一方、アクティブ状態と開いた状態が少し重なると考えるなら、値は替わるかもしれませんがそれに対応して選択肢が強調されることはないでしょう。繰り返しになりますが、これはコントロールが開いた状態の選択肢に対するキーボードの作用を定義していないためです (コントロールが開いた状態で何が起きるかだけを定義しており、その後がないためです)。

+ +

もう少し突っ込んで考えてみます: エスケープキーはどうでしょう? Esc キーを押すと開いた select が閉じます。ネイティブの{{htmlelement('select')}}と同じ機能を提供する場合、キーボードやマウスやスクリーンリーダーへのタッチ、その他あらゆる入力デバイスまで、全てのユーザーにとっての select の動作と全く同じようにふるまうべきです。

+ +

この例では欠けている仕様が明らかですので対処するでしょうが、めずらしい新たなコントロールでは真の問題になり得ます。標準要素では、{{htmlelement('select')}} もその 1 つですが、仕様の作成者は膨大な時間をかけて全てのユースケースの全ての入力デバイスの全ての操作を指定します。新コントロールの作成は簡単ではなく、特にそれが作成されたことのないものの場合は、どのような動作が正しいかについて、わずかなアイデアですら誰も持っていないため簡単ではないです。少なくとも select はこれまでやってきたため、どうふるまうかはわかっています!

+ +

一般的に、新しい操作を設計するのは、標準を作成するに十分なリーチを持った、とても大きな産業プレイヤーだけの選択肢です。例えば、Apple は 2001年に iPod にスクロールホイールを導入しました。完全に新しい操作方法のデバイスを導入するのに成功するマーケットシェアがありましたが、たいていのデバイス会社はそうはいきません。

+ +

新しいユーザーインタラクションを発明しないのがベストです。インタラクションを追加する場合、設計段階で時間を使うのが重要です。動作の定義が貧弱であったり定義もれがあったりした場合、いったんユーザーが使い始めると動作を再定義するのが非常に困難になると思われますので、設計段階に時間をかけることは賢明です。もし疑っているのでしたら、他の人に意見を聞きましょう。また予算を持っているのでしたら、ユーザーテストの実施をためらってはいけません。このプロセスは、UX デザインと呼ばれます。この点について詳しく学びたいのでしたら、以下の役に立つリソースをご覧になるとよいでしょう:

+ + + +
+

注記: さらにほとんどのシステムでは、使用できる選択肢すべてを見るために {{HTMLElement("select")}} 要素を開く手段があります (これは {{HTMLElement("select")}} 要素をマウスでクリックするのと同じです)。これは Windows では Alt + Down  キー で実現できますが、この例では実装しません。しかし、仕組みはすでに click イベント向けに実装されていますので、行うのは簡単です。

+
+ +

HTML の構造とセマンティクスの定義

+ +

コントロールの基本的な機能が決まりましたので、構築を始めるときが来ました。最初のステップはコントロールの HTML 構造の定義と、基本的なセマンティクスの付与です。こちらが、{{HTMLElement("select")}} 要素の再構築に必要な HTML です:

+ +
<!-- これはコントロールの中心的なコンテナです。
+     tabindex 属性は、ユーザーがコントロールにフォーカスを当てられるようにするものです。
+     これを JavaScript で設定する方がよいことは、後で見ていきます。-->
+<div class="select" tabindex="0">
+
+  <!-- このコンテナは、コントロールの現在の値を表示するために使用します。-->
+  <span class="value">Cherry</span>
+
+  <!-- このコンテナは、コントロールで使用できるすべての選択肢を包含します。
+       これはリストですから、ul 要素を使用するとよいでしょう。-->
+  <ul class="optList">
+    <!-- 各々の選択肢は表示される値だけを包含しており、フォームのデータで送信される
+         実際の値を処理する方法は後で見ていきます。-->
+    <li class="option">Cherry</li>
+    <li class="option">Lemon</li>
+    <li class="option">Banana</li>
+    <li class="option">Strawberry</li>
+    <li class="option">Apple</li>
+  </ul>
+
+</div>
+ +

クラス名の使い方に注目してください。これらは基盤となる実際の HTML とは関係なく、フォームに関するそれぞれの部分を示します。これは CSS や JavaScript を強固な HTML の構造と結びつけないようにするために重要であり、そのためにコントロールを扱うコードを壊すことなく、後から実装を変更することができます。例えば {{HTMLElement("optgroup")}} 要素と同等の機能を実装したい場合などです。

+ +

クラス名は、しかしながら、意味のある値ではありません。現在の状態では、スクリーンリーダーのユーザーのみがリストを"見る"ことができます。ARIA セマンティクスを少し追加します。

+ +

CSS でルックアンドフィールを作成する

+ +

構造ができましたので、コントロールのデザインを始められます。カスタムコントロールを作成する上でのポイントは、望むとおりにコントロールへスタイルを設定できることです。そのために、CSS を 2 つの部分に分けます: ひとつはコントロールが {{HTMLElement("select")}} 要素のように動作するために欠かせない CSS ルールであり、もうひとつは希望する見た目にするための好みのスタイルで構成されます。

+ +

必須のスタイル

+ +

必須のスタイルは、コントロールの 3 つの状態を扱うために欠かせないものです。

+ +
.select {
+  /* 選択肢のリスト向けのポジショニングコンテキストを作成します;
+     adding this to .select{{cssxref(':focus-within')}} will be a better option when fully supported
+  */
+  position: relative;
+
+  /* コントロールをテキストフローの一部かつまとまった大きさにします */
+  display : inline-block;
+}
+ +

アクティブ状態であるコントロールのルックアンドフィールを定義するため、追加で active クラスが必要です。このコントロールはフォーカスを得ることができますので、同様に動作させるためにカスタムスタイルを {{cssxref(":focus")}} 疑似クラスにも適用します。

+ +
.select.active,
+.select:focus {
+  outline: none;
+
+  /* box-shadow プロパティは必須ではありませんが、これをデフォルト値として使用するのは
+     アクティブ状態を見えるようにするために重要です。自由に書き換えてください。*/
+  box-shadow: 0 0 3px 1px #227755;
+}
+ +

次に、選択肢のリストを扱いましょう:

+ +
/* .select セレクタは、私たちが定義するクラスがコントロールの内部にあることを
+   確実にするためのシンタックスシュガーです。*/
+.select .optList {
+  /* 選択肢のリストが値の下部かつ HTML フローの外側に表示される
+     ようにします。*/
+  position : absolute;
+  top      : 100%;
+  left     : 0;
+}
+ +

選択肢のリストが隠れている状態を扱うための追加クラスも必要です。これはアクティブ状態と開いた状態で完全には一致しない相違点を扱うために必要です。

+ +
.select .optList.hidden {
+  /* これはアクセシブルな方法でリストを隠すための簡単な方法です。
+     アクセシビリティについては最後に説明します。*/
+  max-height: 0;
+  visibility: hidden;
+}
+ +
+

: 選択肢のリストに高さと幅を与えないように transform: scale(1, 0) も使えます。

+
+ +

美化

+ +

基本的な機能性を適切に置きましたので、戯れを始められます。以下は何ができるかの例であり、本記事の冒頭で示したスクリーンショットに一致するものです。とはいえ、自由に実験して何ができるかを見てみるとよいでしょう。

+ +
.select {
+  /* アクセシビリティのため、すべてのサイズは em 単位の値で表します
+     (ユーザーがテキストのみのモードでブラウザーのズーム機能を使用したときに、
+     コントロールをリサイズ可能にします)。算出結果は、ほとんどのブラウザーで
+     デフォルト値である 1em == 16px を想定します。
+     px から em への変換がわからない場合は http://riddle.pl/emcalc/ を試してください */
+  font-size   : 0.625em; /* この値 (10px) は、本コンテキストにおける新たなフォントサイズの em 単位値です。*/
+  font-family : Verdana, Arial, sans-serif;
+
+  box-sizing : border-box;
+
+  /* 後で追加する下向き矢印のためのスペースが必要です */
+  padding : .1em 2.5em .2em .5em; /* 1px 25px 2px 5px */
+  width   : 10em; /* 100px */
+
+  border        : .2em solid #000; /* 2px */
+  border-radius : .4em; /* 4px */
+  box-shadow    : 0 .1em .2em rgba(0,0,0,.45); /* 0 1px 2px */
+
+  /* 最初の宣言は、線形グラデーションをサポートしないブラウザー向けのものです。*/
+  background : #F0F0F0;
+  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
+}
+
+.select .value {
+  /* 値がコントロールの幅より大きくなる可能性があるため、コントロールの幅を
+     変更しないようにすることが必要です。内容物がオーバーフローした場合は、
+     省略記号をつけるとよいでしょう。*/
+  display  : inline-block;
+  width    : 100%;
+  overflow : hidden;
+  white-space : nowrap;
+  text-overflow: ellipsis;
+  vertical-align: top;
+}
+ +

下向き矢印をデザインするための追加要素は不要です。代わりに {{cssxref(":after")}} 疑似要素を使用します。ただし、select クラスでシンプルな背景画像を使用することによる実装も可能です。

+ +
.select:after {
+  content : "▼"; /* Unicode 文字 U+25BC を使用します。http://www.utf8-chartable.de をご覧ください */
+  position: absolute;
+  z-index : 1; /* これは、矢印が選択肢のリストに重ならないようにするために重要です */
+  top     : 0;
+  right   : 0;
+
+  box-sizing : border-box;
+
+  height  : 100%;
+  width   : 2em;
+  padding-top : .1em;
+
+  border-left  : .2em solid #000;
+  border-radius: 0 .1em .1em 0;
+
+  background-color : #000;
+  color : #FFF;
+  text-align : center;
+}
+ +

次に、選択肢のリストにスタイルを設定しましょう:

+ +
.select .optList {
+  z-index : 2; /* 選択肢のリストが下向き矢印より上になるよう、明示的に示します。*/
+
+  /* ul 要素のデフォルトスタイルを初期化します。*/
+  list-style: none;
+  margin : 0;
+  padding: 0;
+
+  box-sizing : border-box;
+
+  /* 値の幅がコントロールの幅より小さい場合でも、選択肢のリストの幅が
+     コントロール自体と同じになるようにします。*/
+  min-width : 100%;
+
+  /* リストが長すぎる場合に、内容物が垂直方向にはみ出します (自動的に
+     垂直スクロールバーを表示します) が、水平方向にはみ出しません
+     (幅を指定しないため、リストは自身の幅へ自動的に調整されます。
+     それができない場合は、内容物が切り詰められます) 。*/
+  max-height: 10em; /* 100px */
+  overflow-y: auto;
+  overflow-x: hidden;
+
+  border: .2em solid #000; /* 2px */
+  border-top-width : .1em; /* 1px */
+  border-radius: 0 0 .4em .4em; /* 0 0 4px 4px */
+
+  box-shadow: 0 .2em .4em rgba(0,0,0,.4); /* 0 2px 4px */
+  background: #f0f0f0;
+}
+ +

選択肢向けに、ユーザーが選択しようとしている (あるいは選択した) 値を示せるようにするための highlight クラスを追加しなければなりません。

+ +
.select .option {
+  padding: .2em .3em; /* 2px 3px */
+}
+
+.select .highlight {
+  background: #000;
+  color: #FFFFFF;
+}
+ +

これで、3 つの状態の結果は以下のようになります:

+ + + + + + + + + + + + + + + + + + + +
通常状態アクティブ状態開いた状態
{{EmbedLiveSample("Basic_state",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_1")}}{{EmbedLiveSample("Active_state",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_1")}}{{EmbedLiveSample("Open_state",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_1")}}
ソースコードを確認する
+ +

JavaScript でコントロールに命を吹き込む

+ +

デザインや構造の準備ができましたので、コントロールが実際に動作するようにするための JavaScript コードを記述できます。

+ +
+

警告: 以下は教育目的のコードであり、そのままで使用するべきではありません。ご覧のとおり、さまざまな箇所に将来性のないものや古いブラウザーで動作しないものがあります。また、本番のコードでは最適化すべき冗長な箇所もあります。

+
+ +

なぜ動作しないのか?

+ +

始める前に、重要事項を覚えておくことが大切です: ブラウザー内の JavaScript は信頼できる技術ではありません。カスタムコントロールを作成するとき、すべてをつなぎ合わせるために必要であることから JavaScript に頼らなければならないでしょう。ところが、JavaScript をブラウザーで実行できない場合がいくつもあります:

+ + + +

このようなリスクがあるため、JavaScript が動作しない場合に何が起きるかを注意深く考えることが重要です。この問題について詳しく扱うのは、スクリプトをどれだけ汎用および再利用可能にしたいかと密接に関わりますので本記事の範囲を外れますが、本サンプルでは基本的な部分について考えていきます。

+ +

本記事の例では、JavaScript コードが実行されない場合に標準の {{HTMLElement("select")}} 要素にフォールバックします。われわれのコントロールと {{HTMLElement("select")}}を導入し、いずれが表示されるかは body 要素のり class に依存します。読み込みに成功したとき、body 要素の class がコントロールの関数をなす script により更新されます。

+ +

これを実現するには、2 つのことが必要です。

+ +

第一に、カスタムコントロールを使用する前に通常の {{HTMLElement("select")}} 要素を追加することが必要です。実際は、これは残りのフォームデータと共にカスタムコントロールのデータを送信できるようにするために必要です。詳しくは後述します。

+ +
<body class="no-widget">
+  <form>
+    <select name="myFruit">
+      <option>Cherry</option>
+      <option>Lemon</option>
+      <option>Banana</option>
+      <option>Strawberry</option>
+      <option>Apple</option>
+    </select>
+
+    <div class="select">
+      <span class="value">Cherry</span>
+      <ul class="optList hidden">
+        <li class="option">Cherry</li>
+        <li class="option">Lemon</li>
+        <li class="option">Banana</li>
+        <li class="option">Strawberry</li>
+        <li class="option">Apple</li>
+      </ul>
+    </div>
+  </form>
+
+</body>
+ +

第二に、不要な要素 (すなわち、スクリプトを実行する場合における "本物の" {{HTMLElement("select")}} 要素や、実行しない場合におけるカスタムコントロール) を隠せるようにするための新たなクラスが 2 つ必要です。デフォルトでは、HTML コードでカスタムコントロールを隠すことに注意してください。

+ +
.widget select,
+.no-widget .select {
+  /* この CSS セレクタの基本的な意味は:
+     - body のクラスを "widget" に設定して、本物の {{HTMLElement("select")}} 要素を隠す
+     - または body のクラスを変更せずに "no-widget" のままにしておくことで、
+       クラスが "select" である要素が隠される */
+  position : absolute;
+  left     : -5000em;
+  height   : 0;
+  overflow : hidden;
+}
+ +

この CSS は要素の 1 つを見えなくしますが、スクリーンリーダーからは利用できます。

+ +

ここで、スクリプトを実行するか否かを判断するための JavaScript スイッチが必要になります。このスイッチはとても簡単です: ページを読み込むときにスクリプトを実行したら、no-widget クラスを削除して widget クラスを追加します。これにより {{HTMLElement("select")}} 要素やカスタムコントロールの可視性を切り替えます。

+ +
window.addEventListener("load", function () {
+  document.body.classList.remove("no-widget");
+  document.body.classList.add("widget");
+});
+ + + + + + + + + + + + + + + + + +
JS なしJS あり
{{EmbedLiveSample("No_JS",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_2")}}{{EmbedLiveSample("JS",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_2")}}
ソースコードを確認する
+ +
+

注記: コードを本当に汎用かつ再利用可能にしたい場合はクラスを切り替えるのではなく、単に {{HTMLElement("select")}} 要素を隠すためのコントロールのクラスを追加して、ページ内にあるすべての {{HTMLElement("select")}} 要素の後ろにカスタムコントロールを表す DOM ツリーを動的に追加する方がはるかによいでしょう。

+
+ +

作業をより簡単に

+ +

作成しようとしているコードでは、必要な作業すべてのために標準の JavaScript と DOM API を使用するでしょう。ここで使用するつもりである機能は以下のとおりです:

+ +
    +
  1. {{domxref("element.classList","classList")}}
  2. +
  3. {{domxref("EventTarget.addEventListener","addEventListener")}}
  4. +
  5. forEach
  6. +
  7. {{domxref("element.querySelector","querySelector")}} および {{domxref("element.querySelectorAll","querySelectorAll")}}
  8. +
+ +

これら特定機能を利用できるかに加えて、作業を始める前に残されている問題があります。{{domxref("element.querySelectorAll","querySelectorAll()")}} 関数が返すオブジェクトは Array ではなく {{domxref("NodeList")}} です。これは、Array オブジェクトは forEach 関数をサポートしているが {{domxref("NodeList")}} はサポートしていないために重要な問題です。{{domxref("NodeList")}} は Array ととても似ており、また forEach はとても便利であることから、作業を楽にするため以下のように {{domxref("NodeList")}} で forEach をサポートさせることができます:

+ +
NodeList.prototype.forEach = function (callback) {
+  Array.prototype.forEach.call(this, callback);
+}
+ +

古いブラウザーをサポートする必要がある場合、そのブラウザーがこうした機能をサポートしているか確かめてください。そうでない場合、リスト内を繰り返すか、ライブラリーや pollyfill を使う必要があります。

+ +

イベントコールバックを作成する

+ +

基盤が整いましたので、ユーザーがコントロールと対話するたびに使用されるすべての関数を定義し始めることができます。

+ +
// この関数は、カスタムコントロールを非アクティブにしたいときに使用します。
+// 引数は 1 つあります。
+// select : 非アクティブにする `select` クラスの DOM ノード
+function deactivateSelect(select) {
+
+  // コントロールがアクティブではないときは何もしません。
+  if (!select.classList.contains('active')) return;
+
+  // カスタムコントロールの選択肢のリストを取得することが必要です。
+  var optList = select.querySelector('.optList');
+
+  // 選択肢のリストを閉じます。
+  optList.classList.add('hidden');
+
+  // そして、カスタムコントロール自身を非アクティブにします。
+  select.classList.remove('active');
+}
+
+// この関数は、ユーザーがコントロールをアクティブ/非アクティブにしたがっているときに使用します。
+// 引数は 2 つあります:
+// select : アクティブにする `select` クラスの DOM ノード
+// selectList : `select` クラスであるすべての DOM ノードのリスト
+function activeSelect(select, selectList) {
+
+  // コントロールがすでにアクティブであるときは何もしません。
+  if (select.classList.contains('active')) return;
+
+  // すべてのカスタムコントロールを非アクティブにすることが必要です。
+  // deactivateSelect 関数は forEach コールバック関数の要件を
+  // すべて満たしていますので、仲介する無名関数を使用せずに
+  // 直接使用しています。
+  selectList.forEach(deactivateSelect);
+
+  // そして、指定されたコントロールをアクティブ状態にします。
+  select.classList.add('active');
+}
+
+// この関数は、ユーザーが選択肢のリストを開く/閉じることを求めたときに使用します。
+// 引数は 1 つあります:
+// select : 表示を切り替えるリストの DOM ノード
+function toggleOptList(select) {
+
+  // リストはコントロールから確保します。
+  var optList = select.querySelector('.optList');
+
+  // リストのクラスを表示/非表示に切り替えます。
+  optList.classList.toggle('hidden');
+}
+
+// この関数は、選択肢を強調したいときに使用します。
+// 引数は 2 つあります:
+// select : 強調する選択肢を包含する `select` クラスの DOM ノード
+// option : 強調する `option` クラスの DOM ノード
+function highlightOption(select, option) {
+
+  // カスタムコントロールで使用可能なすべての選択肢のリストを取得します。
+  var optionList = select.querySelectorAll('.option');
+
+  // すべての選択肢から強調効果を取り除きます。
+  optionList.forEach(function (other) {
+    other.classList.remove('highlight');
+  });
+
+  // 適切な選択肢を強調します。
+  option.classList.add('highlight');
+};
+ +

以上が、カスタムコントロールのさまざまな状態を制御するために必要なもののすべてです。

+ +

次に、これらの関数と適切なイベントを関連づけます:

+ +
// ドキュメントが読み込まれたときのイベントの関連づけを制御します。
+window.addEventListener('load', function () {
+  var selectList = document.querySelectorAll('.select');
+
+  // 各々のコントロールは初期化が必要です。
+  selectList.forEach(function (select) {
+
+    // すべての `option` も同様です。
+    var optionList = select.querySelectorAll('.option');
+
+    // ユーザーが選択肢にマウスポインタを乗せるたびに、その選択肢を強調します。
+    optionList.forEach(function (option) {
+      option.addEventListener('mouseover', function () {
+        // 注記: 変数 `select` および `option` は、関数呼び出しのスコープ内でのみ
+        // 使用可能なクロージャです。
+        highlightOption(select, option);
+      });
+    });
+
+    // ユーザーが独自の select 要素でクリックするたびに
+    select.addEventListener('click', function (event) {
+      // 注記: 変数 `select` は、関数呼び出しのスコープ内でのみ
+      // 使用可能なクロージャです。
+
+      // 選択肢のリストの可視性を切り替えます。
+      toggleOptList(select);
+    });
+
+    // コントロールが再びフォーカスを得た場合
+    // ユーザーがコントロールをクリックしたり、コントロールへアクセスするために
+    // Tab キーを使用するたびに、コントロールはフォーカスを得ます。
+    select.addEventListener('focus', function (event) {
+      // 注記: 変数 `select` および `selectList` は、関数呼び出しのスコープ内でのみ
+      // 使用可能なクロージャです。
+
+      // コントロールをアクティブにします。
+      activeSelect(select, selectList);
+    });
+
+    // コントロールがフォーカスを失った場合
+    select.addEventListener('blur', function (event) {
+      // 注記: 変数 `select` は、関数呼び出しのスコープ内でのみ
+      // 使用可能なクロージャです。
+
+      // コントロールを非アクティブにします。
+      deactivateSelect(select);
+    });
+
+    // ユーザーが`esc`を押した場合にフォーカスを外す
+    select.addEventListener('keyup', function (event) {
+
+      // deactive on keyup of `esc`
+      if (event.keyCode === 27) {
+         deactivateSelect(select);
+      }
+    });
+});
+ +

この時点でコントロールは設計どおりに状態が変わりますが、コントロール値はまだ更新されません。次の章でこれを扱います。

+ + + + + + + + + + + + + + + +
Live example
{{EmbedLiveSample("Change_states",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_3")}}
ソースコードを確認する
+ +

コントロールの値を制御する

+ +

コントロールが動作するようになりましたので、ユーザーの入力に従って値を更新して、フォームデータと共にその値を送信できるようにするコードを追加しなければなりません。

+ +

これを行うもっとも簡単な方法は、覆い隠したネイティブコントロールを使用することです。そのようなコントロールはブラウザーが提供するすべての組み込みのコントロールと共に値の経過を保持しており、フォームを送信するときは通常どおりに値を送信します。これらすべてを行えるようにするために、車輪の再発明を行うのは無駄です。

+ +

先ほど見たように、アクセシビリティの理由からフォールバック手段としてすでにネイティブの select コントロールを使用しています。単純に、その値をカスタムコントロールの値と同期することができます:

+ +
// この関数は、表示される値を更新してネイティブコントロールの値と同期します。
+// 引数は 2 つあります:
+// select : 更新する値を持つ `select` クラスの DOM ノード
+// index  : 選択される値のインデックス
+function updateValue(select, index) {
+  // 指定されたカスタムコントロール向けのネイティブコントロールを取得することが必要です。
+  // この例では、ネイティブコントロールはカスタムコントロールの兄弟です。
+  var nativeWidget = select.previousElementSibling;
+
+  // カスタムコントロールの値のプレースホルダーの取得も必要です。
+  var value = select.querySelector('.value');
+
+  // そして、選択肢の全リストが必要です。
+  var optionList = select.querySelectorAll('.option');
+
+  // 選択した値のインデックスを、selectedIndex に設定します。
+  nativeWidget.selectedIndex = index;
+
+  // 上記に応じて、値のプレースホルダーも更新します。
+  value.innerHTML = optionList[index].innerHTML;
+
+  // そして、カスタムコントロールで対応する選択肢を強調します。
+  highlightOption(select, optionList[index]);
+};
+
+// この関数は、ネイティブコントロールで現在選択されているインデックスを返します。
+// 引数は 1 つあります:
+// select : ネイティブコントロールに関係する `select` クラスの DOM ノード
+function getIndex(select) {
+  // 指定されたカスタムコントロール向けのネイティブコントロールにアクセスすることが必要です。
+  // この例では、ネイティブコントロールはカスタムコントロールの兄弟です。
+  var nativeWidget = select.previousElementSibling;
+
+  return nativeWidget.selectedIndex;
+};
+ +

これら 2 つの関数で、ネイティブコントロールとカスタムコントロールを関連づけることができます:

+ +
// ドキュメントが読み込まれたときのイベントの関連づけを制御します。
+window.addEventListener('load', function () {
+  var selectList = document.querySelectorAll('.select');
+
+  // 各々のコントロールは初期化が必要です。
+  selectList.forEach(function (select) {
+    var optionList = select.querySelectorAll('.option'),
+        selectedIndex = getIndex(select);
+
+    // カスタムコントロールがフォーカスを得られるようにします。
+    select.tabIndex = 0;
+
+    // ネイティブコントロールがフォーカスを得ないようにします。
+    select.previousElementSibling.tabIndex = -1;
+
+    // デフォルトで選択されている値が正しく表示されるようにします。
+    updateValue(select, selectedIndex);
+
+    // ユーザーが選択肢をクリックするのに応じて値を更新します。
+    optionList.forEach(function (option, index) {
+      option.addEventListener('click', function (event) {
+        updateValue(select, index);
+      });
+    });
+
+    // フォーカスがあるコントロールでユーザーがキーボードを使用するのに応じて、値を更新します。
+    select.addEventListener('keyup', function (event) {
+      var length = optionList.length,
+          index  = getIndex(select);
+
+      // ユーザーが下矢印キーを押すと、次の選択肢にジャンプします。
+      if (event.keyCode === 40 && index < length - 1) { index++; }
+
+      // ユーザーが上矢印キーを押すと、前の選択肢にジャンプします。
+      if (event.keyCode === 38 && index > 0) { index--; }
+
+      updateValue(select, index);
+    });
+  });
+});
+ +

上記のコードで、tabIndex プロパティを使用していることは注目に値します。このプロパティは、ネイティブコントロールにフォーカスが当たらないようにすることと、ユーザーがキーボードやマウスを使用するとカスタムコントロールがフォーカスを得るようにするために必要です。

+ +

これで完了です! 結果は以下のとおりです:

+ + + + + + + + + + + + + + + +
Live example
{{EmbedLiveSample("Change_states",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_4")}}
ソースコードを確認する
+ +

ちょっと待ってください、本当に終わったのでしょうか?

+ +

アクセシブルにする

+ +

フル機能のセレクトボックスとはかけ離れていますが動作するものはできましたし、よく動作しています。しかし、私たちが行ってきたことは DOM の操作にすぎません。これには実際のセマンティクスがなく、またセレクトボックスのように見えていてもブラウザーの視点からはそうではないため、支援技術はそれがセレクトボックスであるとは理解できません。つまり、このきれいなセレクトボックスはアクセシブルではありません!

+ +

幸いなことに解決策があり、それは ARIA と呼ばれます。ARIA は "Accessible Rich Internet Application" を表し、その W3C 仕様 は私たちがここで行っていることに特化して設計されています: ウェブアプリケーションやカスタムコントロールをアクセシブルにします。これは基本的には、私たちが作り出した要素がネイティブコントロールとして通るかのように、役割や状態や特性をより説明できるようにするために HTML を拡張する属性のセットです。これらの属性の使用はとても簡単ですので、行ってみましょう。

+ +

role 属性

+ +

ARIA で使用される主要な属性が、role 属性です。role 属性は、要素を何に使用するかを定義する値を受け入れます。それぞれのロールは、自身の要件や動作を定義します。本記事の例では、ロール listbox を使用します。これは "composite role" であり、このロールの要素は子要素を持ち、またそれぞれの子要素も特定のロールを持ちます (この例では、ロール option の子要素が少なくとも 1 つ)。

+ +

また、ARIA は標準の HTML マークアップにデフォルトで適用されるロールを定義することも特筆に値します。例えば、{{HTMLElement("table")}} 要素はロール grid に、{{HTMLElement("ul")}} 要素はロール list にマッチします。{{HTMLElement("ul")}} 要素を使用しているため、私たちのコントロールのロール listbox が、{{HTMLElement("ul")}} 要素のロール list を置き換えるようにしなければなりません。そのために、ロール presentation を使用します。このロールは要素に特別な意味はないことを示せるようにするためのものであり、単に情報を与えるために使用されます。これを {{HTMLElement("ul")}} 要素に適用します。

+ +

ロール listbox をサポートするため、HTML を以下のように更新することが必要です:

+ +
<!-- 最初の要素に role="listbox" 属性を追加します -->
+<div class="select" role="listbox">
+  <span class="value">Cherry</span>
+  <!-- ul 要素に role="presentation" を追加します -->
+  <ul class="optList" role="presentation">
+    <!-- すべての li 要素に role="option" 属性を追加します -->
+    <li role="option" class="option">Cherry</li>
+    <li role="option" class="option">Lemon</li>
+    <li role="option" class="option">Banana</li>
+    <li role="option" class="option">Strawberry</li>
+    <li role="option" class="option">Apple</li>
+  </ul>
+</div>
+ +
+

注記: role 属性と class 属性の両方を含める方法は、CSS 属性セレクタに対応しない古いブラウザーをサポートしたい場合にのみ必要です。

+
+ +

aria-selected 属性

+ +

role を使用するだけでは不十分です。ARIA は、状態や特性を表す多くの属性も提供します。これらをより多くまた適切に使用すると、コントロールが支援技術にもっと良く理解されるようになります。ここでは、使用する属性を 1 つに絞ります: aria-selected です。

+ +

aria-selected 属性は、どの選択肢が現在選択されているかを示すために使用します。これにより、支援技術はユーザーに現在何が選択されているかを伝えることができます。ここではユーザーが選択肢を選択するたびに、選択された選択肢を示すためにこの属性を JavaScript で動的に使用します。このために、updateValue() 関数の変更が必要です:

+ +
function updateValue(select, index) {
+  var nativeWidget = select.previousElementSibling;
+  var value = select.querySelector('.value');
+  var optionList = select.querySelectorAll('.option');
+
+  // すべての選択肢が選択されていないようにします。
+  optionList.forEach(function (other) {
+    other.setAttribute('aria-selected', 'false');
+  });
+
+  // 指定された選択肢が選択されているようにします。
+  optionList[index].setAttribute('aria-selected', 'true');
+
+  nativeWidget.selectedIndex = index;
+  value.innerHTML = optionList[index].innerHTML;
+  highlightOption(select, optionList[index]);
+};
+ +

スクリーンリーダーにオフスクリーンselectに焦点をあてて他のスタイルを無視するようにした法が簡単に見えますが、これはアクセシブルな解決策ではありません。スクリーンリーダーは盲目の人だけのものではありません。低視力や、完全な視力の人もこれを使います。このため、スクリーンリーダーをオフスクリーン要素だけに焦点をあてるようにはできません。

+ +

以下がこれらの変更を施した最終結果です (NVDAVoiceOver などの支援技術でコントロールを使用してみても、よい感触を得られるでしょう):

+ + + + + + + + + + + + + + + +
Live example
{{EmbedLiveSample("Change_states",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_5")}}
ソースコードを確認する
+ +

もっと先に進むには、この例でのコードは汎用性や再利用性に改善が必要です。これは課題として挑戦できます。この2つのヒントを挙げると:すべての関数で最初の引数は同じで、つまりこれらの関数は同じコンテキストが必要です。そのコンテキストを共有するオブジェクトを作るのが賢明です。

+ +

他の方法: ラジオボタンを使う

+ +

上の例では、非動的なHTML、CSS、JavaScript を使って {{htmlelement('select')}} 要素を再発明しました。このselectは限られた選択肢から1つが選択され、それは {{htmlelement('input/radio', 'radio')}} ボタンのグループと同様な機能です。

+ +

このため、代わりにラジオボタンを使って再発明できます。このオプションを見てみましょう。

+ +

We can start with

+ +

完全に意味のある、アクセシブルで、順序のない、関連する{{htmlelement('label')}}つきの{{htmlelement('input/radio','radio')}} ボタンのリストから始めます、グループ全体を適切な意味のある{{htmlelement('fieldset')}} と{{htmlelement('legend')}} のペアにラベルづけします。.

+ +
 <fieldset>
+  <legend>Pick a fruit</legend>
+    <ul class="styledSelect">
+      <li><input type="radio" name="fruit" value="Cherry" id="fruitCherry" checked><label for="fruitCherry">Cherry</label></li>
+      <li><input type="radio" name="fruit" value="Lemon" id="fruitLemon"><label for="fruitLemon">Lemon</label></li>
+      <li><input type="radio" name="fruit" value="Banana" id="fruitBanana"><label for="fruitBanana"">Banana</label></li>
+      <li><input type="radio" name="fruit" value="Strawberry" id="fruitStrawberry"><label for="fruitStrawberry">Strawberry</label></li>
+      <li><input type="radio" name="fruit" value="Apple" id="fruitApple"><label for="fruitApple">Apple</label></li>
+    </ul>
+  </fieldset>
+ +

(legend/fieldsetではなく)ラジオボタンリストに少しスタイルづけをして、前の例と同じ見た目にし、完了したことがわかるようにします:

+ +
.styledSelect {
+  display: inline-block;
+  padding: 0;
+}
+.styledSelect li {
+  list-style-type: none;
+  padding: 0;
+  display: flex;
+}
+.styledSelect [type=radio] {
+  position: absolute;
+  left: -100vw;
+  top: -100vh;
+}
+.styledSelect label {
+  margin: 0;
+  line-height: 2;
+  padding: 0 0 0 4px;
+}
+.styledSelect:not(:focus-within) input:not(:checked) + label {
+  height: 0;
+  outline: none;
+  overflow: hidden;
+}
+.styledSelect:not(:focus-within) input:checked + label {
+  border: .2em solid #000;
+  border-radius: .4em;
+  box-shadow: 0 .1em .2em rgba(0,0,0,.45);
+}
+.styledSelect:not(:focus-within) input:checked + label::after {
+  content : "▼";
+  background: black;
+  float: right;
+  color: white;
+  padding: 0 4px;
+  margin: 0 -4px 0 4px;
+}
+.styledSelect:focus-within {
+  border: .2em solid #000;
+  border-radius: .4em;
+  box-shadow: 0 .1em .2em rgba(0,0,0,.45);
+}
+.styledSelect:focus-within input:checked + label {
+  background-color: #333;
+  color: #fff;
+  width: 100%;
+}
+ +

JavaScriptなしで少しの CSSにて、ラジオボタンのリストをスタイルづけしてチェック済み項目のみを表示することができます。フォーカスが<fieldset>内の <ul> に来ると、リストは開いて、上下左右の矢印が前後の項目を選択するのに使えます。次で試してください:

+ +

{{EmbedLiveSample("An_alternative_approach_Using_radio_buttons",200,240)}}

+ +

これはある程度、JavaScriptなしで動作します。JavaScript が失敗しても動作する、われわれのカスタムコントロールど同じものを作ってきました。よい解決策でしょう?これはキーボードでは動作しますが、マウスクリックではそうなりません。ネイティブな意味づけのない要素を作るフレームワークに依存する代わりに、ウェブ標準をカスタムコントロールの基礎として使った方が意味があります。しかし、われわれのコントロールは <select> が自ずと持つ機能と同じものを備えていません。

+ +

いい面として、このコントロールはスクリーンリーダーにとって完全にアクセシブルでキーボードで完全に操作できます。しかし、このコントロールは {{htmlelement('select')}} 要素の置き換えではありません。異なる機能や足りない機能があります。例えば、4つの矢印は選択肢を操作できますが、最後のボタンで下を押すと最初のボタンに移動します。<select> のように上端、下端で止まりません。

+ +

この足りない機能の追加は、読者の課題としておきます。

+ +

まとめ

+ +

独自のフォームコントロールの作成方法を見てきましたが、ご覧いただいたようにこれは容易なことではありません。独自のカスタムコントロールを作る前に、HTMLに要求を十分に満たす代替要素がないかを検討してください。本当にカスタムコントロールを作成する必要がある場合、サードパーティのライブラリに頼るほうが簡単かつよいことも少なくありません。独自作成する場合、既存の要素を編集するか、準備されたコントロールを実装するフレームワークを使うようにして、実用的でアクセシブルなフォームコントロールの作成は見た目より複雑であることを忘れないでください。

+ +

自分でコーディングする前に検討するとよいライブラリをいくつか紹介します:

+ + + +

ラジオボタン、独自JavaScript 、またはサードパーティライブラリで代替コントロールを作る場合、アクセシブルかつ機能への耐性を高めましょう。すなわち Web 標準の実装状況がまちまちである、多様なブラウザーで良好に動作できるようにすることが必要です。楽しんでください!

+ +

このモジュール

+ +

学習コース

+ + + +

上級トピック

+ + diff --git a/files/ja/learn/forms/how_to_build_custom_form_widgets/example_1/index.html b/files/ja/learn/forms/how_to_build_custom_form_widgets/example_1/index.html deleted file mode 100644 index 1515dc573f..0000000000 --- a/files/ja/learn/forms/how_to_build_custom_form_widgets/example_1/index.html +++ /dev/null @@ -1,415 +0,0 @@ ---- -title: 例 1 -slug: Learn/Forms/How_to_build_custom_form_widgets/Example_1 -translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_1 ---- -

これは、カスタムフォームウィジェットの作成方法を説明する最初のコード例です。

- -

Basic state

- -

HTML

- -
<div class="select">
-  <span class="value">Cherry</span>
-  <ul class="optList hidden">
-    <li class="option">Cherry</li>
-    <li class="option">Lemon</li>
-    <li class="option">Banana</li>
-    <li class="option">Strawberry</li>
-    <li class="option">Apple</li>
-  </ul>
-</div>
- -

CSS

- -
/* --------------- */
-/* Required Styles */
-/* --------------- */
-
-.select {
-  position: relative;
-  display : inline-block;
-}
-
-.select.active,
-.select:focus {
-  box-shadow: 0 0 3px 1px #227755;
-  outline: none;
-}
-
-.select .optList {
-  position: absolute;
-  top     : 100%;
-  left    : 0;
-}
-
-.select .optList.hidden {
-  max-height: 0;
-  visibility: hidden;
-}
-
-/* ------------ */
-/* Fancy Styles */
-/* ------------ */
-
-.select {
-  font-size   : 0.625em; /* 10px */
-  font-family : Verdana, Arial, sans-serif;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
-  width   : 10em; /* 100px */
-
-  border        : 0.2em solid #000; /* 2px */
-  border-radius : 0.4em; /* 4px */
-
-  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
-
-  background : #F0F0F0;
-  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-}
-
-.select .value {
-  display  : inline-block;
-  width    : 100%;
-  overflow : hidden;
-
-  white-space   : nowrap;
-  text-overflow : ellipsis;
-  vertical-align: top;
-}
-
-.select:after {
-  content : "▼";
-  position: absolute;
-  z-index : 1;
-  height  : 100%;
-  width   : 2em; /* 20px */
-  top     : 0;
-  right   : 0;
-
-  padding-top : .1em;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  text-align : center;
-
-  border-left  : .2em solid #000;
-  border-radius: 0 .1em .1em 0;
-
-  background-color : #000;
-  color : #FFF;
-}
-
-.select .optList {
-  z-index : 2;
-
-  list-style: none;
-  margin : 0;
-  padding: 0;
-
-  background: #f0f0f0;
-  border: .2em solid #000;
-  border-top-width : .1em;
-  border-radius: 0 0 .4em .4em;
-
-  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  min-width : 100%;
-  max-height: 10em; /* 100px */
-  overflow-y: auto;
-  overflow-x: hidden;
-}
-
-.select .option {
-  padding: .2em .3em;
-}
-
-.select .highlight {
-  background: #000;
-  color: #FFFFFF;
-}
-
- -

Result for basic state

- -
{{ EmbedLiveSample('Basic_state', 120, 130) }}
- -

Active state

- -

HTML

- -
<div class="select active">
-  <span class="value">Cherry</span>
-  <ul class="optList hidden">
-    <li class="option">Cherry</li>
-    <li class="option">Lemon</li>
-    <li class="option">Banana</li>
-    <li class="option">Strawberry</li>
-    <li class="option">Apple</li>
-  </ul>
-</div>
- -

CSS

- -
/* --------------- */
-/* Required Styles */
-/* --------------- */
-
-.select {
-  position: relative;
-  display : inline-block;
-}
-
-.select.active,
-.select:focus {
-  box-shadow: 0 0 3px 1px #227755;
-  outline: none;
-}
-
-.select .optList {
-  position: absolute;
-  top     : 100%;
-  left    : 0;
-}
-
-.select .optList.hidden {
-  max-height: 0;
-  visibility: hidden;
-}
-
-/* ------------ */
-/* Fancy Styles */
-/* ------------ */
-
-.select {
-  font-size   : 0.625em; /* 10px */
-  font-family : Verdana, Arial, sans-serif;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
-  width   : 10em; /* 100px */
-
-  border        : 0.2em solid #000; /* 2px */
-  border-radius : 0.4em; /* 4px */
-
-  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
-
-  background : #F0F0F0;
-  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-}
-
-.select .value {
-  display  : inline-block;
-  width    : 100%;
-  overflow : hidden;
-
-  white-space   : nowrap;
-  text-overflow : ellipsis;
-  vertical-align: top;
-}
-
-.select:after {
-  content : "▼";
-  position: absolute;
-  z-index : 1;
-  height  : 100%;
-  width   : 2em; /* 20px */
-  top     : 0;
-  right   : 0;
-
-  padding-top : .1em;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  text-align : center;
-
-  border-left  : .2em solid #000;
-  border-radius: 0 .1em .1em 0;
-
-  background-color : #000;
-  color : #FFF;
-}
-
-.select .optList {
-  z-index : 2;
-
-  list-style: none;
-  margin : 0;
-  padding: 0;
-
-  background: #f0f0f0;
-  border: .2em solid #000;
-  border-top-width : .1em;
-  border-radius: 0 0 .4em .4em;
-
-  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  min-width : 100%;
-  max-height: 10em; /* 100px */
-  overflow-y: auto;
-  overflow-x: hidden;
-}
-
-.select .option {
-  padding: .2em .3em;
-}
-
-.select .highlight {
-  background: #000;
-  color: #FFFFFF;
-}
- -

Result for active state

- -
{{ EmbedLiveSample('Active_state', 120, 130) }}
- -

Open state

- -

HTML

- -
<div class="select active">
-  <span class="value">Cherry</span>
-  <ul class="optList">
-    <li class="option highlight">Cherry</li>
-    <li class="option">Lemon</li>
-    <li class="option">Banana</li>
-    <li class="option">Strawberry</li>
-    <li class="option">Apple</li>
-  </ul>
-</div>
- -

CSS

- -
/* --------------- */
-/* Required Styles */
-/* --------------- */
-
-.select {
-  position: relative;
-  display : inline-block;
-}
-
-.select.active,
-.select:focus {
-  box-shadow: 0 0 3px 1px #227755;
-  outline: none;
-}
-
-.select .optList {
-  position: absolute;
-  top     : 100%;
-  left    : 0;
-}
-
-.select .optList.hidden {
-  max-height: 0;
-  visibility: hidden;
-}
-
-/* ------------ */
-/* Fancy Styles */
-/* ------------ */
-
-.select {
-  font-size   : 0.625em; /* 10px */
-  font-family : Verdana, Arial, sans-serif;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
-  width   : 10em; /* 100px */
-
-  border        : 0.2em solid #000; /* 2px */
-  border-radius : 0.4em; /* 4px */
-
-  box-shadow : 0 0.1em 0.2em rgba(0, 0, 0, .45); /* 0 1px 2px */
-
-  background : #F0F0F0;
-  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-}
-
-.select .value {
-  display  : inline-block;
-  width    : 100%;
-  overflow : hidden;
-
-  white-space   : nowrap;
-  text-overflow : ellipsis;
-  vertical-align: top;
-}
-
-.select:after {
-  content : "▼";
-  position: absolute;
-  z-index : 1;
-  height  : 100%;
-  width   : 2em; /* 20px */
-  top     : 0;
-  right   : 0;
-
-  padding-top : .1em;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  text-align : center;
-
-  border-left  : .2em solid #000;
-  border-radius: 0 .1em .1em 0;
-
-  background-color : #000;
-  color : #FFF;
-}
-
-.select .optList {
-  z-index : 2;
-
-  list-style: none;
-  margin : 0;
-  padding: 0;
-
-  background: #f0f0f0;
-  border: .2em solid #000;
-  border-top-width : .1em;
-  border-radius: 0 0 .4em .4em;
-
-  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  min-width : 100%;
-  max-height: 10em; /* 100px */
-  overflow-y: auto;
-  overflow-x: hidden;
-}
-
-.select .option {
-  padding: .2em .3em;
-}
-
-.select .highlight {
-  background: #000;
-  color: #FFF;
-}
- -

Result for open state

- -
{{ EmbedLiveSample('Open_state', 120, 130) }}
diff --git a/files/ja/learn/forms/how_to_build_custom_form_widgets/example_2/index.html b/files/ja/learn/forms/how_to_build_custom_form_widgets/example_2/index.html deleted file mode 100644 index 7a547909ce..0000000000 --- a/files/ja/learn/forms/how_to_build_custom_form_widgets/example_2/index.html +++ /dev/null @@ -1,212 +0,0 @@ ---- -title: 例 2 -slug: Learn/Forms/How_to_build_custom_form_widgets/Example_2 -tags: - - Forms - - HTML -translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_2 ---- -

これは、カスタムフォームウィジェットの作成方法を説明する2番目の例です。

- -

JS

- -

HTML コンテンツ

- -
<form class="no-widget">
-  <select name="myFruit">
-      <option>Cherry</option>
-      <option>Lemon</option>
-      <option>Banana</option>
-      <option>Strawberry</option>
-      <option>Apple</option>
-  </select>
-
-  <div class="select">
-    <span class="value">Cherry</span>
-    <ul class="optList hidden">
-      <li class="option">Cherry</li>
-      <li class="option">Lemon</li>
-      <li class="option">Banana</li>
-      <li class="option">Strawberry</li>
-      <li class="option">Apple</li>
-    </ul>
-  </div>
-<form>
-
- -

CSS コンテンツ

- -
.widget select,
-.no-widget .select {
-  position : absolute;
-  left     : -5000em;
-  height   : 0;
-  overflow : hidden;
-}
-
-/* --------------- */
-/* Required Styles */
-/* --------------- */
-
-.select {
-  position: relative;
-  display : inline-block;
-}
-
-.select.active,
-.select:focus {
-  box-shadow: 0 0 3px 1px #227755;
-  outline: none;
-}
-
-.select .optList {
-  position: absolute;
-  top     : 100%;
-  left    : 0;
-}
-
-.select .optList.hidden {
-  max-height: 0;
-  visibility: hidden;
-}
-
-/* ------------ */
-/* Fancy Styles */
-/* ------------ */
-
-.select {
-  font-size   : 0.625em; /* 10px */
-  font-family : Verdana, Arial, sans-serif;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
-  width   : 10em; /* 100px */
-
-  border        : 0.2em solid #000; /* 2px */
-  border-radius : 0.4em; /* 4px */
-
-  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
-
-  background : #F0F0F0;
-  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-}
-
-.select .value {
-  display  : inline-block;
-  width    : 100%;
-  overflow : hidden;
-
-  white-space   : nowrap;
-  text-overflow : ellipsis;
-  vertical-align: top;
-}
-
-.select:after {
-  content : "▼";
-  position: absolute;
-  z-index : 1;
-  height  : 100%;
-  width   : 2em; /* 20px */
-  top     : 0;
-  right   : 0;
-
-  padding-top : .1em;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  text-align : center;
-
-  border-left  : .2em solid #000;
-  border-radius: 0 .1em .1em 0;
-
-  background-color : #000;
-  color : #FFF;
-}
-
-.select .optList {
-  z-index : 2;
-
-  list-style: none;
-  margin : 0;
-  padding: 0;
-
-  background: #f0f0f0;
-  border: .2em solid #000;
-  border-top-width : .1em;
-  border-radius: 0 0 .4em .4em;
-
-  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  min-width : 100%;
-  max-height: 10em; /* 100px */
-  overflow-y: auto;
-  overflow-x: hidden;
-}
-
-.select .option {
-  padding: .2em .3em;
-}
-
-.select .highlight {
-  background: #000;
-  color: #FFFFFF;
-}
- -

JavaScript コンテンツ

- -
window.addEventListener("load", function () {
-  var form = document.querySelector('form');
-
-  form.classList.remove("no-widget");
-  form.classList.add("widget");
-});
- -

JS の結果

- -

{{ EmbedLiveSample('JS', 120, 130) }}

- -

No JS

- -

HTML コンテンツ

- -
<form class="no-widget">
-  <select name="myFruit">
-      <option>Cherry</option>
-      <option>Lemon</option>
-      <option>Banana</option>
-      <option>Strawberry</option>
-      <option>Apple</option>
-  </select>
-
-  <div class="select">
-    <span class="value">Cherry</span>
-    <ul class="optList hidden">
-      <li class="option">Cherry</li>
-      <li class="option">Lemon</li>
-      <li class="option">Banana</li>
-      <li class="option">Strawberry</li>
-      <li class="option">Apple</li>
-    </ul>
-  </div>
-<form>
- -

CSS コンテンツ

- -
.widget select,
-.no-widget .select {
-  position : absolute;
-  left     : -5000em;
-  height   : 0;
-  overflow : hidden;
-}
- -

No JS の結果

- -

{{ EmbedLiveSample('No_JS', 120, 130) }}

diff --git a/files/ja/learn/forms/how_to_build_custom_form_widgets/example_3/index.html b/files/ja/learn/forms/how_to_build_custom_form_widgets/example_3/index.html deleted file mode 100644 index ac3763cb80..0000000000 --- a/files/ja/learn/forms/how_to_build_custom_form_widgets/example_3/index.html +++ /dev/null @@ -1,246 +0,0 @@ ---- -title: 例 3 -slug: Learn/Forms/How_to_build_custom_form_widgets/Example_3 -tags: - - Forms - - HTML -translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_3 ---- -

これは、カスタムフォームウィジェットの作成方法を説明する3番目の例です。

- -

状態を変更する

- -

HTML コンテンツ

- -
<form class="no-widget">
-  <select name="myFruit" tabindex="-1">
-      <option>Cherry</option>
-      <option>Lemon</option>
-      <option>Banana</option>
-      <option>Strawberry</option>
-      <option>Apple</option>
-  </select>
-
-  <div class="select" tabindex="0">
-    <span class="value">Cherry</span>
-    <ul class="optList hidden">
-      <li class="option">Cherry</li>
-      <li class="option">Lemon</li>
-      <li class="option">Banana</li>
-      <li class="option">Strawberry</li>
-      <li class="option">Apple</li>
-    </ul>
-  </div>
-</form>
- -

CSS コンテンツ

- -
.widget select,
-.no-widget .select {
-  position : absolute;
-  left     : -5000em;
-  height   : 0;
-  overflow : hidden;
-}
-
-/* --------------- */
-/* Required Styles */
-/* --------------- */
-
-.select {
-  position: relative;
-  display : inline-block;
-}
-
-.select.active,
-.select:focus {
-  box-shadow: 0 0 3px 1px #227755;
-  outline: none;
-}
-
-.select .optList {
-  position: absolute;
-  top     : 100%;
-  left    : 0;
-}
-
-.select .optList.hidden {
-  max-height: 0;
-  visibility: hidden;
-}
-
-/* ------------ */
-/* Fancy Styles */
-/* ------------ */
-
-.select {
-  font-size   : 0.625em; /* 10px */
-  font-family : Verdana, Arial, sans-serif;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
-  width   : 10em; /* 100px */
-
-  border        : 0.2em solid #000; /* 2px */
-  border-radius : 0.4em; /* 4px */
-
-  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
-
-  background : #F0F0F0;
-  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-}
-
-.select .value {
-  display  : inline-block;
-  width    : 100%;
-  overflow : hidden;
-
-  white-space   : nowrap;
-  text-overflow : ellipsis;
-  vertical-align: top;
-}
-
-.select:after {
-  content : "▼";
-  position: absolute;
-  z-index : 1;
-  height  : 100%;
-  width   : 2em; /* 20px */
-  top     : 0;
-  right   : 0;
-
-  padding-top : .1em;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  text-align : center;
-
-  border-left  : .2em solid #000;
-  border-radius: 0 .1em .1em 0;
-
-  background-color : #000;
-  color : #FFF;
-}
-
-.select .optList {
-  z-index : 2;
-
-  list-style: none;
-  margin : 0;
-  padding: 0;
-
-  background: #f0f0f0;
-  border: .2em solid #000;
-  border-top-width : .1em;
-  border-radius: 0 0 .4em .4em;
-
-  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  min-width : 100%;
-  max-height: 10em; /* 100px */
-  overflow-y: auto;
-  overflow-x: hidden;
-}
-
-.select .option {
-  padding: .2em .3em;
-}
-
-.select .highlight {
-  background: #000;
-  color: #FFFFFF;
-}
- -

JavaScript コンテンツ

- -
// ------- //
-// HELPERS //
-// ------- //
-
-NodeList.prototype.forEach = function (callback) {
-  Array.prototype.forEach.call(this, callback);
-}
-
-// -------------------- //
-// Function definitions //
-// -------------------- //
-
-function deactivateSelect(select) {
-  if (!select.classList.contains('active')) return;
-
-  var optList = select.querySelector('.optList');
-
-  optList.classList.add('hidden');
-  select.classList.remove('active');
-}
-
-function activeSelect(select, selectList) {
-  if (select.classList.contains('active')) return;
-
-  selectList.forEach(deactivateSelect);
-  select.classList.add('active');
-};
-
-function toggleOptList(select, show) {
-  var optList = select.querySelector('.optList');
-
-  optList.classList.toggle('hidden');
-}
-
-function highlightOption(select, option) {
-  var optionList = select.querySelectorAll('.option');
-
-  optionList.forEach(function (other) {
-    other.classList.remove('highlight');
-  });
-
-  option.classList.add('highlight');
-};
-
-// ------------- //
-// Event binding //
-// ------------- //
-
-window.addEventListener("load", function () {
-  var form = document.querySelector('form');
-
-  form.classList.remove("no-widget");
-  form.classList.add("widget");
-});
-
-window.addEventListener('load', function () {
-  var selectList = document.querySelectorAll('.select');
-
-  selectList.forEach(function (select) {
-    var optionList = select.querySelectorAll('.option');
-
-    optionList.forEach(function (option) {
-      option.addEventListener('mouseover', function () {
-        highlightOption(select, option);
-      });
-    });
-
-    select.addEventListener('click', function (event) {
-      toggleOptList(select);
-    },  false);
-
-    select.addEventListener('focus', function (event) {
-      activeSelect(select, selectList);
-    });
-
-    select.addEventListener('blur', function (event) {
-      deactivateSelect(select);
-    });
-  });
-});
- -

結果

- -

{{ EmbedLiveSample('Change_states') }}

diff --git a/files/ja/learn/forms/how_to_build_custom_form_widgets/example_4/index.html b/files/ja/learn/forms/how_to_build_custom_form_widgets/example_4/index.html deleted file mode 100644 index 51fa53c27a..0000000000 --- a/files/ja/learn/forms/how_to_build_custom_form_widgets/example_4/index.html +++ /dev/null @@ -1,296 +0,0 @@ ---- -title: 例 4 -slug: Learn/Forms/How_to_build_custom_form_widgets/Example_4 -tags: - - Advanced - - Example - - Forms - - Guide - - HTML - - Web -translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_4 ---- -

これは、カスタムフォームウィジェットの作成方法を説明する4番目の例です。

- -

状態を変更する

- -

HTML コンテンツ

- -
<form class="no-widget">
-  <select name="myFruit">
-    <option>Cherry</option>
-    <option>Lemon</option>
-    <option>Banana</option>
-    <option>Strawberry</option>
-    <option>Apple</option>
-  </select>
-
-  <div class="select">
-    <span class="value">Cherry</span>
-    <ul class="optList hidden">
-      <li class="option">Cherry</li>
-      <li class="option">Lemon</li>
-      <li class="option">Banana</li>
-      <li class="option">Strawberry</li>
-      <li class="option">Apple</li>
-    </ul>
-  </div>
-</form>
- -

CSS コンテンツ

- -
.widget select,
-.no-widget .select {
-  position : absolute;
-  left     : -5000em;
-  height   : 0;
-  overflow : hidden;
-}
-
-/* --------------- */
-/* Required Styles */
-/* --------------- */
-
-.select {
-  position: relative;
-  display : inline-block;
-}
-
-.select.active,
-.select:focus {
-  box-shadow: 0 0 3px 1px #227755;
-  outline: none;
-}
-
-.select .optList {
-  position: absolute;
-  top     : 100%;
-  left    : 0;
-}
-
-.select .optList.hidden {
-  max-height: 0;
-  visibility: hidden;
-}
-
-/* ------------ */
-/* Fancy Styles */
-/* ------------ */
-
-.select {
-  font-size   : 0.625em; /* 10px */
-  font-family : Verdana, Arial, sans-serif;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
-  width   : 10em; /* 100px */
-
-  border        : 0.2em solid #000; /* 2px */
-  border-radius : 0.4em; /* 4px */
-
-  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
-
-  background : #F0F0F0;
-  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-}
-
-.select .value {
-  display  : inline-block;
-  width    : 100%;
-  overflow : hidden;
-
-  white-space   : nowrap;
-  text-overflow : ellipsis;
-  vertical-align: top;
-}
-
-.select:after {
-  content : "▼";
-  position: absolute;
-  z-index : 1;
-  height  : 100%;
-  width   : 2em; /* 20px */
-  top     : 0;
-  right   : 0;
-
-  padding-top : .1em;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  text-align : center;
-
-  border-left  : .2em solid #000;
-  border-radius: 0 .1em .1em 0;
-
-  background-color : #000;
-  color : #FFF;
-}
-
-.select .optList {
-  z-index : 2;
-
-  list-style: none;
-  margin : 0;
-  padding: 0;
-
-  background: #f0f0f0;
-  border: .2em solid #000;
-  border-top-width : .1em;
-  border-radius: 0 0 .4em .4em;
-
-  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  min-width : 100%;
-  max-height: 10em; /* 100px */
-  overflow-y: auto;
-  overflow-x: hidden;
-}
-
-.select .option {
-  padding: .2em .3em;
-}
-
-.select .highlight {
-  background: #000;
-  color: #FFFFFF;
-}
- -

JavaScript コンテンツ

- -
// ------- //
-// HELPERS //
-// ------- //
-
-NodeList.prototype.forEach = function (callback) {
-  Array.prototype.forEach.call(this, callback);
-}
-
-// -------------------- //
-// Function definitions //
-// -------------------- //
-
-function deactivateSelect(select) {
-  if (!select.classList.contains('active')) return;
-
-  var optList = select.querySelector('.optList');
-
-  optList.classList.add('hidden');
-  select.classList.remove('active');
-}
-
-function activeSelect(select, selectList) {
-  if (select.classList.contains('active')) return;
-
-  selectList.forEach(deactivateSelect);
-  select.classList.add('active');
-};
-
-function toggleOptList(select, show) {
-  var optList = select.querySelector('.optList');
-
-  optList.classList.toggle('hidden');
-}
-
-function highlightOption(select, option) {
-  var optionList = select.querySelectorAll('.option');
-
-  optionList.forEach(function (other) {
-    other.classList.remove('highlight');
-  });
-
-  option.classList.add('highlight');
-};
-
-function updateValue(select, index) {
-  var nativeWidget = select.previousElementSibling;
-  var value = select.querySelector('.value');
-  var optionList = select.querySelectorAll('.option');
-
-  nativeWidget.selectedIndex = index;
-  value.innerHTML = optionList[index].innerHTML;
-  highlightOption(select, optionList[index]);
-};
-
-function getIndex(select) {
-  var nativeWidget = select.previousElementSibling;
-
-  return nativeWidget.selectedIndex;
-};
-
-// ------------- //
-// Event binding //
-// ------------- //
-
-window.addEventListener("load", function () {
-  var form = document.querySelector('form');
-
-  form.classList.remove("no-widget");
-  form.classList.add("widget");
-});
-
-window.addEventListener('load', function () {
-  var selectList = document.querySelectorAll('.select');
-
-  selectList.forEach(function (select) {
-    var optionList = select.querySelectorAll('.option');
-
-    optionList.forEach(function (option) {
-      option.addEventListener('mouseover', function () {
-        highlightOption(select, option);
-      });
-    });
-
-    select.addEventListener('click', function (event) {
-      toggleOptList(select);
-    });
-
-    select.addEventListener('focus', function (event) {
-      activeSelect(select, selectList);
-    });
-
-    select.addEventListener('blur', function (event) {
-      deactivateSelect(select);
-    });
-  });
-});
-
-window.addEventListener('load', function () {
-  var selectList = document.querySelectorAll('.select');
-
-  selectList.forEach(function (select) {
-    var optionList = select.querySelectorAll('.option'),
-        selectedIndex = getIndex(select);
-
-    select.tabIndex = 0;
-    select.previousElementSibling.tabIndex = -1;
-
-    updateValue(select, selectedIndex);
-
-    optionList.forEach(function (option, index) {
-      option.addEventListener('click', function (event) {
-        updateValue(select, index);
-      });
-    });
-
-    select.addEventListener('keyup', function (event) {
-      var length = optionList.length,
-          index  = getIndex(select);
-
-      if (event.keyCode === 40 && index < length - 1) { index++; }
-      if (event.keyCode === 38 && index > 0) { index--; }
-
-      updateValue(select, index);
-    });
-  });
-});
- -

結果

- -

{{ EmbedLiveSample('Change_states') }}

diff --git a/files/ja/learn/forms/how_to_build_custom_form_widgets/example_5/index.html b/files/ja/learn/forms/how_to_build_custom_form_widgets/example_5/index.html deleted file mode 100644 index 4bad8016bb..0000000000 --- a/files/ja/learn/forms/how_to_build_custom_form_widgets/example_5/index.html +++ /dev/null @@ -1,289 +0,0 @@ ---- -title: 例 5 -slug: Learn/Forms/How_to_build_custom_form_widgets/Example_5 -tags: - - Forms - - HTML -translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_5 ---- -

これが、カスタムフォームウィジェットの作成方法を説明する最後の例です。

- -

状態を変更する

- -

HTML コンテンツ

- -
<form class="no-widget">
-  <select name="myFruit">
-    <option>Cherry</option>
-    <option>Lemon</option>
-    <option>Banana</option>
-    <option>Strawberry</option>
-    <option>Apple</option>
-  </select>
-
-  <div class="select" role="listbox">
-    <span class="value">Cherry</span>
-    <ul class="optList hidden" role="presentation">
-      <li class="option" role="option" aria-selected="true">Cherry</li>
-      <li class="option" role="option">Lemon</li>
-      <li class="option" role="option">Banana</li>
-      <li class="option" role="option">Strawberry</li>
-      <li class="option" role="option">Apple</li>
-    </ul>
-  </div>
-</form>
- -

CSS コンテンツ

- -
.widget select,
-.no-widget .select {
-  position : absolute;
-  left     : -5000em;
-  height   : 0;
-  overflow : hidden;
-}
-
-/* --------------- */
-/* Required Styles */
-/* --------------- */
-
-.select {
-  position: relative;
-  display : inline-block;
-}
-
-.select.active,
-.select:focus {
-  box-shadow: 0 0 3px 1px #227755;
-  outline: none;
-}
-
-.select .optList {
-  position: absolute;
-  top     : 100%;
-  left    : 0;
-}
-
-.select .optList.hidden {
-  max-height: 0;
-  visibility: hidden;
-}
-
-/* ------------ */
-/* Fancy Styles */
-/* ------------ */
-
-.select {
-  font-size   : 0.625em; /* 10px */
-  font-family : Verdana, Arial, sans-serif;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
-  width   : 10em; /* 100px */
-
-  border        : 0.2em solid #000; /* 2px */
-  border-radius : 0.4em; /* 4px */
-
-  box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */
-
-  background : #F0F0F0;
-  background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-}
-
-.select .value {
-  display  : inline-block;
-  width    : 100%;
-  overflow : hidden;
-
-  white-space   : nowrap;
-  text-overflow : ellipsis;
-  vertical-align: top;
-}
-
-.select:after {
-  content : "▼";
-  position: absolute;
-  z-index : 1;
-  height  : 100%;
-  width   : 2em; /* 20px */
-  top     : 0;
-  right   : 0;
-
-  padding-top : .1em;
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  text-align : center;
-
-  border-left  : .2em solid #000;
-  border-radius: 0 .1em .1em 0;
-
-  background-color : #000;
-  color : #FFF;
-}
-
-.select .optList {
-  z-index : 2;
-
-  list-style: none;
-  margin : 0;
-  padding: 0;
-
-  background: #f0f0f0;
-  border: .2em solid #000;
-  border-top-width : .1em;
-  border-radius: 0 0 .4em .4em;
-
-  box-shadow: 0 .2em .4em rgba(0,0,0,.4);
-
-  -moz-box-sizing : border-box;
-  box-sizing : border-box;
-
-  min-width : 100%;
-  max-height: 10em; /* 100px */
-  overflow-y: auto;
-  overflow-x: hidden;
-}
-
-.select .option {
-  padding: .2em .3em;
-}
-
-.select .highlight {
-  background: #000;
-  color: #FFFFFF;
-}
- -

JavaScript コンテンツ

- -
// ------- //
-// HELPERS //
-// ------- //
-
-NodeList.prototype.forEach = function (callback) {
-  Array.prototype.forEach.call(this, callback);
-}
-
-// -------------------- //
-// Function definitions //
-// -------------------- //
-
-function deactivateSelect(select) {
-  if (!select.classList.contains('active')) return;
-
-  var optList = select.querySelector('.optList');
-
-  optList.classList.add('hidden');
-  select.classList.remove('active');
-}
-
-function activeSelect(select, selectList) {
-  if (select.classList.contains('active')) return;
-
-  selectList.forEach(deactivateSelect);
-  select.classList.add('active');
-};
-
-function toggleOptList(select, show) {
-  var optList = select.querySelector('.optList');
-
-  optList.classList.toggle('hidden');
-}
-
-function highlightOption(select, option) {
-  var optionList = select.querySelectorAll('.option');
-
-  optionList.forEach(function (other) {
-    other.classList.remove('highlight');
-  });
-
-  option.classList.add('highlight');
-};
-
-function updateValue(select, index) {
-  var nativeWidget = select.previousElementSibling;
-  var value = select.querySelector('.value');
-  var optionList = select.querySelectorAll('.option');
-
-  optionList.forEach(function (other) {
-    other.setAttribute('aria-selected', 'false');
-  });
-
-  optionList[index].setAttribute('aria-selected', 'true');
-
-  nativeWidget.selectedIndex = index;
-  value.innerHTML = optionList[index].innerHTML;
-  highlightOption(select, optionList[index]);
-};
-
-function getIndex(select) {
-  var nativeWidget = select.previousElementSibling;
-
-  return nativeWidget.selectedIndex;
-};
-
-// ------------- //
-// Event binding //
-// ------------- //
-
-window.addEventListener("load", function () {
-  var form = document.querySelector('form');
-
-  form.classList.remove("no-widget");
-  form.classList.add("widget");
-});
-
-window.addEventListener('load', function () {
-  var selectList = document.querySelectorAll('.select');
-
-  selectList.forEach(function (select) {
-    var optionList = select.querySelectorAll('.option'),
-        selectedIndex = getIndex(select);
-
-    select.tabIndex = 0;
-    select.previousElementSibling.tabIndex = -1;
-
-    updateValue(select, selectedIndex);
-
-    optionList.forEach(function (option, index) {
-      option.addEventListener('mouseover', function () {
-        highlightOption(select, option);
-      });
-
-      option.addEventListener('click', function (event) {
-        updateValue(select, index);
-      });
-    });
-
-    select.addEventListener('click', function (event) {
-      toggleOptList(select);
-    });
-
-    select.addEventListener('focus', function (event) {
-      activeSelect(select, selectList);
-    });
-
-    select.addEventListener('blur', function (event) {
-      deactivateSelect(select);
-    });
-
-    select.addEventListener('keyup', function (event) {
-      var length = optionList.length,
-          index  = getIndex(select);
-
-      if (event.keyCode === 40 && index < length - 1) { index++; }
-      if (event.keyCode === 38 && index > 0) { index--; }
-
-      updateValue(select, index);
-    });
-  });
-});
-
- -

結果

- -

{{ EmbedLiveSample('Change_states') }}

diff --git a/files/ja/learn/forms/how_to_build_custom_form_widgets/index.html b/files/ja/learn/forms/how_to_build_custom_form_widgets/index.html deleted file mode 100644 index 7bbd20b511..0000000000 --- a/files/ja/learn/forms/how_to_build_custom_form_widgets/index.html +++ /dev/null @@ -1,901 +0,0 @@ ---- -title: カスタムフォームコントロールの作成方法 -slug: Learn/Forms/How_to_build_custom_form_widgets -tags: - - Advanced - - Example - - Forms - - Guide - - HTML - - Web -translation_of: Learn/Forms/How_to_build_custom_form_controls ---- -
{{LearnSidebar}}
- -

HTML フォームで使用可能なコントロールだけでは十分でない場合が多くあります。例えば、{{HTMLElement("select")}} 要素のようなコントロールに高度なスタイル設定を行いたい場合や、独自の動作を提供したい場合は、あなた独自のコントロールを作成するしかありません。

- -

本記事では、そのようなコントロールの作り方を見ていきます。その目的のため、次の例に取り組みます: {{HTMLElement("select")}} 要素の再構築です。このほかに独自コントロール作成をどういう方法でするか、いつするか、またそれが意味をなすのかや、コントロール作成が必須なときに何に気をつけるべきかを扱います。

- -
-

注記: ここではコントロールの構築に注目しており、汎用かつ再利用可能なコードの作成法は見ていきません。それには JavaScript の重要なコードや未知のコンテキストでの DOM 操作が組み合わされており、本記事の対象から外れます。

-
- -

デザイン、構造、セマンティクス

- -

カスタムコントロールを作成する前に、何をしたいかをはっきりと理解することから始めるべきです。これはあなたの貴重な時間を節約するでしょう。特に、コントロールの全状態を明確に定義することが重要です。これを行うには、状態や動作がよく知られている既存のコントロールからスタートするとよいでしょう。この結果、簡単に可能な限りの模倣を行えます。

- -

本記事の例では、{{HTMLElement("select")}} 要素を再構築します。以下が、私たちが実現したい成果です:

- -

The three states of a select box

- -

このスクリーンショットでは、コントロールの主要な状態 3 つを示しています: 通常状態 (左)、アクティブ状態 (中央)、そして開いた状態 (右) です。

- -

動作の点では、ネイティブな HTML用要素を再作成します。このため、ネイティブ HTML要素と同様な動作や意味を持たせるべきです。独自のコントロールもネイティブコントロールと同様に、キーボードだけでなくマウスでも使用できるように、またスクリーンリーダーに理解できるようにしたいと考えます。コントロールがどのように各状態に達するかを定義することから始めましょう:

- -
-
コントロールは以下のときに通常状態になります:
-
-
    -
  • ページを読み込む
  • -
  • コントロールはアクティブであったが、ユーザーがコントロール以外のどこかをクリックした
  • -
  • コントロールはアクティブであったが、キーボードを使用して別のコントロールにフォーカスを移した (例 Tab キー)
  • -
-
-
コントロールは以下のときにアクティブ状態になります:
-
-
    -
  • ユーザーがコントロール上でクリックする
  • -
  • ユーザーが Tab キーを押下して、コントロールがフォーカスを得る
  • -
  • コントロールが開いた状態で、ユーザーがコントロールをクリックする
  • -
-
-
コントロールは以下のときに開いた状態になります:
-
-
    -
  • コントロールが開いた状態ではないときに、ユーザーがコントロールをクリックした
  • -
-
-
- -

状態をどのように変えるかを理解したら、コントロールの値をどのように変えるかの定義が重要になります:

- -
-
以下のときに値が変わります:
-
-
    -
  • コントロールが開いた状態であるときに、ユーザーが選択肢をクリックする
  • -
  • コントロールがアクティブ状態であるときに、ユーザーが上下矢印キーを押下する
  • -
-
-
- -
-
以下のときには値は変わりません:
-
-
    -
  • 最初のオプションが選択済みのときに、ユーザーが上矢印をクリックする
  • -
  • 最後のオプションが選択済みのときに、ユーザーが下矢印をクリックする
  • -
-
-
- -

最後に、コントロールの選択肢がどのように動作するかを定義しましょう:

- - - -

この例の用途としては、ここまでです。しかし注意深い読者の方は、いくつかの動作が欠けていることに気づくでしょう。例えば、コントロールが開いた状態であるときにユーザーが Tab キーを押すと何が起きると考えますか? その答えは... 何も起きません。正しい動作は明らかでしょうが、実際は私たちの仕様で定義されていないため、とても見逃されやすいのです。これは、コントロールの動作を設計する人と実装する人が異なるチーム環境で特に当てはまります。

- -

別のおもしろい例です: コントロールが開いた状態であるときに上下矢印キーを押すと何が起きるのでしょうか? こちらはやや難しくなります。アクティブ状態と開いた状態をまったく別のものと考えるなら、その答えはやはり "何も起きません" です。これは、開いた状態でのキーボードの作用を定義していないためです。一方、アクティブ状態と開いた状態が少し重なると考えるなら、値は替わるかもしれませんがそれに対応して選択肢が強調されることはないでしょう。繰り返しになりますが、これはコントロールが開いた状態の選択肢に対するキーボードの作用を定義していないためです (コントロールが開いた状態で何が起きるかだけを定義しており、その後がないためです)。

- -

もう少し突っ込んで考えてみます: エスケープキーはどうでしょう? Esc キーを押すと開いた select が閉じます。ネイティブの{{htmlelement('select')}}と同じ機能を提供する場合、キーボードやマウスやスクリーンリーダーへのタッチ、その他あらゆる入力デバイスまで、全てのユーザーにとっての select の動作と全く同じようにふるまうべきです。

- -

この例では欠けている仕様が明らかですので対処するでしょうが、めずらしい新たなコントロールでは真の問題になり得ます。標準要素では、{{htmlelement('select')}} もその 1 つですが、仕様の作成者は膨大な時間をかけて全てのユースケースの全ての入力デバイスの全ての操作を指定します。新コントロールの作成は簡単ではなく、特にそれが作成されたことのないものの場合は、どのような動作が正しいかについて、わずかなアイデアですら誰も持っていないため簡単ではないです。少なくとも select はこれまでやってきたため、どうふるまうかはわかっています!

- -

一般的に、新しい操作を設計するのは、標準を作成するに十分なリーチを持った、とても大きな産業プレイヤーだけの選択肢です。例えば、Apple は 2001年に iPod にスクロールホイールを導入しました。完全に新しい操作方法のデバイスを導入するのに成功するマーケットシェアがありましたが、たいていのデバイス会社はそうはいきません。

- -

新しいユーザーインタラクションを発明しないのがベストです。インタラクションを追加する場合、設計段階で時間を使うのが重要です。動作の定義が貧弱であったり定義もれがあったりした場合、いったんユーザーが使い始めると動作を再定義するのが非常に困難になると思われますので、設計段階に時間をかけることは賢明です。もし疑っているのでしたら、他の人に意見を聞きましょう。また予算を持っているのでしたら、ユーザーテストの実施をためらってはいけません。このプロセスは、UX デザインと呼ばれます。この点について詳しく学びたいのでしたら、以下の役に立つリソースをご覧になるとよいでしょう:

- - - -
-

注記: さらにほとんどのシステムでは、使用できる選択肢すべてを見るために {{HTMLElement("select")}} 要素を開く手段があります (これは {{HTMLElement("select")}} 要素をマウスでクリックするのと同じです)。これは Windows では Alt + Down  キー で実現できますが、この例では実装しません。しかし、仕組みはすでに click イベント向けに実装されていますので、行うのは簡単です。

-
- -

HTML の構造とセマンティクスの定義

- -

コントロールの基本的な機能が決まりましたので、構築を始めるときが来ました。最初のステップはコントロールの HTML 構造の定義と、基本的なセマンティクスの付与です。こちらが、{{HTMLElement("select")}} 要素の再構築に必要な HTML です:

- -
<!-- これはコントロールの中心的なコンテナです。
-     tabindex 属性は、ユーザーがコントロールにフォーカスを当てられるようにするものです。
-     これを JavaScript で設定する方がよいことは、後で見ていきます。-->
-<div class="select" tabindex="0">
-
-  <!-- このコンテナは、コントロールの現在の値を表示するために使用します。-->
-  <span class="value">Cherry</span>
-
-  <!-- このコンテナは、コントロールで使用できるすべての選択肢を包含します。
-       これはリストですから、ul 要素を使用するとよいでしょう。-->
-  <ul class="optList">
-    <!-- 各々の選択肢は表示される値だけを包含しており、フォームのデータで送信される
-         実際の値を処理する方法は後で見ていきます。-->
-    <li class="option">Cherry</li>
-    <li class="option">Lemon</li>
-    <li class="option">Banana</li>
-    <li class="option">Strawberry</li>
-    <li class="option">Apple</li>
-  </ul>
-
-</div>
- -

クラス名の使い方に注目してください。これらは基盤となる実際の HTML とは関係なく、フォームに関するそれぞれの部分を示します。これは CSS や JavaScript を強固な HTML の構造と結びつけないようにするために重要であり、そのためにコントロールを扱うコードを壊すことなく、後から実装を変更することができます。例えば {{HTMLElement("optgroup")}} 要素と同等の機能を実装したい場合などです。

- -

クラス名は、しかしながら、意味のある値ではありません。現在の状態では、スクリーンリーダーのユーザーのみがリストを"見る"ことができます。ARIA セマンティクスを少し追加します。

- -

CSS でルックアンドフィールを作成する

- -

構造ができましたので、コントロールのデザインを始められます。カスタムコントロールを作成する上でのポイントは、望むとおりにコントロールへスタイルを設定できることです。そのために、CSS を 2 つの部分に分けます: ひとつはコントロールが {{HTMLElement("select")}} 要素のように動作するために欠かせない CSS ルールであり、もうひとつは希望する見た目にするための好みのスタイルで構成されます。

- -

必須のスタイル

- -

必須のスタイルは、コントロールの 3 つの状態を扱うために欠かせないものです。

- -
.select {
-  /* 選択肢のリスト向けのポジショニングコンテキストを作成します;
-     adding this to .select{{cssxref(':focus-within')}} will be a better option when fully supported
-  */
-  position: relative;
-
-  /* コントロールをテキストフローの一部かつまとまった大きさにします */
-  display : inline-block;
-}
- -

アクティブ状態であるコントロールのルックアンドフィールを定義するため、追加で active クラスが必要です。このコントロールはフォーカスを得ることができますので、同様に動作させるためにカスタムスタイルを {{cssxref(":focus")}} 疑似クラスにも適用します。

- -
.select.active,
-.select:focus {
-  outline: none;
-
-  /* box-shadow プロパティは必須ではありませんが、これをデフォルト値として使用するのは
-     アクティブ状態を見えるようにするために重要です。自由に書き換えてください。*/
-  box-shadow: 0 0 3px 1px #227755;
-}
- -

次に、選択肢のリストを扱いましょう:

- -
/* .select セレクタは、私たちが定義するクラスがコントロールの内部にあることを
-   確実にするためのシンタックスシュガーです。*/
-.select .optList {
-  /* 選択肢のリストが値の下部かつ HTML フローの外側に表示される
-     ようにします。*/
-  position : absolute;
-  top      : 100%;
-  left     : 0;
-}
- -

選択肢のリストが隠れている状態を扱うための追加クラスも必要です。これはアクティブ状態と開いた状態で完全には一致しない相違点を扱うために必要です。

- -
.select .optList.hidden {
-  /* これはアクセシブルな方法でリストを隠すための簡単な方法です。
-     アクセシビリティについては最後に説明します。*/
-  max-height: 0;
-  visibility: hidden;
-}
- -
-

: 選択肢のリストに高さと幅を与えないように transform: scale(1, 0) も使えます。

-
- -

美化

- -

基本的な機能性を適切に置きましたので、戯れを始められます。以下は何ができるかの例であり、本記事の冒頭で示したスクリーンショットに一致するものです。とはいえ、自由に実験して何ができるかを見てみるとよいでしょう。

- -
.select {
-  /* アクセシビリティのため、すべてのサイズは em 単位の値で表します
-     (ユーザーがテキストのみのモードでブラウザーのズーム機能を使用したときに、
-     コントロールをリサイズ可能にします)。算出結果は、ほとんどのブラウザーで
-     デフォルト値である 1em == 16px を想定します。
-     px から em への変換がわからない場合は http://riddle.pl/emcalc/ を試してください */
-  font-size   : 0.625em; /* この値 (10px) は、本コンテキストにおける新たなフォントサイズの em 単位値です。*/
-  font-family : Verdana, Arial, sans-serif;
-
-  box-sizing : border-box;
-
-  /* 後で追加する下向き矢印のためのスペースが必要です */
-  padding : .1em 2.5em .2em .5em; /* 1px 25px 2px 5px */
-  width   : 10em; /* 100px */
-
-  border        : .2em solid #000; /* 2px */
-  border-radius : .4em; /* 4px */
-  box-shadow    : 0 .1em .2em rgba(0,0,0,.45); /* 0 1px 2px */
-
-  /* 最初の宣言は、線形グラデーションをサポートしないブラウザー向けのものです。*/
-  background : #F0F0F0;
-  background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0);
-}
-
-.select .value {
-  /* 値がコントロールの幅より大きくなる可能性があるため、コントロールの幅を
-     変更しないようにすることが必要です。内容物がオーバーフローした場合は、
-     省略記号をつけるとよいでしょう。*/
-  display  : inline-block;
-  width    : 100%;
-  overflow : hidden;
-  white-space : nowrap;
-  text-overflow: ellipsis;
-  vertical-align: top;
-}
- -

下向き矢印をデザインするための追加要素は不要です。代わりに {{cssxref(":after")}} 疑似要素を使用します。ただし、select クラスでシンプルな背景画像を使用することによる実装も可能です。

- -
.select:after {
-  content : "▼"; /* Unicode 文字 U+25BC を使用します。http://www.utf8-chartable.de をご覧ください */
-  position: absolute;
-  z-index : 1; /* これは、矢印が選択肢のリストに重ならないようにするために重要です */
-  top     : 0;
-  right   : 0;
-
-  box-sizing : border-box;
-
-  height  : 100%;
-  width   : 2em;
-  padding-top : .1em;
-
-  border-left  : .2em solid #000;
-  border-radius: 0 .1em .1em 0;
-
-  background-color : #000;
-  color : #FFF;
-  text-align : center;
-}
- -

次に、選択肢のリストにスタイルを設定しましょう:

- -
.select .optList {
-  z-index : 2; /* 選択肢のリストが下向き矢印より上になるよう、明示的に示します。*/
-
-  /* ul 要素のデフォルトスタイルを初期化します。*/
-  list-style: none;
-  margin : 0;
-  padding: 0;
-
-  box-sizing : border-box;
-
-  /* 値の幅がコントロールの幅より小さい場合でも、選択肢のリストの幅が
-     コントロール自体と同じになるようにします。*/
-  min-width : 100%;
-
-  /* リストが長すぎる場合に、内容物が垂直方向にはみ出します (自動的に
-     垂直スクロールバーを表示します) が、水平方向にはみ出しません
-     (幅を指定しないため、リストは自身の幅へ自動的に調整されます。
-     それができない場合は、内容物が切り詰められます) 。*/
-  max-height: 10em; /* 100px */
-  overflow-y: auto;
-  overflow-x: hidden;
-
-  border: .2em solid #000; /* 2px */
-  border-top-width : .1em; /* 1px */
-  border-radius: 0 0 .4em .4em; /* 0 0 4px 4px */
-
-  box-shadow: 0 .2em .4em rgba(0,0,0,.4); /* 0 2px 4px */
-  background: #f0f0f0;
-}
- -

選択肢向けに、ユーザーが選択しようとしている (あるいは選択した) 値を示せるようにするための highlight クラスを追加しなければなりません。

- -
.select .option {
-  padding: .2em .3em; /* 2px 3px */
-}
-
-.select .highlight {
-  background: #000;
-  color: #FFFFFF;
-}
- -

これで、3 つの状態の結果は以下のようになります:

- - - - - - - - - - - - - - - - - - - -
通常状態アクティブ状態開いた状態
{{EmbedLiveSample("Basic_state",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_1")}}{{EmbedLiveSample("Active_state",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_1")}}{{EmbedLiveSample("Open_state",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_1")}}
ソースコードを確認する
- -

JavaScript でコントロールに命を吹き込む

- -

デザインや構造の準備ができましたので、コントロールが実際に動作するようにするための JavaScript コードを記述できます。

- -
-

警告: 以下は教育目的のコードであり、そのままで使用するべきではありません。ご覧のとおり、さまざまな箇所に将来性のないものや古いブラウザーで動作しないものがあります。また、本番のコードでは最適化すべき冗長な箇所もあります。

-
- -

なぜ動作しないのか?

- -

始める前に、重要事項を覚えておくことが大切です: ブラウザー内の JavaScript は信頼できる技術ではありません。カスタムコントロールを作成するとき、すべてをつなぎ合わせるために必要であることから JavaScript に頼らなければならないでしょう。ところが、JavaScript をブラウザーで実行できない場合がいくつもあります:

- - - -

このようなリスクがあるため、JavaScript が動作しない場合に何が起きるかを注意深く考えることが重要です。この問題について詳しく扱うのは、スクリプトをどれだけ汎用および再利用可能にしたいかと密接に関わりますので本記事の範囲を外れますが、本サンプルでは基本的な部分について考えていきます。

- -

本記事の例では、JavaScript コードが実行されない場合に標準の {{HTMLElement("select")}} 要素にフォールバックします。われわれのコントロールと {{HTMLElement("select")}}を導入し、いずれが表示されるかは body 要素のり class に依存します。読み込みに成功したとき、body 要素の class がコントロールの関数をなす script により更新されます。

- -

これを実現するには、2 つのことが必要です。

- -

第一に、カスタムコントロールを使用する前に通常の {{HTMLElement("select")}} 要素を追加することが必要です。実際は、これは残りのフォームデータと共にカスタムコントロールのデータを送信できるようにするために必要です。詳しくは後述します。

- -
<body class="no-widget">
-  <form>
-    <select name="myFruit">
-      <option>Cherry</option>
-      <option>Lemon</option>
-      <option>Banana</option>
-      <option>Strawberry</option>
-      <option>Apple</option>
-    </select>
-
-    <div class="select">
-      <span class="value">Cherry</span>
-      <ul class="optList hidden">
-        <li class="option">Cherry</li>
-        <li class="option">Lemon</li>
-        <li class="option">Banana</li>
-        <li class="option">Strawberry</li>
-        <li class="option">Apple</li>
-      </ul>
-    </div>
-  </form>
-
-</body>
- -

第二に、不要な要素 (すなわち、スクリプトを実行する場合における "本物の" {{HTMLElement("select")}} 要素や、実行しない場合におけるカスタムコントロール) を隠せるようにするための新たなクラスが 2 つ必要です。デフォルトでは、HTML コードでカスタムコントロールを隠すことに注意してください。

- -
.widget select,
-.no-widget .select {
-  /* この CSS セレクタの基本的な意味は:
-     - body のクラスを "widget" に設定して、本物の {{HTMLElement("select")}} 要素を隠す
-     - または body のクラスを変更せずに "no-widget" のままにしておくことで、
-       クラスが "select" である要素が隠される */
-  position : absolute;
-  left     : -5000em;
-  height   : 0;
-  overflow : hidden;
-}
- -

この CSS は要素の 1 つを見えなくしますが、スクリーンリーダーからは利用できます。

- -

ここで、スクリプトを実行するか否かを判断するための JavaScript スイッチが必要になります。このスイッチはとても簡単です: ページを読み込むときにスクリプトを実行したら、no-widget クラスを削除して widget クラスを追加します。これにより {{HTMLElement("select")}} 要素やカスタムコントロールの可視性を切り替えます。

- -
window.addEventListener("load", function () {
-  document.body.classList.remove("no-widget");
-  document.body.classList.add("widget");
-});
- - - - - - - - - - - - - - - - - -
JS なしJS あり
{{EmbedLiveSample("No_JS",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_2")}}{{EmbedLiveSample("JS",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_2")}}
ソースコードを確認する
- -
-

注記: コードを本当に汎用かつ再利用可能にしたい場合はクラスを切り替えるのではなく、単に {{HTMLElement("select")}} 要素を隠すためのコントロールのクラスを追加して、ページ内にあるすべての {{HTMLElement("select")}} 要素の後ろにカスタムコントロールを表す DOM ツリーを動的に追加する方がはるかによいでしょう。

-
- -

作業をより簡単に

- -

作成しようとしているコードでは、必要な作業すべてのために標準の JavaScript と DOM API を使用するでしょう。ここで使用するつもりである機能は以下のとおりです:

- -
    -
  1. {{domxref("element.classList","classList")}}
  2. -
  3. {{domxref("EventTarget.addEventListener","addEventListener")}}
  4. -
  5. forEach
  6. -
  7. {{domxref("element.querySelector","querySelector")}} および {{domxref("element.querySelectorAll","querySelectorAll")}}
  8. -
- -

これら特定機能を利用できるかに加えて、作業を始める前に残されている問題があります。{{domxref("element.querySelectorAll","querySelectorAll()")}} 関数が返すオブジェクトは Array ではなく {{domxref("NodeList")}} です。これは、Array オブジェクトは forEach 関数をサポートしているが {{domxref("NodeList")}} はサポートしていないために重要な問題です。{{domxref("NodeList")}} は Array ととても似ており、また forEach はとても便利であることから、作業を楽にするため以下のように {{domxref("NodeList")}} で forEach をサポートさせることができます:

- -
NodeList.prototype.forEach = function (callback) {
-  Array.prototype.forEach.call(this, callback);
-}
- -

古いブラウザーをサポートする必要がある場合、そのブラウザーがこうした機能をサポートしているか確かめてください。そうでない場合、リスト内を繰り返すか、ライブラリーや pollyfill を使う必要があります。

- -

イベントコールバックを作成する

- -

基盤が整いましたので、ユーザーがコントロールと対話するたびに使用されるすべての関数を定義し始めることができます。

- -
// この関数は、カスタムコントロールを非アクティブにしたいときに使用します。
-// 引数は 1 つあります。
-// select : 非アクティブにする `select` クラスの DOM ノード
-function deactivateSelect(select) {
-
-  // コントロールがアクティブではないときは何もしません。
-  if (!select.classList.contains('active')) return;
-
-  // カスタムコントロールの選択肢のリストを取得することが必要です。
-  var optList = select.querySelector('.optList');
-
-  // 選択肢のリストを閉じます。
-  optList.classList.add('hidden');
-
-  // そして、カスタムコントロール自身を非アクティブにします。
-  select.classList.remove('active');
-}
-
-// この関数は、ユーザーがコントロールをアクティブ/非アクティブにしたがっているときに使用します。
-// 引数は 2 つあります:
-// select : アクティブにする `select` クラスの DOM ノード
-// selectList : `select` クラスであるすべての DOM ノードのリスト
-function activeSelect(select, selectList) {
-
-  // コントロールがすでにアクティブであるときは何もしません。
-  if (select.classList.contains('active')) return;
-
-  // すべてのカスタムコントロールを非アクティブにすることが必要です。
-  // deactivateSelect 関数は forEach コールバック関数の要件を
-  // すべて満たしていますので、仲介する無名関数を使用せずに
-  // 直接使用しています。
-  selectList.forEach(deactivateSelect);
-
-  // そして、指定されたコントロールをアクティブ状態にします。
-  select.classList.add('active');
-}
-
-// この関数は、ユーザーが選択肢のリストを開く/閉じることを求めたときに使用します。
-// 引数は 1 つあります:
-// select : 表示を切り替えるリストの DOM ノード
-function toggleOptList(select) {
-
-  // リストはコントロールから確保します。
-  var optList = select.querySelector('.optList');
-
-  // リストのクラスを表示/非表示に切り替えます。
-  optList.classList.toggle('hidden');
-}
-
-// この関数は、選択肢を強調したいときに使用します。
-// 引数は 2 つあります:
-// select : 強調する選択肢を包含する `select` クラスの DOM ノード
-// option : 強調する `option` クラスの DOM ノード
-function highlightOption(select, option) {
-
-  // カスタムコントロールで使用可能なすべての選択肢のリストを取得します。
-  var optionList = select.querySelectorAll('.option');
-
-  // すべての選択肢から強調効果を取り除きます。
-  optionList.forEach(function (other) {
-    other.classList.remove('highlight');
-  });
-
-  // 適切な選択肢を強調します。
-  option.classList.add('highlight');
-};
- -

以上が、カスタムコントロールのさまざまな状態を制御するために必要なもののすべてです。

- -

次に、これらの関数と適切なイベントを関連づけます:

- -
// ドキュメントが読み込まれたときのイベントの関連づけを制御します。
-window.addEventListener('load', function () {
-  var selectList = document.querySelectorAll('.select');
-
-  // 各々のコントロールは初期化が必要です。
-  selectList.forEach(function (select) {
-
-    // すべての `option` も同様です。
-    var optionList = select.querySelectorAll('.option');
-
-    // ユーザーが選択肢にマウスポインタを乗せるたびに、その選択肢を強調します。
-    optionList.forEach(function (option) {
-      option.addEventListener('mouseover', function () {
-        // 注記: 変数 `select` および `option` は、関数呼び出しのスコープ内でのみ
-        // 使用可能なクロージャです。
-        highlightOption(select, option);
-      });
-    });
-
-    // ユーザーが独自の select 要素でクリックするたびに
-    select.addEventListener('click', function (event) {
-      // 注記: 変数 `select` は、関数呼び出しのスコープ内でのみ
-      // 使用可能なクロージャです。
-
-      // 選択肢のリストの可視性を切り替えます。
-      toggleOptList(select);
-    });
-
-    // コントロールが再びフォーカスを得た場合
-    // ユーザーがコントロールをクリックしたり、コントロールへアクセスするために
-    // Tab キーを使用するたびに、コントロールはフォーカスを得ます。
-    select.addEventListener('focus', function (event) {
-      // 注記: 変数 `select` および `selectList` は、関数呼び出しのスコープ内でのみ
-      // 使用可能なクロージャです。
-
-      // コントロールをアクティブにします。
-      activeSelect(select, selectList);
-    });
-
-    // コントロールがフォーカスを失った場合
-    select.addEventListener('blur', function (event) {
-      // 注記: 変数 `select` は、関数呼び出しのスコープ内でのみ
-      // 使用可能なクロージャです。
-
-      // コントロールを非アクティブにします。
-      deactivateSelect(select);
-    });
-
-    // ユーザーが`esc`を押した場合にフォーカスを外す
-    select.addEventListener('keyup', function (event) {
-
-      // deactive on keyup of `esc`
-      if (event.keyCode === 27) {
-         deactivateSelect(select);
-      }
-    });
-});
- -

この時点でコントロールは設計どおりに状態が変わりますが、コントロール値はまだ更新されません。次の章でこれを扱います。

- - - - - - - - - - - - - - - -
Live example
{{EmbedLiveSample("Change_states",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_3")}}
ソースコードを確認する
- -

コントロールの値を制御する

- -

コントロールが動作するようになりましたので、ユーザーの入力に従って値を更新して、フォームデータと共にその値を送信できるようにするコードを追加しなければなりません。

- -

これを行うもっとも簡単な方法は、覆い隠したネイティブコントロールを使用することです。そのようなコントロールはブラウザーが提供するすべての組み込みのコントロールと共に値の経過を保持しており、フォームを送信するときは通常どおりに値を送信します。これらすべてを行えるようにするために、車輪の再発明を行うのは無駄です。

- -

先ほど見たように、アクセシビリティの理由からフォールバック手段としてすでにネイティブの select コントロールを使用しています。単純に、その値をカスタムコントロールの値と同期することができます:

- -
// この関数は、表示される値を更新してネイティブコントロールの値と同期します。
-// 引数は 2 つあります:
-// select : 更新する値を持つ `select` クラスの DOM ノード
-// index  : 選択される値のインデックス
-function updateValue(select, index) {
-  // 指定されたカスタムコントロール向けのネイティブコントロールを取得することが必要です。
-  // この例では、ネイティブコントロールはカスタムコントロールの兄弟です。
-  var nativeWidget = select.previousElementSibling;
-
-  // カスタムコントロールの値のプレースホルダーの取得も必要です。
-  var value = select.querySelector('.value');
-
-  // そして、選択肢の全リストが必要です。
-  var optionList = select.querySelectorAll('.option');
-
-  // 選択した値のインデックスを、selectedIndex に設定します。
-  nativeWidget.selectedIndex = index;
-
-  // 上記に応じて、値のプレースホルダーも更新します。
-  value.innerHTML = optionList[index].innerHTML;
-
-  // そして、カスタムコントロールで対応する選択肢を強調します。
-  highlightOption(select, optionList[index]);
-};
-
-// この関数は、ネイティブコントロールで現在選択されているインデックスを返します。
-// 引数は 1 つあります:
-// select : ネイティブコントロールに関係する `select` クラスの DOM ノード
-function getIndex(select) {
-  // 指定されたカスタムコントロール向けのネイティブコントロールにアクセスすることが必要です。
-  // この例では、ネイティブコントロールはカスタムコントロールの兄弟です。
-  var nativeWidget = select.previousElementSibling;
-
-  return nativeWidget.selectedIndex;
-};
- -

これら 2 つの関数で、ネイティブコントロールとカスタムコントロールを関連づけることができます:

- -
// ドキュメントが読み込まれたときのイベントの関連づけを制御します。
-window.addEventListener('load', function () {
-  var selectList = document.querySelectorAll('.select');
-
-  // 各々のコントロールは初期化が必要です。
-  selectList.forEach(function (select) {
-    var optionList = select.querySelectorAll('.option'),
-        selectedIndex = getIndex(select);
-
-    // カスタムコントロールがフォーカスを得られるようにします。
-    select.tabIndex = 0;
-
-    // ネイティブコントロールがフォーカスを得ないようにします。
-    select.previousElementSibling.tabIndex = -1;
-
-    // デフォルトで選択されている値が正しく表示されるようにします。
-    updateValue(select, selectedIndex);
-
-    // ユーザーが選択肢をクリックするのに応じて値を更新します。
-    optionList.forEach(function (option, index) {
-      option.addEventListener('click', function (event) {
-        updateValue(select, index);
-      });
-    });
-
-    // フォーカスがあるコントロールでユーザーがキーボードを使用するのに応じて、値を更新します。
-    select.addEventListener('keyup', function (event) {
-      var length = optionList.length,
-          index  = getIndex(select);
-
-      // ユーザーが下矢印キーを押すと、次の選択肢にジャンプします。
-      if (event.keyCode === 40 && index < length - 1) { index++; }
-
-      // ユーザーが上矢印キーを押すと、前の選択肢にジャンプします。
-      if (event.keyCode === 38 && index > 0) { index--; }
-
-      updateValue(select, index);
-    });
-  });
-});
- -

上記のコードで、tabIndex プロパティを使用していることは注目に値します。このプロパティは、ネイティブコントロールにフォーカスが当たらないようにすることと、ユーザーがキーボードやマウスを使用するとカスタムコントロールがフォーカスを得るようにするために必要です。

- -

これで完了です! 結果は以下のとおりです:

- - - - - - - - - - - - - - - -
Live example
{{EmbedLiveSample("Change_states",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_4")}}
ソースコードを確認する
- -

ちょっと待ってください、本当に終わったのでしょうか?

- -

アクセシブルにする

- -

フル機能のセレクトボックスとはかけ離れていますが動作するものはできましたし、よく動作しています。しかし、私たちが行ってきたことは DOM の操作にすぎません。これには実際のセマンティクスがなく、またセレクトボックスのように見えていてもブラウザーの視点からはそうではないため、支援技術はそれがセレクトボックスであるとは理解できません。つまり、このきれいなセレクトボックスはアクセシブルではありません!

- -

幸いなことに解決策があり、それは ARIA と呼ばれます。ARIA は "Accessible Rich Internet Application" を表し、その W3C 仕様 は私たちがここで行っていることに特化して設計されています: ウェブアプリケーションやカスタムコントロールをアクセシブルにします。これは基本的には、私たちが作り出した要素がネイティブコントロールとして通るかのように、役割や状態や特性をより説明できるようにするために HTML を拡張する属性のセットです。これらの属性の使用はとても簡単ですので、行ってみましょう。

- -

role 属性

- -

ARIA で使用される主要な属性が、role 属性です。role 属性は、要素を何に使用するかを定義する値を受け入れます。それぞれのロールは、自身の要件や動作を定義します。本記事の例では、ロール listbox を使用します。これは "composite role" であり、このロールの要素は子要素を持ち、またそれぞれの子要素も特定のロールを持ちます (この例では、ロール option の子要素が少なくとも 1 つ)。

- -

また、ARIA は標準の HTML マークアップにデフォルトで適用されるロールを定義することも特筆に値します。例えば、{{HTMLElement("table")}} 要素はロール grid に、{{HTMLElement("ul")}} 要素はロール list にマッチします。{{HTMLElement("ul")}} 要素を使用しているため、私たちのコントロールのロール listbox が、{{HTMLElement("ul")}} 要素のロール list を置き換えるようにしなければなりません。そのために、ロール presentation を使用します。このロールは要素に特別な意味はないことを示せるようにするためのものであり、単に情報を与えるために使用されます。これを {{HTMLElement("ul")}} 要素に適用します。

- -

ロール listbox をサポートするため、HTML を以下のように更新することが必要です:

- -
<!-- 最初の要素に role="listbox" 属性を追加します -->
-<div class="select" role="listbox">
-  <span class="value">Cherry</span>
-  <!-- ul 要素に role="presentation" を追加します -->
-  <ul class="optList" role="presentation">
-    <!-- すべての li 要素に role="option" 属性を追加します -->
-    <li role="option" class="option">Cherry</li>
-    <li role="option" class="option">Lemon</li>
-    <li role="option" class="option">Banana</li>
-    <li role="option" class="option">Strawberry</li>
-    <li role="option" class="option">Apple</li>
-  </ul>
-</div>
- -
-

注記: role 属性と class 属性の両方を含める方法は、CSS 属性セレクタに対応しない古いブラウザーをサポートしたい場合にのみ必要です。

-
- -

aria-selected 属性

- -

role を使用するだけでは不十分です。ARIA は、状態や特性を表す多くの属性も提供します。これらをより多くまた適切に使用すると、コントロールが支援技術にもっと良く理解されるようになります。ここでは、使用する属性を 1 つに絞ります: aria-selected です。

- -

aria-selected 属性は、どの選択肢が現在選択されているかを示すために使用します。これにより、支援技術はユーザーに現在何が選択されているかを伝えることができます。ここではユーザーが選択肢を選択するたびに、選択された選択肢を示すためにこの属性を JavaScript で動的に使用します。このために、updateValue() 関数の変更が必要です:

- -
function updateValue(select, index) {
-  var nativeWidget = select.previousElementSibling;
-  var value = select.querySelector('.value');
-  var optionList = select.querySelectorAll('.option');
-
-  // すべての選択肢が選択されていないようにします。
-  optionList.forEach(function (other) {
-    other.setAttribute('aria-selected', 'false');
-  });
-
-  // 指定された選択肢が選択されているようにします。
-  optionList[index].setAttribute('aria-selected', 'true');
-
-  nativeWidget.selectedIndex = index;
-  value.innerHTML = optionList[index].innerHTML;
-  highlightOption(select, optionList[index]);
-};
- -

スクリーンリーダーにオフスクリーンselectに焦点をあてて他のスタイルを無視するようにした法が簡単に見えますが、これはアクセシブルな解決策ではありません。スクリーンリーダーは盲目の人だけのものではありません。低視力や、完全な視力の人もこれを使います。このため、スクリーンリーダーをオフスクリーン要素だけに焦点をあてるようにはできません。

- -

以下がこれらの変更を施した最終結果です (NVDAVoiceOver などの支援技術でコントロールを使用してみても、よい感触を得られるでしょう):

- - - - - - - - - - - - - - - -
Live example
{{EmbedLiveSample("Change_states",120,130, "", "HTML/Forms/How_to_build_custom_form_controls/Example_5")}}
ソースコードを確認する
- -

もっと先に進むには、この例でのコードは汎用性や再利用性に改善が必要です。これは課題として挑戦できます。この2つのヒントを挙げると:すべての関数で最初の引数は同じで、つまりこれらの関数は同じコンテキストが必要です。そのコンテキストを共有するオブジェクトを作るのが賢明です。

- -

他の方法: ラジオボタンを使う

- -

上の例では、非動的なHTML、CSS、JavaScript を使って {{htmlelement('select')}} 要素を再発明しました。このselectは限られた選択肢から1つが選択され、それは {{htmlelement('input/radio', 'radio')}} ボタンのグループと同様な機能です。

- -

このため、代わりにラジオボタンを使って再発明できます。このオプションを見てみましょう。

- -

We can start with

- -

完全に意味のある、アクセシブルで、順序のない、関連する{{htmlelement('label')}}つきの{{htmlelement('input/radio','radio')}} ボタンのリストから始めます、グループ全体を適切な意味のある{{htmlelement('fieldset')}} と{{htmlelement('legend')}} のペアにラベルづけします。.

- -
 <fieldset>
-  <legend>Pick a fruit</legend>
-    <ul class="styledSelect">
-      <li><input type="radio" name="fruit" value="Cherry" id="fruitCherry" checked><label for="fruitCherry">Cherry</label></li>
-      <li><input type="radio" name="fruit" value="Lemon" id="fruitLemon"><label for="fruitLemon">Lemon</label></li>
-      <li><input type="radio" name="fruit" value="Banana" id="fruitBanana"><label for="fruitBanana"">Banana</label></li>
-      <li><input type="radio" name="fruit" value="Strawberry" id="fruitStrawberry"><label for="fruitStrawberry">Strawberry</label></li>
-      <li><input type="radio" name="fruit" value="Apple" id="fruitApple"><label for="fruitApple">Apple</label></li>
-    </ul>
-  </fieldset>
- -

(legend/fieldsetではなく)ラジオボタンリストに少しスタイルづけをして、前の例と同じ見た目にし、完了したことがわかるようにします:

- -
.styledSelect {
-  display: inline-block;
-  padding: 0;
-}
-.styledSelect li {
-  list-style-type: none;
-  padding: 0;
-  display: flex;
-}
-.styledSelect [type=radio] {
-  position: absolute;
-  left: -100vw;
-  top: -100vh;
-}
-.styledSelect label {
-  margin: 0;
-  line-height: 2;
-  padding: 0 0 0 4px;
-}
-.styledSelect:not(:focus-within) input:not(:checked) + label {
-  height: 0;
-  outline: none;
-  overflow: hidden;
-}
-.styledSelect:not(:focus-within) input:checked + label {
-  border: .2em solid #000;
-  border-radius: .4em;
-  box-shadow: 0 .1em .2em rgba(0,0,0,.45);
-}
-.styledSelect:not(:focus-within) input:checked + label::after {
-  content : "▼";
-  background: black;
-  float: right;
-  color: white;
-  padding: 0 4px;
-  margin: 0 -4px 0 4px;
-}
-.styledSelect:focus-within {
-  border: .2em solid #000;
-  border-radius: .4em;
-  box-shadow: 0 .1em .2em rgba(0,0,0,.45);
-}
-.styledSelect:focus-within input:checked + label {
-  background-color: #333;
-  color: #fff;
-  width: 100%;
-}
- -

JavaScriptなしで少しの CSSにて、ラジオボタンのリストをスタイルづけしてチェック済み項目のみを表示することができます。フォーカスが<fieldset>内の <ul> に来ると、リストは開いて、上下左右の矢印が前後の項目を選択するのに使えます。次で試してください:

- -

{{EmbedLiveSample("An_alternative_approach_Using_radio_buttons",200,240)}}

- -

これはある程度、JavaScriptなしで動作します。JavaScript が失敗しても動作する、われわれのカスタムコントロールど同じものを作ってきました。よい解決策でしょう?これはキーボードでは動作しますが、マウスクリックではそうなりません。ネイティブな意味づけのない要素を作るフレームワークに依存する代わりに、ウェブ標準をカスタムコントロールの基礎として使った方が意味があります。しかし、われわれのコントロールは <select> が自ずと持つ機能と同じものを備えていません。

- -

いい面として、このコントロールはスクリーンリーダーにとって完全にアクセシブルでキーボードで完全に操作できます。しかし、このコントロールは {{htmlelement('select')}} 要素の置き換えではありません。異なる機能や足りない機能があります。例えば、4つの矢印は選択肢を操作できますが、最後のボタンで下を押すと最初のボタンに移動します。<select> のように上端、下端で止まりません。

- -

この足りない機能の追加は、読者の課題としておきます。

- -

まとめ

- -

独自のフォームコントロールの作成方法を見てきましたが、ご覧いただいたようにこれは容易なことではありません。独自のカスタムコントロールを作る前に、HTMLに要求を十分に満たす代替要素がないかを検討してください。本当にカスタムコントロールを作成する必要がある場合、サードパーティのライブラリに頼るほうが簡単かつよいことも少なくありません。独自作成する場合、既存の要素を編集するか、準備されたコントロールを実装するフレームワークを使うようにして、実用的でアクセシブルなフォームコントロールの作成は見た目より複雑であることを忘れないでください。

- -

自分でコーディングする前に検討するとよいライブラリをいくつか紹介します:

- - - -

ラジオボタン、独自JavaScript 、またはサードパーティライブラリで代替コントロールを作る場合、アクセシブルかつ機能への耐性を高めましょう。すなわち Web 標準の実装状況がまちまちである、多様なブラウザーで良好に動作できるようにすることが必要です。楽しんでください!

- -

このモジュール

- -

学習コース

- - - -

上級トピック

- - diff --git a/files/ja/learn/forms/how_to_structure_a_web_form/example/index.html b/files/ja/learn/forms/how_to_structure_a_web_form/example/index.html new file mode 100644 index 0000000000..2c97485087 --- /dev/null +++ b/files/ja/learn/forms/how_to_structure_a_web_form/example/index.html @@ -0,0 +1,164 @@ +--- +title: '例: お支払いフォーム' +slug: Learn/Forms/How_to_structure_an_HTML_form/Example +translation_of: Learn/Forms/How_to_structure_a_web_form/Example +--- +

これは記事 HTML フォームの構築方法の基本的なお支払いフォームの例です。

+ +

お支払いフォーム

+ +

HTML コンテンツ

+ +
<form>
+        <h1>Payment form</h1>
+        <p>Required fields are followed by <strong><abbr title="required">*</abbr></strong>.</p>
+        <section>
+            <h2>Contact information</h2>
+            <fieldset>
+              <legend>Title</legend>
+              <ul>
+                  <li>
+                    <label for="title_1">
+                      <input type="radio" id="title_1" name="title" value="M." >
+                      Mister
+                    </label>
+                  </li>
+                  <li>
+                    <label for="title_2">
+                      <input type="radio" id="title_2" name="title" value="Ms.">
+                      Miss
+                    </label>
+                  </li>
+              </ul>
+            </fieldset>
+            <p>
+              <label for="name">
+                <span>Name: </span>
+                <strong><abbr title="required">*</abbr></strong>
+              </label>
+              <input type="text" id="name" name="username">
+            </p>
+            <p>
+              <label for="mail">
+                <span>E-mail: </span>
+                <strong><abbr title="required">*</abbr></strong>
+              </label>
+              <input type="email" id="mail" name="usermail">
+            </p>
+            <p>
+              <label for="password">
+                <span>Password: </span>
+                <strong><abbr title="required">*</abbr></strong>
+              </label>
+              <input type="password" id="pwd" name="password">
+            </p>
+        </section>
+        <section>
+            <h2>Payment information</h2>
+            <p>
+              <label for="card">
+                <span>Card type:</span>
+              </label>
+              <select id="card" name="usercard">
+                <option value="visa">Visa</option>
+                <option value="mc">Mastercard</option>
+                <option value="amex">American Express</option>
+              </select>
+            </p>
+            <p>
+              <label for="number">
+                <span>Card number:</span>
+                <strong><abbr title="required">*</abbr></strong>
+              </label>
+                <input type="number" id="number" name="cardnumber">
+            </p>
+            <p>
+              <label for="date">
+                <span>Expiration date:</span>
+                <strong><abbr title="required">*</abbr></strong>
+                <em>formatted as mm/yy</em>
+              </label>
+              <input type="date" id="date" name="expiration">
+            </p>
+        </section>
+        <section>
+            <p> <button type="submit">Validate the payment</button> </p>
+        </section>
+    </form>
+ +

CSS コンテンツ

+ +
      h1 {
+          margin-top: 0;
+      }
+
+      ul {
+          margin: 0;
+          padding: 0;
+          list-style: none;
+      }
+
+      form {
+          margin: 0 auto;
+          width: 400px;
+          padding: 1em;
+          border: 1px solid #CCC;
+          border-radius: 1em;
+      }
+
+      div+div {
+          margin-top: 1em;
+      }
+
+      label span {
+          display: inline-block;
+          width: 120px;
+          text-align: right;
+      }
+
+      input, textarea {
+          font: 1em sans-serif;
+          width: 250px;
+          box-sizing: border-box;
+          border: 1px solid #999;
+      }
+
+      input[type=checkbox], input[type=radio] {
+          width: auto;
+          border: none;
+      }
+
+      input:focus, textarea:focus {
+          border-color: #000;
+      }
+
+      textarea {
+          vertical-align: top;
+          height: 5em;
+          resize: vertical;
+      }
+
+      fieldset {
+          width: 250px;
+          box-sizing: border-box;
+          margin-left: 136px;
+          border: 1px solid #999;
+      }
+
+      button {
+          margin: 20px 0 0 124px;
+      }
+
+      label {
+        position: relative;
+      }
+
+      label em {
+        position: absolute;
+        right: 5px;
+        top: 20px;
+      }
+ +

結果

+ +

{{ EmbedLiveSample('A_payment_form', '100%', 620) }}

diff --git a/files/ja/learn/forms/how_to_structure_a_web_form/index.html b/files/ja/learn/forms/how_to_structure_a_web_form/index.html new file mode 100644 index 0000000000..ca3865643f --- /dev/null +++ b/files/ja/learn/forms/how_to_structure_a_web_form/index.html @@ -0,0 +1,329 @@ +--- +title: フォームの構築方法 +slug: Learn/Forms/How_to_structure_an_HTML_form +tags: + - CodingScripting + - HTML + - Web + - ガイド + - フォーム + - 例 + - 初心者 + - 学習 + - 構造 +translation_of: Learn/Forms/How_to_structure_a_web_form +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Forms/Your_first_form", "Learn/Forms/Basic_native_form_controls", "Learn/Forms")}}
+ +

基本から外れて、ここでは色々なフォームのパーツを構造化し、意味をつけるのに使われる要素を詳しく見ていきます。

+ + + + + + + + + + + + +
前提条件:基本的なコンピューターリテラシーと、HTML の基本的な理解
目的:HTML フォームを構造化して意味を与えて使いやすくアクセシブルにする方法を理解すること。
+ +

フォームは柔軟性があるため、HTML で最も複雑な構造の 1 つとなっています。専用のフォーム要素と属性を使用して、あらゆる種類の基本フォームを作成できます。HTML フォームを構築するときに正しい構造を使用すると、フォームが使用可能でアクセスしやすいことを確実にするのに役立ちます。

+ +

<form> 要素

+ +

{{HTMLElement("form")}} 要素はフォームを正式に定義するとともに、自身の属性でフォームの動作を定義します。HTML フォームを作成しようとするたびに、この要素から始めて、すべてのコンテンツをその中に入れなければなりません。多くの支援技術やブラウザープラグインは {{HTMLElement("form")}} 要素を検出でき、またフォームを使いやすくするための特別なフックを実装できます。

+ +

前の記事ですでにこれを見ています。

+ +
警告: フォームの中にフォームを入れ子にすることは厳格に禁じられています。これは予期せぬ動作を発生するおそれがあるので、悪い方針です。
+ +

{{HTMLElement("form")}} 要素の外部でもフォームウィジェットを使用できますが、そのフォームウィジェットは form 属性を用いて関連付けなければ、どのフォームに対しても何も行わないことに注意してください。これは、実際には {{ HTMLElement("form") }} に包含されていない要素であっても明示的にフォームへ紐付けることを可能にします。

+ +

  次に、フォームに含まれる構造的な要素を見ていきましょう。

+ +

<fieldset> および <legend> 要素

+ +

{{HTMLElement("fieldset")}} 要素は、スタイルや意味付けのために、同じ目的を持つウィジェットのグループの作成に便利です。{{HTMLElement("fieldset")}} 要素は、<fieldset> タグのすぐ下に {{HTMLElement("legend")}} 要素を入れてラベルを付与できます。{{HTMLElement("legend")}} 要素は、{{HTMLElement("fieldset")}} 要素の目的を正式に説明します。

+ +

多くの支援技術は {{HTMLElement("legend")}} 要素を、対応する {{HTMLElement("fieldset")}} 要素内にある各ウィジェットのラベルの一部であるかのように扱うでしょう。例えば JawsNVDA といったスクリーンリーダーは、各ウィジェットのラベルを読み上げる前に legend の内容を読み上げます。

+ +

以下に小さなサンプルを挙げます:

+ +
<form>
+  <fieldset>
+    <legend>Fruit juice size</legend>
+    <p>
+      <input type="radio" name="size" id="size_1" value="small">
+      <label for="size_1">Small</label>
+    </p>
+    <p>
+      <input type="radio" name="size" id="size_2" value="medium">
+      <label for="size_2">Medium</label>
+    </p>
+    <p>
+      <input type="radio" name="size" id="size_3" value="large">
+      <label for="size_3">Large</label>
+    </p>
+  </fieldset>
+</form>
+ +
+

: この例は fieldset-legend.html で見ることができます(ライブ版も見てください)。

+
+ +

この例では、スクリーンリーダーは最初のウィジェットを "Fruit juice size small"、2 番目を "Fruit juice size medium"、3 番目を "Fruit juice size large" と読み上げるでしょう。

+ +

このサンプルでのユースケースは、もっとも重要なことのひとつです。ラジオボタンのセットを置くたびに、それらを {{HTMLElement("fieldset")}} 要素内へ入れ子にするようにしましょう。ユースケースは他にもあり、通常は {{HTMLElement("fieldset")}} 要素でフォームを明確に区分するために使用できます。理想的には長いフォームは複数ページに分けるべきですが、フォームが長くなっても1つのページに収めないといけない場合、別々の関連セクションを別々の fieldset に入れることは使いやすさを改善します。

+ +

支援技術への影響力により、{{HTMLElement("fieldset")}} 要素はアクセシブルなフォームを作成するために重要な要素のひとつです。しかし、それを誤用しないようにするのはあなたの責務です。できれば、フォームを作成するたびにスクリーンリーダーがどのように解釈するか聞いてみましょう。変に聞こえるのであれば、フォームの構造を改善するためのよいヒントになります。

+ +

<label> 要素

+ +

これまでの記事で見てきたように、{{HTMLElement("label")}} 要素は、HTML フォームウィジェットのラベルを定義する正式な方法です。これは、アクセシブルなフォームを作成したい場合にもっとも重要な要素です — 適切に実装された時は、スクリーンリーダーはフォーム要素のラベルと関連する指示を一緒に読み上げます。前の記事で見てきたこの例を見てみます:

+ +
<label for="name">Name:</label> <input type="text" id="name" name="user_name">
+ +

<label><input> とがそれぞれ forid 属性により正し関連付けられると (label の for 属性は対応するウィジェットの id 属性を参照します)、スクリーンリーダーは "Name, edit text"のように読み上げます。

+ +

フォームコントロールとラベルを関連付けるもう 1 つの方法は、フォームコントロールを <label> の中でネストすることで、暗黙的に関連付けることです。

+ +
<label for="name">
+  Name: <input type="text" id="name" name="user_name">
+</label>
+ +

この場合でも for 属性を設定することがベストプラクティスと考えられています。これは、ラベルとウィジェットの暗黙的な関係を理解できない支援技術があるためです。

+ +

ラベルがなかったり、フォームコントロールが明示的/暗黙にラベルに関連付けられていない場合、スクリーンリーダーは全く役立たない "Edit text blank" のような読み上げを行います。

+ +

ラベルもクリック可能です!

+ +

ラベルをセットアップするもう 1 つの利点は、ユーザーがラベルをクリックするとウィジェットをアクティブにすることが、あらゆるブラウザーで可能になります。これは例えば、テキスト入力で、入力と同様にラベルをクリックしてフォーカスさせることができますし、ラジオボタンやチェックボックスで特に有用です — このコントロールのヒットエリアはとても小さく、できるだけ大きくしておくのは便利です。

+ +

例えば、次の例で "I like cherry" の テキストをクリックすると選択された taste_cherry チェックボックスの状態が切り替わります:

+ +
<form>
+  <p>
+    <input type="checkbox" id="taste_1" name="taste_cherry" value="cherry">
+    <label for="taste_1">I like cherry</label>
+  </p>
+  <p>
+    <input type="checkbox" id="taste_2" name="taste_banana" value="banana">
+    <label for="taste_2">I like banana</label>
+  </p>
+</form>
+ +
+

: この例は checkbox-label.html で見ることができます(ライブ版も見てください)。

+
+ +

複数のラベル

+ +

厳密に言うと、1 つのウィジェット内に複数のラベルを入れることができますが、複数のラベルを持つウィジェットの扱いに問題がある支援技術があるかもしれません。複数のラベルがある場合、アクセシブルなフォームを作成するには1つの {{htmlelement("label")}} 要素内にウィジェットを入れ子にするとよいでしょう。

+ +

以下のサンプルについて考えてみましょう:

+ +
<p>Required fields are followed by <abbr title="required">*</abbr>.</p>
+
+<!-- 2 つの例をご覧ください: -->
+<div>
+  <label for="username">Name:</label>
+  <input id="username" type="text" name="username">
+  <label for="username"><abbr title="required" aria-label="required">*</abbr></label>
+</div>
+
+<!-- 前出の例よりは良いです: -->
+<div>
+  <label for="username">
+    <span>Name: </span>
+    <input id="username" type="text" name="username">
+    <abbr title="required" aria-label="required">*</abbr>
+  </label>
+</div>
+
+<!-- これが最も良いでしょう: -->
+<div>
+  <label for="username">Name: <abbr title="required" aria-label="required">*</abbr></label>
+  <input id="username" type="text" name="username">
+</div>
+ +

{{EmbedLiveSample("Multiple_labels", 120, 120)}}

+ +

このサンプルでは、最初の段落で入力必須の要素の規則を定義しています。ユーザーが入力必須の要素を見つける前にスクリーンリーダーのような支援技術が注意事項を表示したり読み上げたりするためには、規則をはじめに置かなければなりません。これがユーザーにアスタリスクの意味を知らせても、それに依存することはできません。スクリーンリーダーはアスタリスクが出てくると "スター" と読み上げます。視力のあるユーザーがマウスを持ってくると、title 属性によって"必須"と表示されます。タイトルはスクリーンリーダーの設定により読み上げられるので、常にスクリーンリーダーに読み上げられる aria-label 属性を入れておくのがより信頼性が高いでしょう。

+ +

上記の違いをふまえると、以降を効率よく見ていけるでしょう:

+ + + +
+

: スクリーンリーダーによっては、少し異なる結果になる場合もあります。これは VoiceOver (と同様に動作する NVDA)でテストしています。あなたの体験を聞きたいです。

+
+ +
+

: この例は GitHub の required-labels.html で見ることができます(ライブ版も見てください)。2 や 3 のコメントを外したバージョンの例を実行しないでください — 複数の label と複数の同じ input ID があると、スクリーンリーダーは確実に混乱します!

+
+ +

フォームで使用される一般的な HTML 構造

+ +

ウェブフォーム特有の構造の前提として、フォームは単に HTML であると覚えておくとよいでしょう。つまり、ウェブフォームを組み立てるために HTML のすべての力を利用できるのです。

+ +

サンプルでわかるように、ラベルとそのウィジェットを {{HTMLElement("ul")}} や {{HTMLElement("ol")}} リストの中の {{HTMLElement("li")}} 要素で包み込むのが一般的な慣習です。HTML リストにあるように、{{HTMLElement("p")}} 要素と {{HTMLElement("div")}} 要素も良く使われます。リストは複数のチェックボックスやラジオボタンを構造化するのに最もよく使われます。

+ +

{{HTMLElement("fieldset")}} 要素に加えて、複雑なフォームの構築に HTML の見出し (例{{htmlelement("h1")}}, {{htmlelement("h2")}}) やセクション (例 {{htmlelement("section")}}) を使うことも一般的です。

+ +

とりわけ、コーディングスタイルがどうあるのが心地よく、どれがアクセシブルで使いやすいフォームとなるのかを見つけるのはあなた次第です。別の機能セクションは別の {{htmlelement("section")}} 要素と、ラジオボタンを含む {{htmlelement("fieldset")}} にそれぞれ分けておくべきです。

+ +

アクティブラーニング: フォーム構造を構築する

+ +

これらのアイデアを実践し、もう少し複雑なフォーム構造、つまり支払いフォームを作成しましょう。このフォームはあなたがまだ理解していないかもしれないウィジェットタイプをいくつも含みますが、今はそのことを心配しないでください。次の記事 (ネイティブフォームウィジェット) でそれらがどのように機能するのかがわかります。今のところ、以下の説明に沿って説明を注意深く読み、フォームを構成するためにどのラッパー要素を使用しているか、そしてその理由を理解することから始めてください。

+ +
    +
  1. あらかじめ、空のテンプレートファイルお支払いフォームの CSS のローカルコピーをコンピューターの新しいディレクトリーに作成します。
  2. +
  3. まず最初に、HTML {{htmlelement("head")}} 内に次の行を追加して CSS を HTML に適用します。 +
    <link href="payment-form.css" rel="stylesheet">
    +
  4. +
  5. 次に、外側の {{htmlelement("form")}} 要素を追加してフォームを作成します: +
    <form>
    +
    +</form>
    +
  6. +
  7. <form> タグ内に、必須フィールドにマークを付ける方法をユーザーに通知するための見出しと段落を追加します: +
    <h1>Payment form</h1>
    +<p>Required fields are followed by <strong><abbr title="required">*</abbr></strong>.</p>
    +
  8. +
  9. 次に、前のエントリーの下に、より大きなコードセクションをフォームに追加します。ここでは、連絡先情報フィールドを個別の {{htmlelement("section")}} 要素内にラップしていることがわかります。さらに、2 つのラジオボタンのセットがあり、それぞれ独自のリスト ({{htmlelement("li")}}) 要素の中に入れています。最後に、2 つの標準テキスト {{htmlelement("input")}} とそれに関連する {{htmlelement("label")}} 要素があり、それぞれ {{htmlelement("p")}} の内側に含まれていて、パスワードを入力するためのパスワード入力があります。フォームにこのコードを追加してください: +
    <section>
    +    <h2>Contact information</h2>
    +    <fieldset>
    +      <legend>Title</legend>
    +      <ul>
    +          <li>
    +            <label for="title_1">
    +              <input type="radio" id="title_1" name="title" value="K" >
    +              King
    +            </label>
    +          </li>
    +          <li>
    +            <label for="title_2">
    +              <input type="radio" id="title_2" name="title" value="Q">
    +              Queen
    +            </label>
    +          </li>
    +          <li>
    +            <label for="title_3">
    +              <input type="radio" id="title_3" name="title" value="J">
    +              Joker
    +            </label>
    +          </li>
    +      </ul>
    +    </fieldset>
    +    <p>
    +      <label for="name">
    +        <span>Name: </span>
    +        <strong><abbr title="required">*</abbr></strong>
    +      </label>
    +      <input type="text" id="name" name="username">
    +    </p>
    +    <p>
    +      <label for="mail">
    +        <span>E-mail: </span>
    +        <strong><abbr title="required">*</abbr></strong>
    +      </label>
    +      <input type="email" id="mail" name="usermail">
    +    </p>
    +    <p>
    +      <label for="pwd">
    +        <span>Password: </span>
    +        <strong><abbr title="required">*</abbr></strong>
    +      </label>
    +      <input type="password" id="pwd" name="password">
    +    </p>
    +</section>
    +
  10. +
  11. それでは、フォームの 2 番目の <section> — 支払い情報に目を向けます。ここには 3 つの異なるウィジェットとそのラベルがあり、それぞれ <p> の中に含まれています。1 つ目は、クレジットカードの種類を選択するためのドロップダウンメニュー ({{htmlelement("select")}}) です。2 番目は、クレジットカード番号を入力するための tel 型の <input> 要素です。number 型を使うこともできますが、そのスピナーUIは望ましくありません。最後のものは、カードの有効期限を入力するための date 型の <input> 要素です。これは、サポートしているブラウザーでは日付選択ウィジェットが表示され、サポートしていないブラウザーでは通常のテキスト入力に戻ります。新しい入力タイプは HTML5 入力タイプで再度紹介されます。
    +
    + 前のセクションの下に次のように入力してください。 +
    <section>
    +    <h2>Payment information</h2>
    +    <p>
    +      <label for="card">
    +        <span>Card type:</span>
    +      </label>
    +      <select id="card" name="usercard">
    +        <option value="visa">Visa</option>
    +        <option value="mc">Mastercard</option>
    +        <option value="amex">American Express</option>
    +      </select>
    +    </p>
    +    <p>
    +      <label for="number">
    +        <span>Card number:</span>
    +        <strong><abbr title="required">*</abbr></strong>
    +      </label>
    +      <input type="tel" id="number" name="cardnumber">
    +    </p>
    +    <p>
    +      <label for="date">
    +        <span>Expiration date:</span>
    +        <strong><abbr title="required">*</abbr></strong>
    +        <em>formatted as mm/dd/yyyy</em>
    +      </label>
    +      <input type="date" id="date" name="expiration">
    +    </p>
    +</section>
    +
  12. +
  13. 最後に追加するセクションはもっと単純で、フォームデータを送信するための submit タイプの {{htmlelement("button")}} のみを含みます。これをフォームの一番下に追加してください: +
    <p> <button type="submit">Validate the payment</button> </p>
    +
  14. +
+ +

完成したフォームは以下のように動作しています (GitHub でも確認できます。payment-form.html ソースを参照してライブ実行してください)。

+ +

{{EmbedLiveSample("A_payment_form","100%",620, "", "Learn/HTML/Forms/How_to_structure_an_HTML_form/Example")}}

+ +

あなたのスキルをテストしてみましょう!

+ +

この記事はここまでですが、最も重要な情報を覚えていますか? 先に進む前に、この情報を保持しているかどうかを確認するためのテストがあります — Test your skills: Form structure を参照してください。

+ +

まとめ

+ +

ウェブフォームを適切に構築するためのあらゆる知識を得ることができました。これからここで出てきた機能を見ていき、次の記事では、ユーザーから情報を集めるのに使いたくなるすべての種類のフォームウィジェットの詳細な実装について詳しく見ていきます。

+ +

関連情報

+ + + +

{{PreviousMenuNext("Learn/HTML/Forms/Your_first_HTML_form", "Learn/HTML/Forms/The_native_form_widgets", "Learn/HTML/Forms")}}

+ +

このモジュール

+ + + +

上級トピック

+ + diff --git a/files/ja/learn/forms/how_to_structure_an_html_form/example/index.html b/files/ja/learn/forms/how_to_structure_an_html_form/example/index.html deleted file mode 100644 index 2c97485087..0000000000 --- a/files/ja/learn/forms/how_to_structure_an_html_form/example/index.html +++ /dev/null @@ -1,164 +0,0 @@ ---- -title: '例: お支払いフォーム' -slug: Learn/Forms/How_to_structure_an_HTML_form/Example -translation_of: Learn/Forms/How_to_structure_a_web_form/Example ---- -

これは記事 HTML フォームの構築方法の基本的なお支払いフォームの例です。

- -

お支払いフォーム

- -

HTML コンテンツ

- -
<form>
-        <h1>Payment form</h1>
-        <p>Required fields are followed by <strong><abbr title="required">*</abbr></strong>.</p>
-        <section>
-            <h2>Contact information</h2>
-            <fieldset>
-              <legend>Title</legend>
-              <ul>
-                  <li>
-                    <label for="title_1">
-                      <input type="radio" id="title_1" name="title" value="M." >
-                      Mister
-                    </label>
-                  </li>
-                  <li>
-                    <label for="title_2">
-                      <input type="radio" id="title_2" name="title" value="Ms.">
-                      Miss
-                    </label>
-                  </li>
-              </ul>
-            </fieldset>
-            <p>
-              <label for="name">
-                <span>Name: </span>
-                <strong><abbr title="required">*</abbr></strong>
-              </label>
-              <input type="text" id="name" name="username">
-            </p>
-            <p>
-              <label for="mail">
-                <span>E-mail: </span>
-                <strong><abbr title="required">*</abbr></strong>
-              </label>
-              <input type="email" id="mail" name="usermail">
-            </p>
-            <p>
-              <label for="password">
-                <span>Password: </span>
-                <strong><abbr title="required">*</abbr></strong>
-              </label>
-              <input type="password" id="pwd" name="password">
-            </p>
-        </section>
-        <section>
-            <h2>Payment information</h2>
-            <p>
-              <label for="card">
-                <span>Card type:</span>
-              </label>
-              <select id="card" name="usercard">
-                <option value="visa">Visa</option>
-                <option value="mc">Mastercard</option>
-                <option value="amex">American Express</option>
-              </select>
-            </p>
-            <p>
-              <label for="number">
-                <span>Card number:</span>
-                <strong><abbr title="required">*</abbr></strong>
-              </label>
-                <input type="number" id="number" name="cardnumber">
-            </p>
-            <p>
-              <label for="date">
-                <span>Expiration date:</span>
-                <strong><abbr title="required">*</abbr></strong>
-                <em>formatted as mm/yy</em>
-              </label>
-              <input type="date" id="date" name="expiration">
-            </p>
-        </section>
-        <section>
-            <p> <button type="submit">Validate the payment</button> </p>
-        </section>
-    </form>
- -

CSS コンテンツ

- -
      h1 {
-          margin-top: 0;
-      }
-
-      ul {
-          margin: 0;
-          padding: 0;
-          list-style: none;
-      }
-
-      form {
-          margin: 0 auto;
-          width: 400px;
-          padding: 1em;
-          border: 1px solid #CCC;
-          border-radius: 1em;
-      }
-
-      div+div {
-          margin-top: 1em;
-      }
-
-      label span {
-          display: inline-block;
-          width: 120px;
-          text-align: right;
-      }
-
-      input, textarea {
-          font: 1em sans-serif;
-          width: 250px;
-          box-sizing: border-box;
-          border: 1px solid #999;
-      }
-
-      input[type=checkbox], input[type=radio] {
-          width: auto;
-          border: none;
-      }
-
-      input:focus, textarea:focus {
-          border-color: #000;
-      }
-
-      textarea {
-          vertical-align: top;
-          height: 5em;
-          resize: vertical;
-      }
-
-      fieldset {
-          width: 250px;
-          box-sizing: border-box;
-          margin-left: 136px;
-          border: 1px solid #999;
-      }
-
-      button {
-          margin: 20px 0 0 124px;
-      }
-
-      label {
-        position: relative;
-      }
-
-      label em {
-        position: absolute;
-        right: 5px;
-        top: 20px;
-      }
- -

結果

- -

{{ EmbedLiveSample('A_payment_form', '100%', 620) }}

diff --git a/files/ja/learn/forms/how_to_structure_an_html_form/index.html b/files/ja/learn/forms/how_to_structure_an_html_form/index.html deleted file mode 100644 index ca3865643f..0000000000 --- a/files/ja/learn/forms/how_to_structure_an_html_form/index.html +++ /dev/null @@ -1,329 +0,0 @@ ---- -title: フォームの構築方法 -slug: Learn/Forms/How_to_structure_an_HTML_form -tags: - - CodingScripting - - HTML - - Web - - ガイド - - フォーム - - 例 - - 初心者 - - 学習 - - 構造 -translation_of: Learn/Forms/How_to_structure_a_web_form ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/Forms/Your_first_form", "Learn/Forms/Basic_native_form_controls", "Learn/Forms")}}
- -

基本から外れて、ここでは色々なフォームのパーツを構造化し、意味をつけるのに使われる要素を詳しく見ていきます。

- - - - - - - - - - - - -
前提条件:基本的なコンピューターリテラシーと、HTML の基本的な理解
目的:HTML フォームを構造化して意味を与えて使いやすくアクセシブルにする方法を理解すること。
- -

フォームは柔軟性があるため、HTML で最も複雑な構造の 1 つとなっています。専用のフォーム要素と属性を使用して、あらゆる種類の基本フォームを作成できます。HTML フォームを構築するときに正しい構造を使用すると、フォームが使用可能でアクセスしやすいことを確実にするのに役立ちます。

- -

<form> 要素

- -

{{HTMLElement("form")}} 要素はフォームを正式に定義するとともに、自身の属性でフォームの動作を定義します。HTML フォームを作成しようとするたびに、この要素から始めて、すべてのコンテンツをその中に入れなければなりません。多くの支援技術やブラウザープラグインは {{HTMLElement("form")}} 要素を検出でき、またフォームを使いやすくするための特別なフックを実装できます。

- -

前の記事ですでにこれを見ています。

- -
警告: フォームの中にフォームを入れ子にすることは厳格に禁じられています。これは予期せぬ動作を発生するおそれがあるので、悪い方針です。
- -

{{HTMLElement("form")}} 要素の外部でもフォームウィジェットを使用できますが、そのフォームウィジェットは form 属性を用いて関連付けなければ、どのフォームに対しても何も行わないことに注意してください。これは、実際には {{ HTMLElement("form") }} に包含されていない要素であっても明示的にフォームへ紐付けることを可能にします。

- -

  次に、フォームに含まれる構造的な要素を見ていきましょう。

- -

<fieldset> および <legend> 要素

- -

{{HTMLElement("fieldset")}} 要素は、スタイルや意味付けのために、同じ目的を持つウィジェットのグループの作成に便利です。{{HTMLElement("fieldset")}} 要素は、<fieldset> タグのすぐ下に {{HTMLElement("legend")}} 要素を入れてラベルを付与できます。{{HTMLElement("legend")}} 要素は、{{HTMLElement("fieldset")}} 要素の目的を正式に説明します。

- -

多くの支援技術は {{HTMLElement("legend")}} 要素を、対応する {{HTMLElement("fieldset")}} 要素内にある各ウィジェットのラベルの一部であるかのように扱うでしょう。例えば JawsNVDA といったスクリーンリーダーは、各ウィジェットのラベルを読み上げる前に legend の内容を読み上げます。

- -

以下に小さなサンプルを挙げます:

- -
<form>
-  <fieldset>
-    <legend>Fruit juice size</legend>
-    <p>
-      <input type="radio" name="size" id="size_1" value="small">
-      <label for="size_1">Small</label>
-    </p>
-    <p>
-      <input type="radio" name="size" id="size_2" value="medium">
-      <label for="size_2">Medium</label>
-    </p>
-    <p>
-      <input type="radio" name="size" id="size_3" value="large">
-      <label for="size_3">Large</label>
-    </p>
-  </fieldset>
-</form>
- -
-

: この例は fieldset-legend.html で見ることができます(ライブ版も見てください)。

-
- -

この例では、スクリーンリーダーは最初のウィジェットを "Fruit juice size small"、2 番目を "Fruit juice size medium"、3 番目を "Fruit juice size large" と読み上げるでしょう。

- -

このサンプルでのユースケースは、もっとも重要なことのひとつです。ラジオボタンのセットを置くたびに、それらを {{HTMLElement("fieldset")}} 要素内へ入れ子にするようにしましょう。ユースケースは他にもあり、通常は {{HTMLElement("fieldset")}} 要素でフォームを明確に区分するために使用できます。理想的には長いフォームは複数ページに分けるべきですが、フォームが長くなっても1つのページに収めないといけない場合、別々の関連セクションを別々の fieldset に入れることは使いやすさを改善します。

- -

支援技術への影響力により、{{HTMLElement("fieldset")}} 要素はアクセシブルなフォームを作成するために重要な要素のひとつです。しかし、それを誤用しないようにするのはあなたの責務です。できれば、フォームを作成するたびにスクリーンリーダーがどのように解釈するか聞いてみましょう。変に聞こえるのであれば、フォームの構造を改善するためのよいヒントになります。

- -

<label> 要素

- -

これまでの記事で見てきたように、{{HTMLElement("label")}} 要素は、HTML フォームウィジェットのラベルを定義する正式な方法です。これは、アクセシブルなフォームを作成したい場合にもっとも重要な要素です — 適切に実装された時は、スクリーンリーダーはフォーム要素のラベルと関連する指示を一緒に読み上げます。前の記事で見てきたこの例を見てみます:

- -
<label for="name">Name:</label> <input type="text" id="name" name="user_name">
- -

<label><input> とがそれぞれ forid 属性により正し関連付けられると (label の for 属性は対応するウィジェットの id 属性を参照します)、スクリーンリーダーは "Name, edit text"のように読み上げます。

- -

フォームコントロールとラベルを関連付けるもう 1 つの方法は、フォームコントロールを <label> の中でネストすることで、暗黙的に関連付けることです。

- -
<label for="name">
-  Name: <input type="text" id="name" name="user_name">
-</label>
- -

この場合でも for 属性を設定することがベストプラクティスと考えられています。これは、ラベルとウィジェットの暗黙的な関係を理解できない支援技術があるためです。

- -

ラベルがなかったり、フォームコントロールが明示的/暗黙にラベルに関連付けられていない場合、スクリーンリーダーは全く役立たない "Edit text blank" のような読み上げを行います。

- -

ラベルもクリック可能です!

- -

ラベルをセットアップするもう 1 つの利点は、ユーザーがラベルをクリックするとウィジェットをアクティブにすることが、あらゆるブラウザーで可能になります。これは例えば、テキスト入力で、入力と同様にラベルをクリックしてフォーカスさせることができますし、ラジオボタンやチェックボックスで特に有用です — このコントロールのヒットエリアはとても小さく、できるだけ大きくしておくのは便利です。

- -

例えば、次の例で "I like cherry" の テキストをクリックすると選択された taste_cherry チェックボックスの状態が切り替わります:

- -
<form>
-  <p>
-    <input type="checkbox" id="taste_1" name="taste_cherry" value="cherry">
-    <label for="taste_1">I like cherry</label>
-  </p>
-  <p>
-    <input type="checkbox" id="taste_2" name="taste_banana" value="banana">
-    <label for="taste_2">I like banana</label>
-  </p>
-</form>
- -
-

: この例は checkbox-label.html で見ることができます(ライブ版も見てください)。

-
- -

複数のラベル

- -

厳密に言うと、1 つのウィジェット内に複数のラベルを入れることができますが、複数のラベルを持つウィジェットの扱いに問題がある支援技術があるかもしれません。複数のラベルがある場合、アクセシブルなフォームを作成するには1つの {{htmlelement("label")}} 要素内にウィジェットを入れ子にするとよいでしょう。

- -

以下のサンプルについて考えてみましょう:

- -
<p>Required fields are followed by <abbr title="required">*</abbr>.</p>
-
-<!-- 2 つの例をご覧ください: -->
-<div>
-  <label for="username">Name:</label>
-  <input id="username" type="text" name="username">
-  <label for="username"><abbr title="required" aria-label="required">*</abbr></label>
-</div>
-
-<!-- 前出の例よりは良いです: -->
-<div>
-  <label for="username">
-    <span>Name: </span>
-    <input id="username" type="text" name="username">
-    <abbr title="required" aria-label="required">*</abbr>
-  </label>
-</div>
-
-<!-- これが最も良いでしょう: -->
-<div>
-  <label for="username">Name: <abbr title="required" aria-label="required">*</abbr></label>
-  <input id="username" type="text" name="username">
-</div>
- -

{{EmbedLiveSample("Multiple_labels", 120, 120)}}

- -

このサンプルでは、最初の段落で入力必須の要素の規則を定義しています。ユーザーが入力必須の要素を見つける前にスクリーンリーダーのような支援技術が注意事項を表示したり読み上げたりするためには、規則をはじめに置かなければなりません。これがユーザーにアスタリスクの意味を知らせても、それに依存することはできません。スクリーンリーダーはアスタリスクが出てくると "スター" と読み上げます。視力のあるユーザーがマウスを持ってくると、title 属性によって"必須"と表示されます。タイトルはスクリーンリーダーの設定により読み上げられるので、常にスクリーンリーダーに読み上げられる aria-label 属性を入れておくのがより信頼性が高いでしょう。

- -

上記の違いをふまえると、以降を効率よく見ていけるでしょう:

- - - -
-

: スクリーンリーダーによっては、少し異なる結果になる場合もあります。これは VoiceOver (と同様に動作する NVDA)でテストしています。あなたの体験を聞きたいです。

-
- -
-

: この例は GitHub の required-labels.html で見ることができます(ライブ版も見てください)。2 や 3 のコメントを外したバージョンの例を実行しないでください — 複数の label と複数の同じ input ID があると、スクリーンリーダーは確実に混乱します!

-
- -

フォームで使用される一般的な HTML 構造

- -

ウェブフォーム特有の構造の前提として、フォームは単に HTML であると覚えておくとよいでしょう。つまり、ウェブフォームを組み立てるために HTML のすべての力を利用できるのです。

- -

サンプルでわかるように、ラベルとそのウィジェットを {{HTMLElement("ul")}} や {{HTMLElement("ol")}} リストの中の {{HTMLElement("li")}} 要素で包み込むのが一般的な慣習です。HTML リストにあるように、{{HTMLElement("p")}} 要素と {{HTMLElement("div")}} 要素も良く使われます。リストは複数のチェックボックスやラジオボタンを構造化するのに最もよく使われます。

- -

{{HTMLElement("fieldset")}} 要素に加えて、複雑なフォームの構築に HTML の見出し (例{{htmlelement("h1")}}, {{htmlelement("h2")}}) やセクション (例 {{htmlelement("section")}}) を使うことも一般的です。

- -

とりわけ、コーディングスタイルがどうあるのが心地よく、どれがアクセシブルで使いやすいフォームとなるのかを見つけるのはあなた次第です。別の機能セクションは別の {{htmlelement("section")}} 要素と、ラジオボタンを含む {{htmlelement("fieldset")}} にそれぞれ分けておくべきです。

- -

アクティブラーニング: フォーム構造を構築する

- -

これらのアイデアを実践し、もう少し複雑なフォーム構造、つまり支払いフォームを作成しましょう。このフォームはあなたがまだ理解していないかもしれないウィジェットタイプをいくつも含みますが、今はそのことを心配しないでください。次の記事 (ネイティブフォームウィジェット) でそれらがどのように機能するのかがわかります。今のところ、以下の説明に沿って説明を注意深く読み、フォームを構成するためにどのラッパー要素を使用しているか、そしてその理由を理解することから始めてください。

- -
    -
  1. あらかじめ、空のテンプレートファイルお支払いフォームの CSS のローカルコピーをコンピューターの新しいディレクトリーに作成します。
  2. -
  3. まず最初に、HTML {{htmlelement("head")}} 内に次の行を追加して CSS を HTML に適用します。 -
    <link href="payment-form.css" rel="stylesheet">
    -
  4. -
  5. 次に、外側の {{htmlelement("form")}} 要素を追加してフォームを作成します: -
    <form>
    -
    -</form>
    -
  6. -
  7. <form> タグ内に、必須フィールドにマークを付ける方法をユーザーに通知するための見出しと段落を追加します: -
    <h1>Payment form</h1>
    -<p>Required fields are followed by <strong><abbr title="required">*</abbr></strong>.</p>
    -
  8. -
  9. 次に、前のエントリーの下に、より大きなコードセクションをフォームに追加します。ここでは、連絡先情報フィールドを個別の {{htmlelement("section")}} 要素内にラップしていることがわかります。さらに、2 つのラジオボタンのセットがあり、それぞれ独自のリスト ({{htmlelement("li")}}) 要素の中に入れています。最後に、2 つの標準テキスト {{htmlelement("input")}} とそれに関連する {{htmlelement("label")}} 要素があり、それぞれ {{htmlelement("p")}} の内側に含まれていて、パスワードを入力するためのパスワード入力があります。フォームにこのコードを追加してください: -
    <section>
    -    <h2>Contact information</h2>
    -    <fieldset>
    -      <legend>Title</legend>
    -      <ul>
    -          <li>
    -            <label for="title_1">
    -              <input type="radio" id="title_1" name="title" value="K" >
    -              King
    -            </label>
    -          </li>
    -          <li>
    -            <label for="title_2">
    -              <input type="radio" id="title_2" name="title" value="Q">
    -              Queen
    -            </label>
    -          </li>
    -          <li>
    -            <label for="title_3">
    -              <input type="radio" id="title_3" name="title" value="J">
    -              Joker
    -            </label>
    -          </li>
    -      </ul>
    -    </fieldset>
    -    <p>
    -      <label for="name">
    -        <span>Name: </span>
    -        <strong><abbr title="required">*</abbr></strong>
    -      </label>
    -      <input type="text" id="name" name="username">
    -    </p>
    -    <p>
    -      <label for="mail">
    -        <span>E-mail: </span>
    -        <strong><abbr title="required">*</abbr></strong>
    -      </label>
    -      <input type="email" id="mail" name="usermail">
    -    </p>
    -    <p>
    -      <label for="pwd">
    -        <span>Password: </span>
    -        <strong><abbr title="required">*</abbr></strong>
    -      </label>
    -      <input type="password" id="pwd" name="password">
    -    </p>
    -</section>
    -
  10. -
  11. それでは、フォームの 2 番目の <section> — 支払い情報に目を向けます。ここには 3 つの異なるウィジェットとそのラベルがあり、それぞれ <p> の中に含まれています。1 つ目は、クレジットカードの種類を選択するためのドロップダウンメニュー ({{htmlelement("select")}}) です。2 番目は、クレジットカード番号を入力するための tel 型の <input> 要素です。number 型を使うこともできますが、そのスピナーUIは望ましくありません。最後のものは、カードの有効期限を入力するための date 型の <input> 要素です。これは、サポートしているブラウザーでは日付選択ウィジェットが表示され、サポートしていないブラウザーでは通常のテキスト入力に戻ります。新しい入力タイプは HTML5 入力タイプで再度紹介されます。
    -
    - 前のセクションの下に次のように入力してください。 -
    <section>
    -    <h2>Payment information</h2>
    -    <p>
    -      <label for="card">
    -        <span>Card type:</span>
    -      </label>
    -      <select id="card" name="usercard">
    -        <option value="visa">Visa</option>
    -        <option value="mc">Mastercard</option>
    -        <option value="amex">American Express</option>
    -      </select>
    -    </p>
    -    <p>
    -      <label for="number">
    -        <span>Card number:</span>
    -        <strong><abbr title="required">*</abbr></strong>
    -      </label>
    -      <input type="tel" id="number" name="cardnumber">
    -    </p>
    -    <p>
    -      <label for="date">
    -        <span>Expiration date:</span>
    -        <strong><abbr title="required">*</abbr></strong>
    -        <em>formatted as mm/dd/yyyy</em>
    -      </label>
    -      <input type="date" id="date" name="expiration">
    -    </p>
    -</section>
    -
  12. -
  13. 最後に追加するセクションはもっと単純で、フォームデータを送信するための submit タイプの {{htmlelement("button")}} のみを含みます。これをフォームの一番下に追加してください: -
    <p> <button type="submit">Validate the payment</button> </p>
    -
  14. -
- -

完成したフォームは以下のように動作しています (GitHub でも確認できます。payment-form.html ソースを参照してライブ実行してください)。

- -

{{EmbedLiveSample("A_payment_form","100%",620, "", "Learn/HTML/Forms/How_to_structure_an_HTML_form/Example")}}

- -

あなたのスキルをテストしてみましょう!

- -

この記事はここまでですが、最も重要な情報を覚えていますか? 先に進む前に、この情報を保持しているかどうかを確認するためのテストがあります — Test your skills: Form structure を参照してください。

- -

まとめ

- -

ウェブフォームを適切に構築するためのあらゆる知識を得ることができました。これからここで出てきた機能を見ていき、次の記事では、ユーザーから情報を集めるのに使いたくなるすべての種類のフォームウィジェットの詳細な実装について詳しく見ていきます。

- -

関連情報

- - - -

{{PreviousMenuNext("Learn/HTML/Forms/Your_first_HTML_form", "Learn/HTML/Forms/The_native_form_widgets", "Learn/HTML/Forms")}}

- -

このモジュール

- - - -

上級トピック

- - diff --git a/files/ja/learn/forms/styling_html_forms/index.html b/files/ja/learn/forms/styling_html_forms/index.html deleted file mode 100644 index 71d463f0c7..0000000000 --- a/files/ja/learn/forms/styling_html_forms/index.html +++ /dev/null @@ -1,399 +0,0 @@ ---- -title: HTML フォームへのスタイル設定 -slug: Learn/Forms/Styling_HTML_forms -tags: - - CSS - - Example - - Forms - - Guide - - HTML - - Intermediate - - Web -translation_of: Learn/Forms/Styling_web_forms ---- -

{{LearnSidebar}}{{PreviousMenuNext("Learn/Forms/Other_form_controls","Learn/Forms/Advanced_form_styling","Learn/Forms")}}

- -

前の記事ではウェブフォームを構築するのに必要な HTML のすべてを見てきました。この記事ではフォームコントロールにスタイル設定する CSS の使い方に進みます。これは歴史的に難しかったです — フォームコントロールは大きく変わり CSS を使ったフォームのカスタマイズは簡単になりました— しかし古いブラウザーが引退してモダンブラウザーが多くの機能を与えるため、より簡単になりました。

- - - - - - - - - - - - -
前提条件: -

基本的なコンピューターリテラシーと、HTMLCSS の基本的な理解。

-
目的:フォームのスタイル設定の問題を理解し、役立つスタイル付けのテクニックを学ぶこと。
- -

なぜ CSS によるフォームウィジェットへのスタイル設定は困難であるか?

- -

1995年頃に HTML 2 仕様へフォームコントロールが追加されました。CSS は 1996年までリリースされず、その後も少しのブラウザーによって十分サポートされませんでした。ブラウザーはフォームコントロールの管理や表示について下層の OS に頼ることを選択しました。

- -

CSS が HTML のスタイル設定できるようになってからも、ユーザーは各プラットフォームの視覚的な外見に慣れていましたので、ブラウザーベンダーはフォームコントロールをスタイル付け可能にすることに乗り気ではありませんでした。しかしこれは変わりました。ウェブサイトのオーナーはこれまでよりも、サイト全体に適するスタイルを欲しており、ウェブプラットフォームはこれを実現可能にしました。

- -

いくつかのフォームウィジェットでは、コントロールをスタイル設定できるように作成し直すのは難しいですが、ユーザービリティを破綻させないよう気をつける必要はあるものの、CSS を使って多くのフォーム機能をスタイル設定できます。

- -

CSS を伴ってもすべてのウィジェットが同等に作成されるわけではありません

- -

いまだに、フォームで CSS を使用する際に困ることが存在します。この問題は、3 つのカテゴリーに分けられます。

- -

良好

- -

いくつかの要素はプラットフォーム間の問題があるとしても、ほとんど問題なくスタイルを設定できます。これらは以下の構造的な要素が含まれます:

- -
    -
  1. {{HTMLElement("form")}}
  2. -
  3. {{HTMLElement("fieldset")}} と {{HTMLElement("legend")}}
  4. -
  5. 単一行のテキスト {{HTMLElement("input")}} (例 text, url, email...のタイプ) <input type="search">を除く
  6. -
  7. 複数行の {{HTMLElement("textarea")}}
  8. -
  9. ボタン ({{HTMLElement("input")}} と {{HTMLElement("button")}}の両方)
  10. -
  11. {{HTMLElement("label")}}
  12. -
  13. {{HTMLElement("output")}}
  14. -
- -

不良

- -

一部の要素はほとんどスタイル設定ができず、時に CSS3 の高度な知識やトリックが必要になるかもしれません。

- -
    -
  1. チェックボックスとラジオボタン
  2. -
  3. <input type="search">
  4. -
- -

これら特殊なケースをどのように扱うかについては、HTML フォームへの高度なスタイル設定の記事で見ていきます。

- -

劣悪

- -

一部の要素は、CSS でスタイルを設定できません。たとえば次のもの:

- - - -

これらの要素をスタイル設定するのに関して何ができるかについては、HTML フォームへの高度なスタイル設定の記事で見ていきます。

- -

これらすべてのウィジェットの主な問題は、ウィジェットの構造がとても複雑であるという事実と、(コントロールの width や margin の変更といった)基本的なスタイル設定を超えると、現在の CSS では(例えばカレンダー日付ピッカーや、選択肢のリストを表示する<select>のボタンのような)ウィジェットの細かい部分すべてにスタイルを設定できるほどの表現力がないことによります。

- -

これらのウィジェットを完全にカスタマイズしたい場合は、HTML, CSS, JavaScript を使って独自のものを作成する必要があります。それはこのコアフォームの記事の範囲を超えますが、高度な記事のカスタムウィジェットの作成方法の記事で説明します。

- -
-

: フォームコントロールの内部コンポーネントにスタイル設定するプロプライエタリな CSS 疑似要素、例えば {{cssxref('::-moz-range-track')}}がありますが、これはブラウザー同士で整合していないので、これに頼るべきではありません。これについては後程でも触れます。

-
- -

良好

- -

CSS でのスタイル設定が容易な要素は、振る舞いが他の HTML 要素とほとんど同じであるため、問題に直面することはないでしょう。ただし、ブラウザー間でユーザーエージェントのスタイルシートが若干矛盾するかもしれませんので、より簡単にスタイルを設定できるようにするためのトリックがあります。

- -

上記で述べた基本的な CSS ツールと同じく、いくつかのセレクターが与えられます — UI 疑似クラス — これにより現在の UI の状態に基づくスタイル設定ができます。これは次の記事である、UI 疑似クラスで扱います。

- -

この記事の最後で基本的なフォームコントロールのスタイル設定と配置について理解できる実例を詳しく見ていきます。しかしその前に、知っておくと良いフォームスタイル設定の特定の面をいくつか述べておきます。

- -

フォントとテキスト

- -

CSS のフォントやテキストの機能は、任意のウィジェットで容易に使用できます (また、フォームウィジェットで {{cssxref("@font-face")}} も使用できます)。ただし、ブラウザーの動作にしばしば矛盾があります。デフォルトで、一部のブラウザーは親から {{cssxref("font-family")}} や {{cssxref("font-size")}} を継承しません。代わりに多くのブラウザーでは、システムのデフォルトの体裁を使用します。フォームの体裁を他のコンテンツと一致させるには、以下のルールをスタイルシートに追加するとよいでしょう:

- -
button, input, select, textarea {
-  font-family : inherit;
-  font-size   : 100%;
-}
- -

{{cssxref('inherit')}} のプロパティ値で、プロパティ値は計算された親要素のプロパティ値に一致するようになります。つまり親の値を継承します。

- -

以下のスクリーンショットで違いを示します。左側は Mac OS X の Chrome における<input type="text">, <input type="date">, {{htmlelement('select')}}, {{htmlelement('textarea')}}, <input type="submit">, <button> 要素の既定のレンダリングで、プラットフォームのデフォルトフォントスタイルを使用しています。右側は同じ要素ですが、フォントを調和させるスタイルルールを適用したものです。

- -

Form controls with default and inherited font families. 既定では, some types are serif and others are sans serif. Inheriting should change the fonts of all to the parent's font family - in this case a paragraph. Oddly, input of type submit does not inherit from the parent paragraph.

- -

既定はいろいろと変わります。継承により、フォントは親のフォントファミリーに変更されます — ここでは親コンテナのデフォルトの serif フォントです。ほぼすべてそうですが、例外として Chrome では<input type="submit"> は親段落を継承しません。むしろ、{{cssxref('font-family#Values', 'font-family: system-ui')}}を使います。これは同等な入力タイプの中で <button> 要素を使う理由です!

- -

フォームはシステムのデフォルトスタイルを使用するか、コンテンツに合うよう設計されたカスタムスタイルを使用するかについては多くの議論があります。これを決めるのは、設計者としてサイトやウェブアプリケーションを作成するあなた次第です。

- -

ボックスモデル

- -

すべてのテキストフィールドは、CSS のボックスモデルに関する全プロパティ ({{cssxref("width")}}、{{cssxref("height")}}、{{cssxref("padding")}}、{{cssxref("margin")}}、および {{cssxref("border")}}) を完全にサポートしています。ただし前述のとおり、ブラウザーがウィジェットを表示する際はシステムのデフォルトスタイルに依存します。コンテンツに対してそれらをどのように混ぜ合わせるかを決めるのは、あなた次第です。ウィジェットでネイティブのルックアンドフィールを維持したいのでしたら、ウィジェットのサイズを調和させたい場合に若干の問題に直面するでしょう。

- -

これは各ウィジェットがボーダー、パディング、マージンについて独自のルールを持っているためです。このためさまざまなウィジェットを同じサイズにしたい場合に、{{cssxref("box-sizing")}} プロパティを使用しなければなりません:

- -
input, textarea, select, button {
-  width : 150px;
-  padding: 0;
-  margin: 0;
-  box-sizing: border-box;
-}
- -

下のスクリーンショットで、左の列は<input type="radio">, <input type="checkbox">, <input type="range">, <input type="text">, <input type="date"> input, {{htmlelement('select')}}, {{htmlelement('textarea')}},<input type="submit">, {{htmlelement('button')}} の既定の描画、右の列は同じ要素に上のルールを使用して作成したものです。各種のウィジェットのプラットフォームのデフォルトルールと比較して、すべての要素が同じ領域を占めるようにすることが可能な点に注目してください。

- -

box model properties effect most input types.

- -

スクリーンショットで明白でないことはラジオボタンとチェックボックスコントロールが同じであるが、水平位置が {{cssxref('width')}} プロパティで与えられる 150px の中心にあることです。他のブラウザーではウィジェットを中心揃えにしませんが、割り当てられたスペースに付着させます。

- -

legend 配置

- -

{{HTMLElement("legend")}} 要素はポジショニングを除いて、スタイル設定の問題はありません。既定では、それは親 {{HTMLElement("fieldset")}} の上ボーダーの前面に、左上の隅の近くに配置されます。これを他の場所、例えば fieldset内のどこかや、左下の隅に配置するには、配置に頼る必要があります。

- -

下記の例を見てください:

- -

{{EmbedGHLiveSample("learning-area/html/forms/native-form-widgets/positioned-legend.html", '100%', 400)}}

- -

この方法で legend を配置するには、次の CSS を使います(簡単のため他の宣言は削除しています):

- -
fieldset {
-  position: relative;
-}
-
-legend {
-  position: absolute;
-  bottom: 0;
-  right: 0;
-}
- -

<fieldset> も配置される必要があり、<legend> がそれに合わせて位置が決まるように (そうでなければ <legend><body>に合わせて位置決めされます)

- -

{{HTMLElement("legend")}} 要素はアクセシビリティのためとても重要です — これはアシスト技術により fieldset 内の各フォーム要素のラベルとして話されます — が、上のようなテクニックの使用は良いです。legend コンテンツは同じ方法で話されます; 単に見た目の位置が変更されます。

- -
-

: <legend>の位置決めに役立つ{{cssxref("transform")}}プロパティも使用できますが、例えばa transform: translateY();を使って配置するとき、移動はするものの <fieldset> の枠に劣悪なギャップができて、除去が困難です。

-
- -

特定のスタイル設定の例

- -

HTML フォームにスタイルを設定する方法の具体例を見ていきましょう。以下のような "はがき" 風の連絡フォームを作成します。完成バージョンはこちら

- -

この例に従うには、postcard-start.html ファイルをコピーして、次のやり方に従ってください。

- -

HTML

- -

HTML は、ガイドの最初の記事で使用したものより少しだけ複雑です。いくつか ID やタイトルを追加しています。

- -
<form>
- <h1>to: Mozilla</h1>
-
-  <div id="from">
-    <label for="name">from:</label>
-    <input type="text" id="name" name="user_name">
-  </div>
-
-  <div id="reply">
-    <label for="mail">reply:</label>
-    <input type="email" id="mail" name="user_email">
-  </div>
-
-  <div id="message">
-    <label for="msg">Your message:</label>
-    <textarea id="msg" name="user_message"></textarea>
-  </div>
-
-  <div class="button">
-    <button type="submit">Send your message</button>
-  </div>
-</form>
- -

上記のコードを HTML の body に追加します。

- -

アセットを揃える

- -

ここからがおもしろいところです! コードを書き始める前に、ここでは 3 つの追加要素が必要です:

- -
    -
  1. はがきの背景 — この画像をダウンロードして作業している HTML ファイルと同じディレクトリーに保存します。
  2. -
  3. タイプライター風フォント: fontsquirrel.com の "Secret Typewriter"  — TTF ファイルを上記と同じディレクトリーにダウンロードします。
  4. -
  5. 手書き風フォント: fontsquirrel.com の "Journal"  — TTF ファイルを上記と同じディレクトリーにダウンロードします。
  6. -
- -

始める前にフォントの処理が必要です:

- -
    -
  1. fontsquirrel Webfont Generator に移動します。
  2. -
  3. フォームを使って、両方のフォントファイルをアップロードして webfont キットを生成します。キットをコンピューターにダウンロードします。
  4. -
  5. zip ファイルを展開します。
  6. -
  7. 展開した中身には 2 つの .woff ファイルと 2 つの .woff2 ファイルがあります。このファイルを、前と同じ fonts というディレクトリーにコピーします。各フォントの 2 つのファイルはブラウザー互換性を最大化するのに使います; より詳しい情報は Web fonts の記事を見てください。
  8. -
- -

CSS

- -

ここから例の CSS を見ていきましょう。{{htmlelement("style")}} 要素の中にすべてのコードブロックを一つ一つ追加します。

- -

全体レイアウト

- -

まず、{{cssxref("@font-face")}} ルールと、すべての{{HTMLElement("body")}} と {{HTMLElement("form")}} 要素に設定するスタイルを定義して準備します。fontsquirrel 出力が上記で述べたものと異なる場合、stylesheet.css ファイル内にダウンロード済みの webfont キットの中から正しい @font-face ブロックを見つけることができます(下記の @font-face ブロックをそれで置換し、パスをフォントファイルのものに更新する必要があります):

- -
@font-face {
-    font-family: 'handwriting';
-    src: url('fonts/journal-webfont.woff2') format('woff2'),
-         url('fonts/journal-webfont.woff') format('woff');
-    font-weight: normal;
-    font-style: normal;
-}
-
-@font-face {
-    font-family: 'typewriter';
-    src: url('fonts/veteran_typewriter-webfont.woff2') format('woff2'),
-         url('fonts/veteran_typewriter-webfont.woff') format('woff');
-    font-weight: normal;
-    font-style: normal;
-}
-
-body {
-  font  : 1.3rem sans-serif;
-  padding : 0.5em;
-  margin  : 0;
-  background : #222;
-}
-
-form {
-  position : relative;
-  width  : 740px;
-  height : 498px;
-  margin : 0 auto;
-  padding: 1em;
-  box-sizing: border-box;
-  background : #FFF url(background.jpg);
-
-  /* we create our grid */
-  display  : grid;
-  grid-gap : 20px;
-  grid-template-columns : repeat(2, 1fr);
-  grid-template-rows    : 10em 1em 1em 1em;
-}
- -

注意として、フォームをレイアウトするのに CSS GridFlexbox を使っています。これで、タイトルやフォーム要素といった各要素を配置できます:

- -
h1 {
-  font : 1em "typewriter", monospace;
-  align-self : end;
-}
-
-#message {
-   grid-row: 1 / 5;
-}
-
-#from, #reply {
-   display: flex;
-}
- -

ラベルとコントロール

- -

そして、フォーム要素自体に対するスタイル設定を始めます。まずは、{{HTMLElement("label")}} に適切なフォントを割り当てましょう。

- -
label {
-  font : .8em "typewriter", sans-serif;
-}
- -

テキストフィールドには、共通のルールがいくつか必要です。{{cssxref("border")}} や {{cssxref("background")}} の削除と {{cssxref("padding")}} や {{cssxref("margin")}} の再定義を行います。

- -
input, textarea {
-  font    : 1.4em/1.5em "handwriting", cursive, sans-serif;
-  border  : none;
-  padding : 0 10px;
-  margin  : 0;
-  width   : 80%;
-  background : none;
-}
- -

これらフィールドのひとつがフォーカスを得たときに、ライトグレー色で透過する背景で強調します。一部のブラウザーで付加されるデフォルトのフォーカス強調を取り除くため、{{cssxref("outline")}} プロパティを追加することが重要ですので注意してください。

- -
input:focus, textarea:focus {
-  background   : rgba(0,0,0,.1);
-  border-radius: 5px;
-}
- -

テキストフィールドのスタイル設定が完了して、次は単一行および複数行のテキストフィールドの表示が同じになるよう調整しなければなりません。これは、一般的にこれらのデフォルト表示が同じでないためです。

- -

textareaの微調整

- -

{{HTMLElement("textarea")}} 要素はデフォルトでブロック要素としてレンダリングされるようにします。ここで重要なことは、{{cssxref("resize")}} プロパティと {{cssxref("overflow")}} プロパティの 2 つです。ここでは固定サイズでデザインしているため、ユーザーが複数行のテキストフィールドをリサイズできないように resize プロパティを使用します。{{cssxref("overflow")}} プロパティは、ブラウザー間でのフィールドの一貫性を向上させるために使用します。これのデフォルト値が auto であるブラウザーと scroll であるブラウザーが存在します。この例では、すべてのブラウザーが auto になるようにするのがよいでしょう。

- -
textarea {
-  display : block;
-
-  padding : 10px;
-  margin  : 10px 0 0 -10px;
-  width   : 100%;
-  height  : 90%;
-
-  border-right: 1px solid;
-
-  /* resize  : none; */
-  overflow: auto;
-}
- -

送信ボタンにスタイル設定する

- -

{{HTMLElement("button")}} 要素は、CSS によってより便利になります。疑似要素を含めて、行いたいことが何でもできます!

- -
button {
-  padding      : 5px;
-  font         : bold .6em sans-serif;
-  border       : 2px solid #333;
-  border-radius: 5px;
-  background   : none;
-  cursor       : pointer;
-  transform    : rotate(-1.5deg);
-}
-
-button:after {
-  content      : " >>>";
-}
-
-button:hover,
-button:focus {
-  outline     : none;
-  background  : #000;
-  color       : #FFF;
-}
- -

最終結果

- -

これでよし! フォームは次のようになるでしょう:

- -

- -
-

: 例が期待どおり動かず、われわれのバージョンを確認したい場合、GitHub にあります — ライブ版を見てください (ソースコードも見てください)。

-
- -

スキルを試しましょう!​

- -

この記事の終わりまで到達しました。しかし、肝要な点を思い起こせるでしょうか?次に進む前に、テストによって知識の定着を試すことができます——スキルテスト:スタイリングの基本をご覧ください。

- -

まとめ

- -

ご覧いただいたとおり、テキストフィールドとボタンだけでフォームを作成する限りでは、CSS を使用したスタイル設定は容易です。次の記事では、"不良" や "劣悪" カテゴリに入っているウィジェットの扱い方を見ていきます。

- -

{{PreviousMenuNext("Learn/Forms/Other_form_controls","Learn/Forms/Advanced_form_styling","Learn/Forms")}}

- -

このモジュール

- -

E

- - - -

上級トピック

- -

セクション

- - diff --git a/files/ja/learn/forms/styling_web_forms/index.html b/files/ja/learn/forms/styling_web_forms/index.html new file mode 100644 index 0000000000..71d463f0c7 --- /dev/null +++ b/files/ja/learn/forms/styling_web_forms/index.html @@ -0,0 +1,399 @@ +--- +title: HTML フォームへのスタイル設定 +slug: Learn/Forms/Styling_HTML_forms +tags: + - CSS + - Example + - Forms + - Guide + - HTML + - Intermediate + - Web +translation_of: Learn/Forms/Styling_web_forms +--- +

{{LearnSidebar}}{{PreviousMenuNext("Learn/Forms/Other_form_controls","Learn/Forms/Advanced_form_styling","Learn/Forms")}}

+ +

前の記事ではウェブフォームを構築するのに必要な HTML のすべてを見てきました。この記事ではフォームコントロールにスタイル設定する CSS の使い方に進みます。これは歴史的に難しかったです — フォームコントロールは大きく変わり CSS を使ったフォームのカスタマイズは簡単になりました— しかし古いブラウザーが引退してモダンブラウザーが多くの機能を与えるため、より簡単になりました。

+ + + + + + + + + + + + +
前提条件: +

基本的なコンピューターリテラシーと、HTMLCSS の基本的な理解。

+
目的:フォームのスタイル設定の問題を理解し、役立つスタイル付けのテクニックを学ぶこと。
+ +

なぜ CSS によるフォームウィジェットへのスタイル設定は困難であるか?

+ +

1995年頃に HTML 2 仕様へフォームコントロールが追加されました。CSS は 1996年までリリースされず、その後も少しのブラウザーによって十分サポートされませんでした。ブラウザーはフォームコントロールの管理や表示について下層の OS に頼ることを選択しました。

+ +

CSS が HTML のスタイル設定できるようになってからも、ユーザーは各プラットフォームの視覚的な外見に慣れていましたので、ブラウザーベンダーはフォームコントロールをスタイル付け可能にすることに乗り気ではありませんでした。しかしこれは変わりました。ウェブサイトのオーナーはこれまでよりも、サイト全体に適するスタイルを欲しており、ウェブプラットフォームはこれを実現可能にしました。

+ +

いくつかのフォームウィジェットでは、コントロールをスタイル設定できるように作成し直すのは難しいですが、ユーザービリティを破綻させないよう気をつける必要はあるものの、CSS を使って多くのフォーム機能をスタイル設定できます。

+ +

CSS を伴ってもすべてのウィジェットが同等に作成されるわけではありません

+ +

いまだに、フォームで CSS を使用する際に困ることが存在します。この問題は、3 つのカテゴリーに分けられます。

+ +

良好

+ +

いくつかの要素はプラットフォーム間の問題があるとしても、ほとんど問題なくスタイルを設定できます。これらは以下の構造的な要素が含まれます:

+ +
    +
  1. {{HTMLElement("form")}}
  2. +
  3. {{HTMLElement("fieldset")}} と {{HTMLElement("legend")}}
  4. +
  5. 単一行のテキスト {{HTMLElement("input")}} (例 text, url, email...のタイプ) <input type="search">を除く
  6. +
  7. 複数行の {{HTMLElement("textarea")}}
  8. +
  9. ボタン ({{HTMLElement("input")}} と {{HTMLElement("button")}}の両方)
  10. +
  11. {{HTMLElement("label")}}
  12. +
  13. {{HTMLElement("output")}}
  14. +
+ +

不良

+ +

一部の要素はほとんどスタイル設定ができず、時に CSS3 の高度な知識やトリックが必要になるかもしれません。

+ +
    +
  1. チェックボックスとラジオボタン
  2. +
  3. <input type="search">
  4. +
+ +

これら特殊なケースをどのように扱うかについては、HTML フォームへの高度なスタイル設定の記事で見ていきます。

+ +

劣悪

+ +

一部の要素は、CSS でスタイルを設定できません。たとえば次のもの:

+ + + +

これらの要素をスタイル設定するのに関して何ができるかについては、HTML フォームへの高度なスタイル設定の記事で見ていきます。

+ +

これらすべてのウィジェットの主な問題は、ウィジェットの構造がとても複雑であるという事実と、(コントロールの width や margin の変更といった)基本的なスタイル設定を超えると、現在の CSS では(例えばカレンダー日付ピッカーや、選択肢のリストを表示する<select>のボタンのような)ウィジェットの細かい部分すべてにスタイルを設定できるほどの表現力がないことによります。

+ +

これらのウィジェットを完全にカスタマイズしたい場合は、HTML, CSS, JavaScript を使って独自のものを作成する必要があります。それはこのコアフォームの記事の範囲を超えますが、高度な記事のカスタムウィジェットの作成方法の記事で説明します。

+ +
+

: フォームコントロールの内部コンポーネントにスタイル設定するプロプライエタリな CSS 疑似要素、例えば {{cssxref('::-moz-range-track')}}がありますが、これはブラウザー同士で整合していないので、これに頼るべきではありません。これについては後程でも触れます。

+
+ +

良好

+ +

CSS でのスタイル設定が容易な要素は、振る舞いが他の HTML 要素とほとんど同じであるため、問題に直面することはないでしょう。ただし、ブラウザー間でユーザーエージェントのスタイルシートが若干矛盾するかもしれませんので、より簡単にスタイルを設定できるようにするためのトリックがあります。

+ +

上記で述べた基本的な CSS ツールと同じく、いくつかのセレクターが与えられます — UI 疑似クラス — これにより現在の UI の状態に基づくスタイル設定ができます。これは次の記事である、UI 疑似クラスで扱います。

+ +

この記事の最後で基本的なフォームコントロールのスタイル設定と配置について理解できる実例を詳しく見ていきます。しかしその前に、知っておくと良いフォームスタイル設定の特定の面をいくつか述べておきます。

+ +

フォントとテキスト

+ +

CSS のフォントやテキストの機能は、任意のウィジェットで容易に使用できます (また、フォームウィジェットで {{cssxref("@font-face")}} も使用できます)。ただし、ブラウザーの動作にしばしば矛盾があります。デフォルトで、一部のブラウザーは親から {{cssxref("font-family")}} や {{cssxref("font-size")}} を継承しません。代わりに多くのブラウザーでは、システムのデフォルトの体裁を使用します。フォームの体裁を他のコンテンツと一致させるには、以下のルールをスタイルシートに追加するとよいでしょう:

+ +
button, input, select, textarea {
+  font-family : inherit;
+  font-size   : 100%;
+}
+ +

{{cssxref('inherit')}} のプロパティ値で、プロパティ値は計算された親要素のプロパティ値に一致するようになります。つまり親の値を継承します。

+ +

以下のスクリーンショットで違いを示します。左側は Mac OS X の Chrome における<input type="text">, <input type="date">, {{htmlelement('select')}}, {{htmlelement('textarea')}}, <input type="submit">, <button> 要素の既定のレンダリングで、プラットフォームのデフォルトフォントスタイルを使用しています。右側は同じ要素ですが、フォントを調和させるスタイルルールを適用したものです。

+ +

Form controls with default and inherited font families. 既定では, some types are serif and others are sans serif. Inheriting should change the fonts of all to the parent's font family - in this case a paragraph. Oddly, input of type submit does not inherit from the parent paragraph.

+ +

既定はいろいろと変わります。継承により、フォントは親のフォントファミリーに変更されます — ここでは親コンテナのデフォルトの serif フォントです。ほぼすべてそうですが、例外として Chrome では<input type="submit"> は親段落を継承しません。むしろ、{{cssxref('font-family#Values', 'font-family: system-ui')}}を使います。これは同等な入力タイプの中で <button> 要素を使う理由です!

+ +

フォームはシステムのデフォルトスタイルを使用するか、コンテンツに合うよう設計されたカスタムスタイルを使用するかについては多くの議論があります。これを決めるのは、設計者としてサイトやウェブアプリケーションを作成するあなた次第です。

+ +

ボックスモデル

+ +

すべてのテキストフィールドは、CSS のボックスモデルに関する全プロパティ ({{cssxref("width")}}、{{cssxref("height")}}、{{cssxref("padding")}}、{{cssxref("margin")}}、および {{cssxref("border")}}) を完全にサポートしています。ただし前述のとおり、ブラウザーがウィジェットを表示する際はシステムのデフォルトスタイルに依存します。コンテンツに対してそれらをどのように混ぜ合わせるかを決めるのは、あなた次第です。ウィジェットでネイティブのルックアンドフィールを維持したいのでしたら、ウィジェットのサイズを調和させたい場合に若干の問題に直面するでしょう。

+ +

これは各ウィジェットがボーダー、パディング、マージンについて独自のルールを持っているためです。このためさまざまなウィジェットを同じサイズにしたい場合に、{{cssxref("box-sizing")}} プロパティを使用しなければなりません:

+ +
input, textarea, select, button {
+  width : 150px;
+  padding: 0;
+  margin: 0;
+  box-sizing: border-box;
+}
+ +

下のスクリーンショットで、左の列は<input type="radio">, <input type="checkbox">, <input type="range">, <input type="text">, <input type="date"> input, {{htmlelement('select')}}, {{htmlelement('textarea')}},<input type="submit">, {{htmlelement('button')}} の既定の描画、右の列は同じ要素に上のルールを使用して作成したものです。各種のウィジェットのプラットフォームのデフォルトルールと比較して、すべての要素が同じ領域を占めるようにすることが可能な点に注目してください。

+ +

box model properties effect most input types.

+ +

スクリーンショットで明白でないことはラジオボタンとチェックボックスコントロールが同じであるが、水平位置が {{cssxref('width')}} プロパティで与えられる 150px の中心にあることです。他のブラウザーではウィジェットを中心揃えにしませんが、割り当てられたスペースに付着させます。

+ +

legend 配置

+ +

{{HTMLElement("legend")}} 要素はポジショニングを除いて、スタイル設定の問題はありません。既定では、それは親 {{HTMLElement("fieldset")}} の上ボーダーの前面に、左上の隅の近くに配置されます。これを他の場所、例えば fieldset内のどこかや、左下の隅に配置するには、配置に頼る必要があります。

+ +

下記の例を見てください:

+ +

{{EmbedGHLiveSample("learning-area/html/forms/native-form-widgets/positioned-legend.html", '100%', 400)}}

+ +

この方法で legend を配置するには、次の CSS を使います(簡単のため他の宣言は削除しています):

+ +
fieldset {
+  position: relative;
+}
+
+legend {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+}
+ +

<fieldset> も配置される必要があり、<legend> がそれに合わせて位置が決まるように (そうでなければ <legend><body>に合わせて位置決めされます)

+ +

{{HTMLElement("legend")}} 要素はアクセシビリティのためとても重要です — これはアシスト技術により fieldset 内の各フォーム要素のラベルとして話されます — が、上のようなテクニックの使用は良いです。legend コンテンツは同じ方法で話されます; 単に見た目の位置が変更されます。

+ +
+

: <legend>の位置決めに役立つ{{cssxref("transform")}}プロパティも使用できますが、例えばa transform: translateY();を使って配置するとき、移動はするものの <fieldset> の枠に劣悪なギャップができて、除去が困難です。

+
+ +

特定のスタイル設定の例

+ +

HTML フォームにスタイルを設定する方法の具体例を見ていきましょう。以下のような "はがき" 風の連絡フォームを作成します。完成バージョンはこちら

+ +

この例に従うには、postcard-start.html ファイルをコピーして、次のやり方に従ってください。

+ +

HTML

+ +

HTML は、ガイドの最初の記事で使用したものより少しだけ複雑です。いくつか ID やタイトルを追加しています。

+ +
<form>
+ <h1>to: Mozilla</h1>
+
+  <div id="from">
+    <label for="name">from:</label>
+    <input type="text" id="name" name="user_name">
+  </div>
+
+  <div id="reply">
+    <label for="mail">reply:</label>
+    <input type="email" id="mail" name="user_email">
+  </div>
+
+  <div id="message">
+    <label for="msg">Your message:</label>
+    <textarea id="msg" name="user_message"></textarea>
+  </div>
+
+  <div class="button">
+    <button type="submit">Send your message</button>
+  </div>
+</form>
+ +

上記のコードを HTML の body に追加します。

+ +

アセットを揃える

+ +

ここからがおもしろいところです! コードを書き始める前に、ここでは 3 つの追加要素が必要です:

+ +
    +
  1. はがきの背景 — この画像をダウンロードして作業している HTML ファイルと同じディレクトリーに保存します。
  2. +
  3. タイプライター風フォント: fontsquirrel.com の "Secret Typewriter"  — TTF ファイルを上記と同じディレクトリーにダウンロードします。
  4. +
  5. 手書き風フォント: fontsquirrel.com の "Journal"  — TTF ファイルを上記と同じディレクトリーにダウンロードします。
  6. +
+ +

始める前にフォントの処理が必要です:

+ +
    +
  1. fontsquirrel Webfont Generator に移動します。
  2. +
  3. フォームを使って、両方のフォントファイルをアップロードして webfont キットを生成します。キットをコンピューターにダウンロードします。
  4. +
  5. zip ファイルを展開します。
  6. +
  7. 展開した中身には 2 つの .woff ファイルと 2 つの .woff2 ファイルがあります。このファイルを、前と同じ fonts というディレクトリーにコピーします。各フォントの 2 つのファイルはブラウザー互換性を最大化するのに使います; より詳しい情報は Web fonts の記事を見てください。
  8. +
+ +

CSS

+ +

ここから例の CSS を見ていきましょう。{{htmlelement("style")}} 要素の中にすべてのコードブロックを一つ一つ追加します。

+ +

全体レイアウト

+ +

まず、{{cssxref("@font-face")}} ルールと、すべての{{HTMLElement("body")}} と {{HTMLElement("form")}} 要素に設定するスタイルを定義して準備します。fontsquirrel 出力が上記で述べたものと異なる場合、stylesheet.css ファイル内にダウンロード済みの webfont キットの中から正しい @font-face ブロックを見つけることができます(下記の @font-face ブロックをそれで置換し、パスをフォントファイルのものに更新する必要があります):

+ +
@font-face {
+    font-family: 'handwriting';
+    src: url('fonts/journal-webfont.woff2') format('woff2'),
+         url('fonts/journal-webfont.woff') format('woff');
+    font-weight: normal;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: 'typewriter';
+    src: url('fonts/veteran_typewriter-webfont.woff2') format('woff2'),
+         url('fonts/veteran_typewriter-webfont.woff') format('woff');
+    font-weight: normal;
+    font-style: normal;
+}
+
+body {
+  font  : 1.3rem sans-serif;
+  padding : 0.5em;
+  margin  : 0;
+  background : #222;
+}
+
+form {
+  position : relative;
+  width  : 740px;
+  height : 498px;
+  margin : 0 auto;
+  padding: 1em;
+  box-sizing: border-box;
+  background : #FFF url(background.jpg);
+
+  /* we create our grid */
+  display  : grid;
+  grid-gap : 20px;
+  grid-template-columns : repeat(2, 1fr);
+  grid-template-rows    : 10em 1em 1em 1em;
+}
+ +

注意として、フォームをレイアウトするのに CSS GridFlexbox を使っています。これで、タイトルやフォーム要素といった各要素を配置できます:

+ +
h1 {
+  font : 1em "typewriter", monospace;
+  align-self : end;
+}
+
+#message {
+   grid-row: 1 / 5;
+}
+
+#from, #reply {
+   display: flex;
+}
+ +

ラベルとコントロール

+ +

そして、フォーム要素自体に対するスタイル設定を始めます。まずは、{{HTMLElement("label")}} に適切なフォントを割り当てましょう。

+ +
label {
+  font : .8em "typewriter", sans-serif;
+}
+ +

テキストフィールドには、共通のルールがいくつか必要です。{{cssxref("border")}} や {{cssxref("background")}} の削除と {{cssxref("padding")}} や {{cssxref("margin")}} の再定義を行います。

+ +
input, textarea {
+  font    : 1.4em/1.5em "handwriting", cursive, sans-serif;
+  border  : none;
+  padding : 0 10px;
+  margin  : 0;
+  width   : 80%;
+  background : none;
+}
+ +

これらフィールドのひとつがフォーカスを得たときに、ライトグレー色で透過する背景で強調します。一部のブラウザーで付加されるデフォルトのフォーカス強調を取り除くため、{{cssxref("outline")}} プロパティを追加することが重要ですので注意してください。

+ +
input:focus, textarea:focus {
+  background   : rgba(0,0,0,.1);
+  border-radius: 5px;
+}
+ +

テキストフィールドのスタイル設定が完了して、次は単一行および複数行のテキストフィールドの表示が同じになるよう調整しなければなりません。これは、一般的にこれらのデフォルト表示が同じでないためです。

+ +

textareaの微調整

+ +

{{HTMLElement("textarea")}} 要素はデフォルトでブロック要素としてレンダリングされるようにします。ここで重要なことは、{{cssxref("resize")}} プロパティと {{cssxref("overflow")}} プロパティの 2 つです。ここでは固定サイズでデザインしているため、ユーザーが複数行のテキストフィールドをリサイズできないように resize プロパティを使用します。{{cssxref("overflow")}} プロパティは、ブラウザー間でのフィールドの一貫性を向上させるために使用します。これのデフォルト値が auto であるブラウザーと scroll であるブラウザーが存在します。この例では、すべてのブラウザーが auto になるようにするのがよいでしょう。

+ +
textarea {
+  display : block;
+
+  padding : 10px;
+  margin  : 10px 0 0 -10px;
+  width   : 100%;
+  height  : 90%;
+
+  border-right: 1px solid;
+
+  /* resize  : none; */
+  overflow: auto;
+}
+ +

送信ボタンにスタイル設定する

+ +

{{HTMLElement("button")}} 要素は、CSS によってより便利になります。疑似要素を含めて、行いたいことが何でもできます!

+ +
button {
+  padding      : 5px;
+  font         : bold .6em sans-serif;
+  border       : 2px solid #333;
+  border-radius: 5px;
+  background   : none;
+  cursor       : pointer;
+  transform    : rotate(-1.5deg);
+}
+
+button:after {
+  content      : " >>>";
+}
+
+button:hover,
+button:focus {
+  outline     : none;
+  background  : #000;
+  color       : #FFF;
+}
+ +

最終結果

+ +

これでよし! フォームは次のようになるでしょう:

+ +

+ +
+

: 例が期待どおり動かず、われわれのバージョンを確認したい場合、GitHub にあります — ライブ版を見てください (ソースコードも見てください)。

+
+ +

スキルを試しましょう!​

+ +

この記事の終わりまで到達しました。しかし、肝要な点を思い起こせるでしょうか?次に進む前に、テストによって知識の定着を試すことができます——スキルテスト:スタイリングの基本をご覧ください。

+ +

まとめ

+ +

ご覧いただいたとおり、テキストフィールドとボタンだけでフォームを作成する限りでは、CSS を使用したスタイル設定は容易です。次の記事では、"不良" や "劣悪" カテゴリに入っているウィジェットの扱い方を見ていきます。

+ +

{{PreviousMenuNext("Learn/Forms/Other_form_controls","Learn/Forms/Advanced_form_styling","Learn/Forms")}}

+ +

このモジュール

+ +

E

+ + + +

上級トピック

+ +

セクション

+ + diff --git a/files/ja/learn/forms/the_native_form_widgets/index.html b/files/ja/learn/forms/the_native_form_widgets/index.html deleted file mode 100644 index f0ddcdc09d..0000000000 --- a/files/ja/learn/forms/the_native_form_widgets/index.html +++ /dev/null @@ -1,339 +0,0 @@ ---- -title: 基本的なネイティブフォームコントロール -slug: Learn/Forms/The_native_form_widgets -tags: - - Example - - Forms - - Guide - - HTML - - Intermediate - - Web -translation_of: Learn/Forms/Basic_native_form_controls ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/Forms/How_to_structure_a_web_form", "Learn/Forms/HTML5_input_types", "Learn/Forms")}}
- -

直前の記事では、機能的なウェブフォームの例をマークアップし、いくつかのフォームコントロールとよくある構造要素を導入し、アクセシビリティのベストプラクティスを見てきました。次にさまざまなフォームコントロールやウィジェットの機能を詳しく見ていきます — 色々な種類のデータを集めるのにどんなオプションが使えるのかを見ていきます。とりわけこの記事では、ウェブの初期からある全てのブラウザーで利用できる、オリジナルのフォームコントロールを見ていきます。

- - - - - - - - - - - - -
前提条件:基本的なコンピューターリテラシーと、基本的な HTML の理解
目的:データを収集するためにブラウザーで使用できるネイティブフォームウィジェットの種類と、それらを HTML を使用して実装する方法を理解する。
- -

{{HTMLelement('form')}}, {{HTMLelement('fieldset')}}, {{HTMLelement('legend')}}, {{HTMLelement('textarea')}}, {{HTMLelement('label')}}, {{HTMLelement('button')}},  {{HTMLelement('input')}}といったフォーム要素については既に見てきました。この記事では次を網羅します:

- - - -
-

: この記事で説明されている機能のほとんどは、ブラウザー間で幅広くサポートされています。これに対する例外に注意しましょう。より正確な詳細が必要な場合は、HTML フォーム要素のリファレンス、特に広範囲にわたる <input> 型の参照を参照してください。

-
- -

テキスト入力フィールド

- -

テキスト {{htmlelement("input", "入力")}} フィールドは最も基本的なフォームウィジェットです。これらはユーザーがあらゆる種類のデータを入力できるとても便利な方法です。

- -
-

: HTML フォームのテキストフィールドは単純なプレーンテキストの入力コントロールです。つまり、これらを使ってリッチエディット (太字、斜体など) を実行することはできません。見かけるすべてのリッチテキストエディタは、HTML、CSS、および JavaScript で作成されたカスタムウィジェットです。

-
- -

すべてのテキストフィールドに共通する動作があります:

- - - -
-

: {{htmlelement("input")}} 要素は、type 属性によってさまざまなフォームとなるため、、HTML要素の中でも特別です。単一行のテキストフィールド、テキスト入力のないコントロール、時間と日付のコントロール、チェックボックス、カラーピッカー、ボタンといったテキスト入力のないコントロールなど、ほとんどのタイプのフォームウィジェットの作成に使用されます。

-
- -

単一行のテキストフィールド

- -

単一行のテキストフィールドは、{{htmlattrxref("type","input")}} 属性値が text に設定されている {{HTMLElement("input")}} 要素を使用するか、{{htmlattrxref("type","input")}} 属性を指定しない場合( text がデフォルト値になり)に作成されます。{{htmlattrxref("type","input")}} 属性に指定した値がブラウザーに認識されない場合 (たとえば type="color" を指定してブラウザーがネイティブの色ピッカーをサポートしていない場合)、この属性の値のテキストは代替値になります。

- -
-

: GitHub の single-line-text-fields.html に、すべての単一行テキストフィールドタイプの例があります (こちらも参照してください)。

-
- -

これは基本的な単一行のテキストフィールドの例です。

- -
<input type="text" id="comment" name="comment" value="I'm a text field">
- -

単一行のテキストフィールドは、ひとつだけ厳密な制約があります: 改行を含むテキストを入力した場合、ブラウザーはデータを送信する前に改行を取り除きます。

- -

下記のスクリーンショットは macOS での Firefox 71 と Safari と Windows 10 の Chrome 79 と Edge 18 にて、既定の、フォーカスされた、無効にされたテキスト入力を示しています。

- -

Screenshot of the disabled attribute and default :focus styles on a text input in Firefox, Safari, Chrome and Edge.

- -
-

HTML5 では {{htmlattrxref("type","input")}} 属性に専用の値を追加することで、基本的な単一行のテキストフィールドを拡張しています。これらの値もやはり {{HTMLElement("input")}} 要素を単一行のテキストフィールドにしますが、フィールドに対して追加の制約や機能を付加します。

-
- -

パスワードフィールド

- -

このタイプのフィールドは、{{htmlattrxref("type","input")}} 属性の値 password を使用して設定できます:

- -
<input type="password" id="pwd" name="pwd">
- -

password の値は入力したテキストに対する特別な制約は付加しませんが、フィールドの値を隠します(例、ドットやアスタリスク)ので読むことができません。

- -

これはユーザーインターフェイスの機能でしかないことに注意してください。テキストは JavaScript を使用してあなた自身でエンコードしなければ、平文で送信されてしまい、セキュリティには良くありません — 悪い組織がデータを遮ってパスワードや、クレジットカードデータや、送信したあらゆるものを盗むことがあります。ユーザーからこれを保護するためにはフォームを含むあらゆるページをセキュア通信でホストし (つまり https:// ... アドレスにて) 、データ送信前に暗号化することです。

- -

最近のブラウザーは、安全でない接続を介してフォームデータを送信することによるセキュリティへの影響を認識しており、ユーザーが安全でないフォームを使用しないように警告を実装しています。Firefox が実装しているものの詳細については、安全でないパスワードをご覧ください。

- -

隠しコンテンツ

- -

もう1つのオリジナルなテキストコントロールは hidden 入力タイプです。これは他のフォームデータとともにサーバー送信されるがユーザーからは見えないデータを持つのに使われています — 例えば命令を発行するときにサーバーにタイムスタンプを送りたい場合。これは隠れているので、ユーザーが見ることも、意図せずに値を編集することもなく、フォーカスを得ることもないしスクリーンリーダーが気づくこともありません。

- -
<input type="hidden" id="timestamp" name="timestamp" value="1286705410">
-
- -

このような要素を作成する場合は、name 属性と value 属性の設定が必要です。この値は JavaScript にて動的にセットできます。hidden 入力タイプには関連したラベルはありません。

- -

その他のテキストタイプ、{{HTMLElement("input/search", "search")}}, {{HTMLElement("input/url", "url")}}, と{{HTMLElement("input/tel", "tel")}}, は HTML5 で追加されました。これは次のチュートリアルの「HTML5 入力タイプ」にて網羅されます。

- -

チェック可能アイテム:チェックボックスとラジオボタン

- -

チェック可能アイテムは、そのものや、関連したラベルをクリックすることで状態を変更できるコントロールです。チェック可能アイテムは 2 種類あります: チェックボックスとラジオボタンです。どちらもデフォルトでチェックするかを示すために、checked 属性を使用します。

- -

これらのウィジェットは、他のフォームウィジェットと同じようには動作しない点が特筆されます。ほとんどのフォームウィジェットではフォームを送信すると、name 属性を持つすべてのウィジェットは値がなくても送信します。チェック可能アイテムでは、それらがチェックされている場合にのみ値を送信します。チェックされていない場合は、name も含めて何も送信しません。チェックされているが値がない場合、name が on という値で送信されます。

- -
-

: このセクションの例は、checkable-items.html として GitHub にあります (こちらも参照してください)。

-
- -

最大限のユーザービリティ/アクセシビリティを実現するために、関連項目の各リストを {{htmlelement("fieldset")}} で囲み、リストの全体的な説明を示す {{htmlelement("legend")}} で囲むことをお勧めします。{{htmlelement("label")}}/{{htmlelement("input")}} 要素の個々のペアは、それぞれ独自のリスト項目 (または同様のもの) に含める必要があります。関連した {{htmlelement('label')}} はラジオボタンやチェックボックスの直後に、{{htmlelement("legend")}}の中身にラジオボタンやチェックボックスのグループの説明が置かれます。これは上の例に示されています。

- -

チェックボックス

- -

チェックボックスは、type 属性を {{HTMLElement("input/checkbox", "checkbox")}} に設定した {{HTMLElement("input")}} 要素で作成します。

- -
<input type="checkbox" checked id="carrots" name="carrots" value="carrots">
- -

checked 属性を含んだチェックボックスはページ読み込み時に自動的にチェックされます。チェックボックスまたはその関連ラベルをチェックするとチェックボックスのオン/オフがトグルされます。

- -

下記のスクリーンショットは macOS での Firefox 71 と Safari と Windows 10 の Chrome 79 と Edge 18 にて、既定の、フォーカスされた、無効にされたチェックボックスを示しています。

- -

Default, focused and disabled Checkboxes in Firefox 71 and Safari 13 on Mac and Chrome 79 and Edge 18 on Windows 10

- -
-

: checked 属性のあるあらゆるチェックボックスやラジオボタンには、チェックされていない場合でも、対応する {{cssxref(':default')}} 仮想クラスがあります。現在チェックされているものには{{cssxref(':checked')}} 仮想クラスがあります。

-
- -

チェックボックスのオンオフ性質により、チェックボックスは、規定のチェックボックスを拡張してトグルスイッチのように見えるボタンを作っている開発者やデザイナーにとって、トグルボタンとして考えられます。ここで動作する例を 見ることができます(ソースコードも見られます)。

- -

ラジオボタン

- -

ラジオボタンは、{{htmlattrxref("type","input")}} 属性を radio に設定した {{HTMLElement("input")}} 要素で作成します。

- -
<input type="radio" checked id="soup" name="meal">
- -

いくつかのラジオボタンをまとめることができます。{{htmlattrxref("name","input")}} 属性で同じ値を共有すると、それらのラジオボタンは同じボタングループに属するとみなされます。グループ内でボタンは同時に 1 つだけチェックできます。つまり、あるラジオボタンをチェックすると、他のラジオボタンは自動的にチェックが外れます。フォームを送信するときは、チェックしているラジオボタンのみの値を送信します。何もチェックしていない場合はラジオボタンの集まり全体が未知の状態であるとみなし、フォーム送信時は値を送信しません。

- -
<fieldset>
-  <legend>What is your favorite meal?</legend>
-  <ul>
-    <li>
-      <label for="soup">Soup</label>
-      <input type="radio" checked id="soup" name="meal" value="soup">
-    </li>
-    <li>
-      <label for="curry">Curry</label>
-      <input type="radio" id="curry" name="meal" value="curry">
-    </li>
-    <li>
-      <label for="pizza">Pizza</label>
-      <input type="radio" id="pizza" name="meal" value="pizza">
-    </li>
-  </ul>
-</fieldset>
- -

下記のスクリーンショットは macOS での Firefox 71 と Safari と Windows 10 の Chrome 79 と Edge 18 にて、チェックなしとチェックされたラジオボタン、フォーカスされた、また無効でチェックなしとチェックされたラジオボタンを示しています。

- -

Radio buttons on Firefox 71 and Safari 13 on Mac and Chrome 79 and Edge 18 on Windows 10

- -

ボタン

- -

ラジオボタンはその名に反して、実際のボタンではありません。実際のボタンを見てみましょう! ボタンを生成するには、3 種類の入力タイプがあります:

- -
-
{{原語併記("送信", "Submit")}}
-
フォームデータをサーバーに送信します。{{HTMLElement("button")}} 要素の場合、type 属性 (または type の無効な値) を省略すると、送信ボタンが表示されます。
-
{{原語併記("リセット", "Reset")}}
-
すべてのフォームウィジェットをデフォルト値にリセットします。
-
button
-
自動的な効果のないボタンで、JavaScript コードを用いてカスタマイズできるもの。
-
- -

それから、{{htmlelement("button")}} 要素それ自体もあります。これは値が submitreset または button である type 属性をとり、上記の 3 つの <input> 種別を模倣できます。この 2 つの主な違いは実際の <button> 要素の方が多くのスタイル設定できることです。

- -
-

: image 入力タイプもボタンとしてレンダリングされます。それはあとで見ます。

-
- -
-

: このセクションの例は button-examples.html として GitHub にあります (こちらも参照してください)。

-
- -

ボタンは {{HTMLElement("button")}} 要素か {{HTMLElement("input")}} 要素で作成します。どの種類のボタンを表示するかを指定するのは、{{htmlattrxref("type","input")}} 属性の値です:

- -

送信

- -
<button type="submit">
-    This a <br><strong>submit button</strong>
-</button>
-
-<input type="submit" value="This is a submit button">
- -

リセット

- -
<button type="reset">
-    This a <br><strong>reset button</strong>
-</button>
-
-<input type="reset" value="This is a reset button">
- -

無名

- -
<button type="button">
-    This an <br><strong>anonymous button</strong>
-</button>
-
-<input type="button" value="This is an anonymous button">
- -

ボタンは {{HTMLElement("button")}} 要素でも {{HTMLElement("input")}} 要素でも、常に同じ動作になります。上記のサンプルでわかるように、{{HTMLElement("button")}} 要素はラベルとして HTML コンテンツを使用できて、これは開始と終了の<button>タグの間に挿入されます。一方で{{HTMLElement("input")}} 要素は空要素です。つまり value 属性の中にラベルが挿入され、このためプレーンテキストのコンテンツのみ使用できます。

- -

下記の例は macOS での Firefox 71 と Safari と Windows 10 の Chrome 79 と Edge 18 にて、既定の、フォーカスされた、無効なボタンを示しています。

- -

Default, focused and disabled button input types in Firefox 71 and Safari 13 on Mac and Chrome 79 and Edge 18 on Windows 10

- -

画像ボタン

- -

画像ボタンコントロールは {{HTMLElement("img")}} 要素とまったく同じように表示されますが、ユーザーがクリックすると送信ボタン (前述) のように動作します。

- -

画像ボタンは、{{htmlattrxref("type","input")}} 属性を image に設定した {{HTMLElement("input")}} 要素で作成します。

- -

この要素は {{HTMLElement("img")}} 要素とまったく同じ属性をサポートして、さらにフォームボタンがサポートする属性もすべてサポートします。

- -
<input type="image" alt="Click me!" src="my-img.png" width="80" height="30" />
- -

画像ボタンをフォームの送信に使用する際にこのウィジェットは自身の値を送信しませんが、代わりに画像上でクリックした位置の X 座標と Y 座標を送信します (座標は画像に対して相対的、つまり画像の左上隅が座標 0, 0 になります)。座標は 2 つのキーと値の組として送信されます。

- - - -

サンプルをご覧ください。フォームの画像上の座標 (123, 456) でクリックすると、 get メソッド経由で送信されて、以下のような値の追加された URL が送信されます:

- -
http://foo.com?pos.x=123&pos.y=456
- -

これは "hot map" を作成するためにとても便利な手段です。これらの値がどのように送信あるいは取得されるかについては、フォームデータの送信の記事で詳しく説明します。

- -

ファイルピッカー

- -

初期のHTMLであった最後の <input> タイプがあります: ファイル入力タイプです。フォームで、ファイルをサーバーに送信できます。この特定操作については以下の記事で詳しく説明します: フォームデータの送信。ファイルピッカーウィジェットで、ユーザーは送信するファイルを 1 つ以上選択できます。

- -

ファイルピッカーウィジェットを作成するには、{{htmlattrxref("type","input")}} 属性を file に設定した {{HTMLElement("input")}} 要素を使用します。{{htmlattrxref("accept","input")}} 属性を使用して、受け入れるファイルの種類を制限できます。加えて、ユーザーが複数のファイルを選択できるようにしたい場合は、{{htmlattrxref("multiple","input")}} 属性を付加します。

- -

- -

以下の例では、画像ファイルを要求するファイルピッカーを作成しています。ユーザーは複数のファイルを指定できます。

- -
<input type="file" name="file" id="file" accept="image/*" multiple>
- -

いくつかのモバイルデバイスでは、ファイルピッカーは、次のようにキャプチャー情報を accept 属性に追加することで、端末のカメラやマイクでキャプチャーされた写真、動画、オーディオにアクセスできます:

- -
<input type="file" accept="image/*;capture=camera">
-<input type="file" accept="video/*;capture=camcorder">
-<input type="file" accept="audio/*;capture=microphone">
- -

共通属性

- -

フォームウィジェットを定義するために使用される要素の多くは、独自の属性をいくつか持っています。ただし、すべてのフォーム要素に共通の一連の属性があり、それによりウィジェットをある程度制御できます。共通属性のリストは以下のとおりです。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
属性名既定値説明
autofocusfalseこの真偽値属性を使用すると、ユーザーがページをロードするときに、たとえば別のコントロールを入力して上書きしない限り、要素に自動的に入力フォーカスするように指定できます。この属性を指定できるのは、文書内の 1 つのフォーム関連要素だけです。
disabledfalseこの真偽値属性は、ユーザーが要素と対話できないことを示します。この属性が指定されていない場合、要素はそれを含む要素 (例えば {{HTMLElement("fieldset")}}) からその設定を継承します。disabled 属性が設定されている包含要素がない場合は、その要素が有効になります。
formウィジェットが関連付けられている <form> 要素。属性の値は、同じ文書内の {{HTMLElement("form")}} 要素の id 属性でなければなりません。理論的には、フォームウィジェットを {{HTMLElement("form")}} 要素の外側に設定できます。しかし実際には、その機能をサポートするブラウザーはありません。
name要素の名前。これはフォームデータとともに送信されます。
value要素の初期値
- -

スキルをテストしましょう!

- -

この記事の最後に到着しましたが、最も大事な情報を覚えていますか? 次に進む前に、この情報を保持しているか検証するテストがあります — Test your skills: Basic controls を見てください。

- -

まとめ

- -

上で見たように、利用可能なフォーム要素には多くの異なるタイプがあります。一度にこれらの詳細の全てを覚えておく必要はありません。詳細について調べるために好きなだけこの記事に戻ることができます 。

- -

この記事では古い入力タイプをカバーしてきました — これは HTML の初期の頃に導入されたオリジナルで、すべてのブラウザーでよくサポートされます。次のセクションでは、HTML 5 で追加された新しい type 属性の値を見ていきます。

- -

{{PreviousMenuNext("Learn/Forms/How_to_structure_a_web_form", "Learn/Forms/HTML5_input_types", "Learn/Forms")}}

- -

このモジュール

- - - -

上級トピック

- - -- cgit v1.2.3-54-g00ecf