diff --git a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_1/index.md b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_1/index.md
index 44f9c81511596b..b03f2b6ddefca6 100644
--- a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_1/index.md
+++ b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_1/index.md
@@ -1,9 +1,9 @@
---
-title: Example 1
+title: 示例 1
slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
---
-这是第一个[如果构建自定义表单小部件](/zh-CN/docs/Web/Guide/HTML/Forms/How_to_build_custom_form_widgets)的代码解释事例。
+这是第一个[如何构建自定义表单控件](/zh-CN/docs/Learn/Forms/How_to_build_custom_form_controls)的代码解释事例。
## 基本状态
@@ -25,9 +25,9 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
### CSS
```css
-/* --------------- */
-/* Required Styles */
-/* --------------- */
+/*--------- */
+/* 所需的样式 */
+/* -------- */
.select {
position: relative;
@@ -51,15 +51,14 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
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 */
@@ -71,7 +70,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
box-shadow: 0 0.1em 0.2em rgba(0, 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);
}
@@ -85,7 +83,7 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
vertical-align: top;
}
-.select:after {
+.select::after {
content: "▼";
position: absolute;
z-index: 1;
@@ -96,7 +94,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
padding-top: 0.1em;
- -moz-box-sizing: border-box;
box-sizing: border-box;
text-align: center;
@@ -122,7 +119,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
- -moz-box-sizing: border-box;
box-sizing: border-box;
min-width: 100%;
@@ -141,7 +137,7 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
}
```
-### 结果
+### 基本状态结果
{{ EmbedLiveSample('基本状态', 120, 130) }}
@@ -165,9 +161,9 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
### CSS
```css
-/* --------------- */
-/* Required Styles */
-/* --------------- */
+/*--------- */
+/* 所需的样式 */
+/* -------- */
.select {
position: relative;
@@ -191,15 +187,14 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
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 */
@@ -211,7 +206,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
box-shadow: 0 0.1em 0.2em rgba(0, 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);
}
@@ -225,7 +219,7 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
vertical-align: top;
}
-.select:after {
+.select::after {
content: "▼";
position: absolute;
z-index: 1;
@@ -236,7 +230,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
padding-top: 0.1em;
- -moz-box-sizing: border-box;
box-sizing: border-box;
text-align: center;
@@ -262,7 +255,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
- -moz-box-sizing: border-box;
box-sizing: border-box;
min-width: 100%;
@@ -281,7 +273,7 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
}
```
-### 结果
+### 活动状态结果
{{ EmbedLiveSample('活动状态', 120, 130) }}
@@ -305,9 +297,9 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
### CSS
```css
-/* --------------- */
-/* Required Styles */
-/* --------------- */
+/*--------- */
+/* 所需的样式 */
+/* -------- */
.select {
position: relative;
@@ -331,15 +323,14 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
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 */
@@ -351,7 +342,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
box-shadow: 0 0.1em 0.2em rgba(0, 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);
}
@@ -365,7 +355,7 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
vertical-align: top;
}
-.select:after {
+.select::after {
content: "▼";
position: absolute;
z-index: 1;
@@ -376,7 +366,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
padding-top: 0.1em;
- -moz-box-sizing: border-box;
box-sizing: border-box;
text-align: center;
@@ -402,7 +391,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
- -moz-box-sizing: border-box;
box-sizing: border-box;
min-width: 100%;
@@ -421,6 +409,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_1
}
```
-### 结果
+### 展开状态结果
{{ EmbedLiveSample('展开状态', 120, 130) }}
diff --git a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_2/index.md b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_2/index.md
index 9b6e35b8c417c4..1b8a8cf421ccef 100644
--- a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_2/index.md
+++ b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_2/index.md
@@ -1,9 +1,9 @@
---
-title: Example 2
+title: 示例 2
slug: Learn/Forms/How_to_build_custom_form_controls/Example_2
---
-这是解释 [如何构建自定义表单小部件](/zh-CN/docs/HTML/Forms/How_to_build_custom_form_widgets)的第二个示例。
+这是解释[如何构建自定义表单控件](/zh-CN/docs/Learn/Forms/How_to_build_custom_form_controls)的第二个示例。
## 使用 JS
@@ -29,7 +29,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_2
Apple
-
```
@@ -44,9 +43,9 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_2
overflow: hidden;
}
-/* --------------- */
-/* Required Styles */
-/* --------------- */
+/*--------- */
+/* 所需的样式 */
+/* -------- */
.select {
position: relative;
@@ -70,15 +69,14 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_2
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 */
@@ -90,7 +88,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_2
box-shadow: 0 0.1em 0.2em rgba(0, 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);
}
@@ -104,7 +101,7 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_2
vertical-align: top;
}
-.select:after {
+.select::after {
content: "▼";
position: absolute;
z-index: 1;
@@ -115,7 +112,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_2
padding-top: 0.1em;
- -moz-box-sizing: border-box;
box-sizing: border-box;
text-align: center;
@@ -141,7 +137,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_2
box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
- -moz-box-sizing: border-box;
box-sizing: border-box;
min-width: 100%;
@@ -163,8 +158,8 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_2
### JavaScript
```js
-window.addEventListener("load", function () {
- var form = document.querySelector("form");
+window.addEventListener("load", () => {
+ const form = document.querySelector("form");
form.classList.remove("no-widget");
form.classList.add("widget");
@@ -199,7 +194,6 @@ window.addEventListener("load", function () {
Apple
-
```
diff --git a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_3/index.md b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_3/index.md
index 1081b79b78b885..722b41335b7952 100644
--- a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_3/index.md
+++ b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_3/index.md
@@ -3,11 +3,11 @@ title: Example 3
slug: Learn/Forms/How_to_build_custom_form_controls/Example_3
---
-这是解释 [如何构建自定义表单小部件](/zh-CN/docs/HTML/Forms/How_to_build_custom_form_widgets) 的第三个示例。
+这是解释[如何构建自定义表单控件](/zh-CN/docs/Learn/Forms/How_to_build_custom_form_controls)的第三个示例。
-## Change states
+## 改变状态
-### HTML 内容
+### HTML
```html
```
-### CSS 内容
+### CSS
```css
.widget select,
@@ -43,9 +43,9 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_3
overflow: hidden;
}
-/* --------------- */
-/* Required Styles */
-/* --------------- */
+/*--------- */
+/* 所需的样式 */
+/* -------- */
.select {
position: relative;
@@ -69,15 +69,14 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_3
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 */
@@ -89,7 +88,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_3
box-shadow: 0 0.1em 0.2em rgba(0, 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);
}
@@ -103,7 +101,7 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_3
vertical-align: top;
}
-.select:after {
+.select::after {
content: "▼";
position: absolute;
z-index: 1;
@@ -114,7 +112,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_3
padding-top: 0.1em;
- -moz-box-sizing: border-box;
box-sizing: border-box;
text-align: center;
@@ -140,7 +137,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_3
box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
- -moz-box-sizing: border-box;
box-sizing: border-box;
min-width: 100%;
@@ -159,25 +155,17 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_3
}
```
-### JavaScript 内容
+### JavaScript
```js
// ------- //
-// 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");
+ const optList = select.querySelector(".optList");
optList.classList.add("hidden");
select.classList.remove("active");
@@ -191,63 +179,69 @@ function activeSelect(select, selectList) {
}
function toggleOptList(select, show) {
- var optList = select.querySelector(".optList");
+ const optList = select.querySelector(".optList");
optList.classList.toggle("hidden");
}
function highlightOption(select, option) {
- var optionList = select.querySelectorAll(".option");
+ const optionList = select.querySelectorAll(".option");
- optionList.forEach(function (other) {
+ optionList.forEach((other) => {
other.classList.remove("highlight");
});
option.classList.add("highlight");
}
-// ------------- //
-// Event binding //
-// ------------- //
+// ------- //
+// 事件绑定 //
+// ------- //
-window.addEventListener("load", function () {
- var form = document.querySelector("form");
+window.addEventListener("load", () => {
+ const form = document.querySelector("form");
form.classList.remove("no-widget");
form.classList.add("widget");
});
-window.addEventListener("load", function () {
- var selectList = document.querySelectorAll(".select");
+window.addEventListener("load", () => {
+ const selectList = document.querySelectorAll(".select");
- selectList.forEach(function (select) {
- var optionList = select.querySelectorAll(".option");
+ selectList.forEach((select) => {
+ const optionList = select.querySelectorAll(".option");
- optionList.forEach(function (option) {
- option.addEventListener("mouseover", function () {
+ optionList.forEach((option) => {
+ option.addEventListener("mouseover", () => {
highlightOption(select, option);
});
});
select.addEventListener(
"click",
- function (event) {
+ (event) => {
toggleOptList(select);
},
false,
);
- select.addEventListener("focus", function (event) {
+ select.addEventListener("focus", (event) => {
activeSelect(select, selectList);
});
- select.addEventListener("blur", function (event) {
+ select.addEventListener("blur", (event) => {
deactivateSelect(select);
});
+
+ select.addEventListener("keyup", (event) => {
+ if (event.key === "Escape") {
+ deactivateSelect(select);
+ }
+ });
});
});
```
### 结果
-{{ EmbedLiveSample('Change_states') }}
+{{ EmbedLiveSample('改变状态') }}
diff --git a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_4/index.md b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_4/index.md
index 35b15b27158193..d525a374019bad 100644
--- a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_4/index.md
+++ b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_4/index.md
@@ -3,7 +3,7 @@ title: Example 4
slug: Learn/Forms/How_to_build_custom_form_controls/Example_4
---
-这是解释 [如何构建自定义表单小部件](/zh-CN/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets) 的第四个示例。
+这是解释[如何构建自定义表单控件](/zh-CN/docs/Learn/Forms/How_to_build_custom_form_controls)的第四个示例。
## 改变状态
@@ -43,9 +43,9 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_4
overflow: hidden;
}
-/* --------------- */
-/* Required Styles */
-/* --------------- */
+/*--------- */
+/* 所需的样式 */
+/* -------- */
.select {
position: relative;
@@ -69,15 +69,14 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_4
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 */
@@ -89,7 +88,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_4
box-shadow: 0 0.1em 0.2em rgba(0, 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);
}
@@ -103,7 +101,7 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_4
vertical-align: top;
}
-.select:after {
+.select::after {
content: "▼";
position: absolute;
z-index: 1;
@@ -114,7 +112,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_4
padding-top: 0.1em;
- -moz-box-sizing: border-box;
box-sizing: border-box;
text-align: center;
@@ -140,7 +137,6 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_4
box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
- -moz-box-sizing: border-box;
box-sizing: border-box;
min-width: 100%;
@@ -163,21 +159,13 @@ slug: Learn/Forms/How_to_build_custom_form_controls/Example_4
```js
// ------- //
-// 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");
+ const optList = select.querySelector(".optList");
optList.classList.add("hidden");
select.classList.remove("active");
@@ -191,15 +179,15 @@ function activeSelect(select, selectList) {
}
function toggleOptList(select, show) {
- var optList = select.querySelector(".optList");
+ const optList = select.querySelector(".optList");
optList.classList.toggle("hidden");
}
function highlightOption(select, option) {
- var optionList = select.querySelectorAll(".option");
+ const optionList = select.querySelectorAll(".option");
- optionList.forEach(function (other) {
+ optionList.forEach((other) => {
other.classList.remove("highlight");
});
@@ -207,9 +195,9 @@ function highlightOption(select, option) {
}
function updateValue(select, index) {
- var nativeWidget = select.previousElementSibling;
- var value = select.querySelector(".value");
- var optionList = select.querySelectorAll(".option");
+ const nativeWidget = select.previousElementSibling;
+ const value = select.querySelector(".value");
+ const optionList = select.querySelectorAll(".option");
nativeWidget.selectedIndex = index;
value.innerHTML = optionList[index].innerHTML;
@@ -217,74 +205,76 @@ function updateValue(select, index) {
}
function getIndex(select) {
- var nativeWidget = select.previousElementSibling;
+ const nativeWidget = select.previousElementSibling;
return nativeWidget.selectedIndex;
}
// ------------- //
-// Event binding //
+// 事件绑定 //
// ------------- //
-window.addEventListener("load", function () {
- var form = document.querySelector("form");
+window.addEventListener("load", () => {
+ const form = document.querySelector("form");
form.classList.remove("no-widget");
form.classList.add("widget");
});
-window.addEventListener("load", function () {
- var selectList = document.querySelectorAll(".select");
+window.addEventListener("load", () => {
+ const selectList = document.querySelectorAll(".select");
- selectList.forEach(function (select) {
- var optionList = select.querySelectorAll(".option");
+ selectList.forEach((select) => {
+ const optionList = select.querySelectorAll(".option");
- optionList.forEach(function (option) {
- option.addEventListener("mouseover", function () {
+ optionList.forEach((option) => {
+ option.addEventListener("mouseover", () => {
highlightOption(select, option);
});
});
- select.addEventListener("click", function (event) {
+ select.addEventListener("click", (event) => {
toggleOptList(select);
});
- select.addEventListener("focus", function (event) {
+ select.addEventListener("focus", (event) => {
activeSelect(select, selectList);
});
- select.addEventListener("blur", function (event) {
+ select.addEventListener("blur", (event) => {
deactivateSelect(select);
});
});
});
-window.addEventListener("load", function () {
- var selectList = document.querySelectorAll(".select");
+window.addEventListener("load", () => {
+ const selectList = document.querySelectorAll(".select");
- selectList.forEach(function (select) {
- var optionList = select.querySelectorAll(".option"),
- selectedIndex = getIndex(select);
+ selectList.forEach((select) => {
+ const optionList = select.querySelectorAll(".option");
+ const selectedIndex = getIndex(select);
select.tabIndex = 0;
select.previousElementSibling.tabIndex = -1;
updateValue(select, selectedIndex);
- optionList.forEach(function (option, index) {
- option.addEventListener("click", function (event) {
+ optionList.forEach((option, index) => {
+ option.addEventListener("click", (event) => {
updateValue(select, index);
});
});
- select.addEventListener("keyup", function (event) {
- var length = optionList.length,
- index = getIndex(select);
+ select.addEventListener("keyup", (event) => {
+ let index = getIndex(select);
- if (event.keyCode === 40 && index < length - 1) {
+ if (event.key === "Escape") {
+ deactivateSelect(select);
+ }
+ if (event.key === "ArrowDown" && index < optionList.length - 1) {
index++;
}
- if (event.keyCode === 38 && index > 0) {
+ if (event.key === "ArrowUp" && index > 0) {
index--;
}
diff --git a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_5/index.md b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_5/index.md
new file mode 100644
index 00000000000000..e938053a23f161
--- /dev/null
+++ b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/example_5/index.md
@@ -0,0 +1,286 @@
+---
+title: 示例 5
+slug: Learn/Forms/How_to_build_custom_form_controls/Example_5
+page-type: learn-module-chapter
+---
+
+这是解释[如何构建自定义表单控件](/zh-CN/docs/Learn/Forms/How_to_build_custom_form_controls)的最后一个示例。
+
+## 改变状态
+
+### HTML
+
+```html
+
+```
+
+### CSS
+
+```css
+.widget select,
+.no-widget .select {
+ position: absolute;
+ left: -5000em;
+ height: 0;
+ overflow: hidden;
+}
+
+/*--------- */
+/* 所需的样式 */
+/* -------- */
+
+.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;
+}
+
+/* ------- */
+/* 美化样式 */
+/* ------- */
+
+.select {
+ font-size: 0.625em; /* 10px */
+ font-family: Verdana, Arial, sans-serif;
+
+ 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, 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;
+}
+
+.select::after {
+ content: "▼";
+ position: absolute;
+ z-index: 1;
+ height: 100%;
+ width: 2em; /* 20px */
+ top: 0;
+ right: 0;
+
+ padding-top: 0.1em;
+
+ box-sizing: border-box;
+
+ text-align: center;
+
+ border-left: 0.2em solid #000;
+ border-radius: 0 0.1em 0.1em 0;
+
+ background-color: #000;
+ color: #fff;
+}
+
+.select .optList {
+ z-index: 2;
+
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ background: #f0f0f0;
+ border: 0.2em solid #000;
+ border-top-width: 0.1em;
+ border-radius: 0 0 0.4em 0.4em;
+
+ box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
+
+ box-sizing: border-box;
+
+ min-width: 100%;
+ max-height: 10em; /* 100px */
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.select .option {
+ padding: 0.2em 0.3em;
+}
+
+.select .highlight {
+ background: #000;
+ color: #ffffff;
+}
+```
+
+### JavaScript
+
+```js
+// ------- //
+// 函数定义 //
+// ------- //
+
+function deactivateSelect(select) {
+ if (!select.classList.contains("active")) return;
+
+ const 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) {
+ const optList = select.querySelector(".optList");
+
+ optList.classList.toggle("hidden");
+}
+
+function highlightOption(select, option) {
+ const optionList = select.querySelectorAll(".option");
+
+ optionList.forEach((other) => {
+ other.classList.remove("highlight");
+ });
+
+ option.classList.add("highlight");
+}
+
+function updateValue(select, index) {
+ const nativeWidget = select.previousElementSibling;
+ const value = select.querySelector(".value");
+ const optionList = select.querySelectorAll(".option");
+
+ optionList.forEach((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) {
+ const nativeWidget = select.previousElementSibling;
+
+ return nativeWidget.selectedIndex;
+}
+
+// ------------- //
+// 事件绑定 //
+// ------------- //
+
+window.addEventListener("load", () => {
+ const form = document.querySelector("form");
+
+ form.classList.remove("no-widget");
+ form.classList.add("widget");
+});
+
+window.addEventListener("load", () => {
+ const selectList = document.querySelectorAll(".select");
+
+ selectList.forEach((select) => {
+ const optionList = select.querySelectorAll(".option");
+ const selectedIndex = getIndex(select);
+
+ select.tabIndex = 0;
+ select.previousElementSibling.tabIndex = -1;
+
+ updateValue(select, selectedIndex);
+
+ optionList.forEach((option, index) => {
+ option.addEventListener("mouseover", () => {
+ highlightOption(select, option);
+ });
+
+ option.addEventListener("click", (event) => {
+ updateValue(select, index);
+ });
+ });
+
+ select.addEventListener("click", (event) => {
+ toggleOptList(select);
+ });
+
+ select.addEventListener("focus", (event) => {
+ activeSelect(select, selectList);
+ });
+
+ select.addEventListener("blur", (event) => {
+ deactivateSelect(select);
+ });
+
+ select.addEventListener("keyup", (event) => {
+ let index = getIndex(select);
+
+ if (event.key === "Escape") {
+ deactivateSelect(select);
+ }
+ if (event.key === "ArrowDown" && index < optionList.length - 1) {
+ index++;
+ }
+ if (event.key === "ArrowUp" && index > 0) {
+ index--;
+ }
+
+ updateValue(select, index);
+ });
+ });
+});
+```
+
+### 结果
+
+{{ EmbedLiveSample('改变状态') }}
diff --git a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/index.md b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/index.md
index 8608f9b9e078ed..c5463f394658c6 100644
--- a/files/zh-cn/learn/forms/how_to_build_custom_form_controls/index.md
+++ b/files/zh-cn/learn/forms/how_to_build_custom_form_controls/index.md
@@ -5,80 +5,89 @@ slug: Learn/Forms/How_to_build_custom_form_controls
{{LearnSidebar}}
-在许多情况下,可用的原生 HTML 表单控件是不够的。如果要在某些控件(例如 {{HTMLElement("select")}} 元素)上执行[高级样式](/zh-CN/docs/Learn/Forms/Advanced_form_styling),或者如果要提供自定义表现,则别无选择,只能构建自己的控件。
+在许多情况下,可用的原生 HTML 表单控件是不够的。如果要在某些控件(例如 {{HTMLElement("select")}} 元素)上[设置高级样式](/zh-CN/docs/Learn/Forms/Advanced_form_styling),或者如果要提供自定义行为,你就需要考虑构建自己的控件。
-在本文中,我们会看到如何构建这样的组件。为此,我们将使用这样一个例子:重建 {{HTMLElement("select")}} 元素。
+在本文中,我们会看到如何构建自定义控件。为此,我们将使用这样一个示例:重建 {{HTMLElement("select")}} 元素。我们还将讨论如何构建、何时构建自定义控件、构建是否存在意义,以及构建控件的相关注意事项。
-> **备注:** 我们将专注于构建小部件,而不是怎样让代码更通用或可复用;那会涉及一些非基础的 JavaScript 代码和未知环境下的 DOM 操作,这超过了这篇文章的范围。
+> **备注:** 我们将专注于构建控件,而不是怎样让代码更通用或可复用;那会涉及一些非基础的 JavaScript 代码和未知上下文下的 DOM 操作,这超过了这篇文章的范畴。
## 设计、结构和语义
-在构建一个自定义控件之前,首先你要确切的知道你要什么。这将为您节省宝贵的时间。特别地,清楚地定义控件的所有状态非常重要。为了做到这一点,从状态和行为表现都众所周知的现有小控件开始是很好的选择,这样你可以轻松的尽量模仿这些控件。
+在构建一个自定义控件之前,首先你要确切的知道你要什么。这将为你节省宝贵的时间。特别地,清楚地定义控件的所有状态非常重要。要做到这一点,从状态和行为表现都为人所熟知的现有控件开始是很好的选择,这样你可以充分地模仿这些控件。
在我们的示例中,我们将重建 {{HTMLElement("select")}} 元素,这是我们希望实现的结果:
![选择框的三种状态](custom-select.png)
-上面图片显示了我们控件的三个主要状态:正常状态(左); 活动状态(中)和打开状态(右)。
+上面图片显示了我们控件的三个主要状态:正常状态(左)、活动状态(中)和展开状态(右)。
-在行为方面,我们希望我们的控件像任何原生控件一样对鼠标和键盘都可用。让我们从定义控件如何到达每个状态开始:
+在行为方面,我们正在重建原始 HTML 元素,因此它应该具有与原生 HTML 元素相同的行为和语义。我们要求我们的控件可以通过鼠标和键盘进行使用,并且可以被屏幕阅读器所识别,就像任何原生控件一样。首先让我们定义控件如何进入每种状态:
-- 以下情况控件将会呈现正常状态:
+**在以下情况下,控件处于正常状态:**
- - 页面加载
- - 控件处于活动状态,但用户点击控件以外的任何位置
- - 控件是活动状态,但用户使用键盘将焦点移动到另一个小部件
+- 页面加载。
+- 控件处于活动状态,但用户点击了控件以外的任何位置。
+- 控件处于活动状态,但用户使用键盘(例如 Tab 键)将焦点移动到另一个控件。
- > **备注:** 在页面上移动焦点通常是通过按 Tab 键来完成的,但这并不是哪都通用的标准。例如,在 Safari 中页面上的链接间的循环切换默认下是通过使用[组合键 Option + Tab](http://www.456bereastreet.com/archive/200906/enabling_keyboard_navigation_in_mac_os_x_web_browsers/)完成的。
+**在以下情况下,控件处于活动状态:**
-- 以下情况控件将会呈现活动状态:
+- 用户点击或在触摸屏上触摸控件。
+- 用户按下 tab 键使控件获得了焦点。
+- 控件处于展开状态然后用户点击控件。
- - 用户点击
- - 用户按下 tab 让控件获得了焦点。
- - 控件呈现打开状态然后用户点击控件。
+**在以下情况下,控件处于展开状态:**
-- 以下情况控件将会呈现打开状态:
+- 控件处于非展开状态时被用户点击。
- - 控件在非打开状态时被用户点击。
+我们知道如何改变状态后,定义如何改变控件的值同样重要:
-我们知道如何改变状态后,定义如何改变小工具的值还很重要:
+**在以下情况下,其值将会被改变:**
-- 以下情况控件的值将会被改变:
+- 控件在展开状态下用户点击一个选项。
+- 控件在活动状态下用户按下键盘的上/下方向键。
- - 控件在打开状态下用户点击一个选项
- - 控件在活动状态下用户按下键盘上方向键或者下方向键
+**在以下情况下,其值不会被改变:**
+
+- 在选择第一个选项时,用户按下键盘的上方向键。
+- 在选择最后一个选项时,用户按下键盘的下方向键。
最后,让我们定义控件的选项将要怎么表现:
-- 当控件在打开状态时,被选中的选项将被突出显示
-- 当鼠标悬停在某个选项上时,该选项将被突出显示,并且之前突出显示的选项将返回正常的状态
+- 当控件处于展开状态时,被选中的选项将被突出显示
+- 当鼠标悬停在某个选项上时,该选项将被突出显示,并且之前突出显示的选项将返回其正常的状态
+
+对于我们的示例的目的,我们将就此结束;但是,如果你是一个认真的读者,你会注意到我们省略了一些东西,例如,你认为用户在控件处于展开状态时点击 tab 键会发生什么?答案是:_什么也不会发生_。好吧,似乎很明显这就是正确的行为,但事实是,因为在我们的规范中没有定义这种情况,我们很容易忽略这种行为。在团队环境中尤其是这样,因为设计控件行为的人与实现的人通常是不同的。
+
+另外一个有趣的例子是:当控件处于展开状态时,用户按下键盘上方向键和下方向键将会发生什么?这个问题有些棘手,如果你认为活动状态和展开状态是完全不同的,那么答案又是“什么都不会发生”,因为我们没有定义任何在展开状态下键盘的交互行为。从另一个方面看,如果你认为活动状态和展开状态是有重叠的部分,那么控件的值可能会改变,但是被选中的选项肯定不会相应的进行突出显示,同样是因为我们没有定义在控件展开状态下的任何键盘交互事件(我们仅仅定义了控件打开会发生什么,而没有定义在其打开后会发生什么)。
+
+我们必须进一步思考:按退出键会发生什么?按下 Esc 键会关闭一个打开的选择框。记住,如果你想要提供与现有的原生 {{htmlelement('select')}} 相同的功能,那么它应该对所有用户都有相同的行为,不论是键盘、鼠标、触摸、屏幕阅读器,还是其他任何输入设备。
-对于我们的例子的目的,我们将就此结束;但是,如果你是一个认真的读者,你会注意到我们省略了一些东西,例如,你认为用户在小部件处于打开状态时点击 tab 键会发生什么?答案是:什么也不会发生。好吧,似乎很明显这就是正确的行为,但事实是,因为在我们的规范中没有定义这种情况,我们很容易忽略这种行为。在团队环境中尤其是这样,因为设计小部件行为的人与实现的人通常是不同的。
+在我们的示例中,规范的缺失是显而易见的,所以我们将着手处理它们,但是对于一些没有人想到去定义正确行为的控件而言,这的确是一个问题。所以在元素(例如 {{htmlelement('select')}})标准化阶段,规范作者花费了大量的时间来定义每个输入设备每个用例的所有涉及的交互。创建新的控件并不容易,特别是你正在创建以前从未做过的东西,没有人知道其预期的行为和相关的交互是什么。至少 select 已经完成了这些设计,所以我们知道它应该如何表现!
-另外一个有趣的例子是:当小部件处于打开状态时,用户按下键盘上方向键和下方向键将会发生什么?这个问题有些棘手,如果你认为活动状态和打开状态是完全不同的,那么答案就是“什么都不会发生”,因为我们没有定义任何在打开状态下键盘的交互行为。从另一个方面看,如果你认为活动状态和打开状态是有重叠的部分,那么控件的值可能会改变,但是被选中的选项肯定不会相应的进行突出显示,同样是因为我们没有定义在控件打开状态下的任何键盘交互事件(我们仅仅定义了控件打开会发生什么,而没有定义在其打开后会发生什么)
+设计新的交互方式只是行业中重要参与者的一种选择,他们有足够的影响力来推动他们创建的交互方式成为标准。例如,Apple 于 2001 年在 iPod 中推出了滚轮。他们拥有足够的市场份额,而成功推出了一种全新的设备交互方式,这是大多数设备公司无法做到的。
-在我们的例子中,缺失的规范是显而易见的,所以我们将着手处理他们,但是对于一些没有人想到去定义正确行为的小部件而言,这的确是一个问题。所以在设计阶段花费时间是值得的,因为如果你定义的行为不够好,或者忘记定义了一个行为,那么在用户开始实际使用时,将会很难去重新定义它们。如果你在定义时有疑问,请征询他人的意见,如果你有预算,请不要犹豫的去进行[用户可用性测试](https://zh.wikipedia.org/wiki/可用性测试),这个过程被称为用户体验设计(UX Design),如果你想要深入的学习相关的内容,请查阅下面这些有用资源:
+最好不要发明新的用户交互方式。对于你添加的任何交互方式,在设计阶段花费时间至关重要;如果你对一种行为的定义不够合适,或者忘记定义了某种行为,那么在用户习惯之后,将很难去重新定义它们。如果你在定义时有疑问,请征询他人的意见,如果你有预算,请不要犹豫去进行[用户可用性测试](https://zh.wikipedia.org/wiki/可用性测试),这个过程被称为用户体验设计(UX Design),如果你想要深入的学习相关的内容,请查阅下面这些有用资源:
-- [UXMatters.com](http://www.uxmatters.com/)
-- [UXDesign.com](http://uxdesign.com/)
-- [The UX Design section of SmashingMagazine](http://uxdesign.smashingmagazine.com/)
+- [UXMatters.com](https://www.uxmatters.com/)
+- [UXDesign.com](https://uxdesign.com/)
+- [SmashingMagazine 用户体验设计部分](https://www.smashingmagazine.com/)
-> **备注:** 另外,在绝大多数系统中,还有一种方法能够打开{{HTMLElement("select")}}元素来观察其所有的选项(这和用鼠标点击{{HTMLElement("select")}}元素是一样的)。通过 Windows 下的 Alt + 向下箭头实现,在我们的例子中没有实现---但是这样做会很方便,因为鼠标点击事件就是由该原理实现的。
+> **备注:** 此外,在绝大多数系统中,还有一种方法能够打开 {{HTMLElement("select")}} 元素来观察其所有的选项(这和用鼠标点击 {{HTMLElement("select")}} 元素是一样的)。这可以通过 Windows 下的 Alt + Down 实现。这没有在我们的示例中实现,但是这样做会很方便,因为鼠标点击(`click`)事件就是由该原理实现的。
### 定义语义化的 HTML 结构
-现在控件的基本功能已经决定了,可以开始构建自定义控件了。第一步就是去确定它的 HTML 结构并给予一些基本的语义规则。重构 {{HTMLElement("select")}} 元素需要这样做:
+现在控件的基本功能已经决定,可以开始构建自定义控件了。第一步就是去确定它的 HTML 结构并给予一些基本的语义规则。重构 {{HTMLElement("select")}} 元素需要这样做:
```html
-
-
+
Cherry
-
+
@@ -91,34 +100,38 @@ slug: Learn/Forms/How_to_build_custom_form_controls
```
-注意类名的使用:不管实际使用了哪种底层 HTML 元素,它们都标识每个相关的部分。这很重要,因为这样做能确保我们的 CSS 和 JavaScript 不会和 HTML 结构强绑定,这样我们就可以在不破坏使用小部件的代码的情况下进行实现更改。比如,如果你希望增加一个等价的{{HTMLElement("optgroup")}}元素。
+注意类名的使用:不管实际使用了哪种底层 HTML 元素,它们都标识每个相关的部分。这很重要,因为这样做能确保我们的 CSS 和 JavaScript 不会和 HTML 结构强绑定,这样我们就可以在不破坏使用控件的代码的情况下进行实现更改。比如,如果你希望增加一个等价的 {{HTMLElement("optgroup")}} 元素。
-### 使用 CSS 创建外观
+然而,类名并不提供语义值。到现在为止,屏幕阅读器的用户只能“看到”无序列表。我们后面会为其添加 ARIA 语义。
-现在我们有了控件结构,我们可以开始设计我们的控件了。构建自定义控件的重点是能够完全按照我们的期望设置它的样式。为了达到这个目的,我们将 CSS 部分的工作分为两部分:第一部分是让我们的控件表现得像一个{{HTMLElement("select")}}元素所必需的的 CSS 规则,第二部分包含了让组件看起来像我们所希望那样的精妙样式。
+## 使用 CSS 创建外观
-#### 所需的样式
+现在我们有了结构,我们可以开始设计我们的控件了。构建自定义控件的重点是能够完全按照我们的期望设置它的样式。为了达到这个目的,我们将 CSS 部分的工作分为两部分:第一部分是让我们的控件表现得像一个 {{HTMLElement("select")}} 元素所必需的的 CSS 规则,第二部分包含了让控件看起来像我们所希望那样的精妙样式。
-所需的样式是那些用以处理我们组件的三种状态的必须样式。
+### 所需的样式
+
+所需的样式是那些用以处理我们控件的三种状态的必须样式。
```css
.select {
- /* 这将为选项列表创建一个上下文定位 */
+ /* 这将为选项列表创建一个上下文定位;如果完全支持 focus-within,
+ 则将其添加到“.select:focus-within”是个更好的选择
+ */
position: relative;
- /* 这将使我们的组件成为文本流的一部分,同时又可以调整大小 */
+ /* 这将使我们的控件成为文本流的一部分,同时又可以调整大小 */
display: inline-block;
}
```
-我们需要一个额外的类 `active` 来定义我们的组件处于其激活状态时的的界面外观。因为我们的组件是可以聚焦的,我们通过{{cssxref(":focus")}} 伪类重复自定义样式来确保它们表现得一样。
+我们需要一个额外的 `active` 类来定义我们的控件处于其激活状态时的的界面外观。因为我们的控件是可以聚焦的,我们通过 {{cssxref(":focus")}} 伪类复用自定义样式来确保它们表现得一样。
```css
.select .active,
.select:focus {
outline: none;
- /* 这里的 box-shadow 属性并非必须,但确保活动状态能看出来非常重要---我们
+ /* 这里的 box-shadow 属性并非必须,但确保活动状态能看出来非常重要——我们
将其作为一个默认值,你可以随意地覆盖掉它。*/
box-shadow: 0 0 3px 1px #227755;
}
@@ -127,93 +140,85 @@ slug: Learn/Forms/How_to_build_custom_form_controls
现在,让我们处理选项列表:
```css
-/* 这里的 .select 选择器是一个糖衣语法,用来确保我们定义的类是
- 在我们的组件里的那个。 */
+/* 这里的 .select 选择器帮助我们确保定义的类是
+ 在我们的控件里的那个。 */
.select .optList {
/* 这可以确保我们的选项列表将会显示在值的下面,并且会处在
- HTML 流之外*/
+ HTML 流之外 */
position: absolute;
top: 100%;
left: 0;
}
```
-我们需要一个额外的类来处理选项列表隐藏时的情况。为了管理没有完全匹配的活动状态和打开状态之间的差异,这是有必要的。
+我们需要一个额外的类来处理选项列表隐藏时的情况。为了管理没有完全匹配的活动状态和展开状态之间的差异,这是有必要的。
```css
.select .optList.hidden {
- /* 这是一个以可访问形式隐藏列表的简单方法,
+ /* 这是一个以无障碍的形式隐藏列表的简单方法,
对无障碍我们将在最后进一步拓展 */
max-height: 0;
visibility: hidden;
}
```
-#### 美化
+> **备注:** 我们也可以使用 `transform: scale(1, 0)` 来指定选项列表的高度为零,但宽度不变。
+
+### 美化
-所以现在我们的基本功能已经就位,有趣的事情就可以开始了。下面是一个可行的简单的例子,和本文开头的截图是相对应的。不管怎样,你可以随意的体验一下看看能收获什么。
+所以现在我们的基本功能已经就位,有趣的事情就可以开始了。下面是一个可行的简单示例,这和本文开头的截图是相对应的。不管怎样,你可以随意尝试看看能想出什么。
```css
.select {
- /* 出于无障碍方面的原因,所有尺寸都会由 em 值表示
- (用来确保用户在文本模式下使用浏览器缩放时组件的可缩放性).
- 在大多数浏览器下的默认换算是 1em == 16px.
- 如果你对 em 和 px 的转换感到疑惑,请参考 http://riddle.pl/emcalc/ */
- font-size: 0.625em; /* 这个(=10px)是以 em 方式表达的这个环境里的字体大小 */
+ /* 假设的单位换算是 1em == 16px,这是大多数浏览器的默认值。
+ 如果你对 em 和 px 的转换感到疑惑,请参考 https://nekocalc.com/px-to-em-converter */
+ font-size: 0.625em; /* 这个(10px)是以 em 值表达这个上下文的字体大小 */
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 */
+ padding: 0.1em 2.5em 0.2em 0.5em;
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, 0.45); /* 0 1px 2px */
+ border: 0.2em solid #000;
+ border-radius: 0.4em;
+ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45);
- /* 第一段声明是为了不支持线性梯度填充的浏览器准备的。
- 第二段声明是因为基于 WebKit 的浏览器没有预先定义它。
- 如果你想为过时的浏览器提供支持,请参阅 http://www.colorzilla.com/gradient-editor/ */
+ /* 第一段声明是为不支持线性渐变的浏览器准备的。 */
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;
-
- vertical-align: top;
-
- /* 如果内容溢出了,最好有一个恰当的缩写。*/
white-space: nowrap;
text-overflow: ellipsis;
+ vertical-align: top;
}
```
-我们不需要一个额外的元素来设计向下的箭头,而使用{{cssxref(":after")}} 伪类来替代。然而,这也可以通过使用一张加在`select` class 上的简单的背景图像来实现。
+我们不需要一个额外的元素来设计向下箭头,而使用 {{cssxref("::after")}} 伪类替代。这也可以通过使用一张加在 `select` 类上的简单的背景图像来实现。
```css
-.select:after {
- content: "▼"; /* 我们使用了 unicode 编码的字符 U+25BC,确保设置了 charset meta 标签 */
+.select::after {
+ content: "▼"; /* 我们使用了 unicode 字符 U+25BC,请确保设置了 charset meta 标签 */
position: absolute;
z-index: 1; /* 这对于防止箭头覆盖选项列表很重要 */
top: 0;
right: 0;
- -moz-box-sizing: border-box;
box-sizing: border-box;
height: 100%;
- width: 2em; /* 20px */
- padding-top: 0.1em; /* 1px */
+ width: 2em;
+ padding-top: 0.1em;
- border-left: 0.2em solid #000; /* 2px */
- border-radius: 0 0.1em 0.1em 0; /* 0 1px 1px 0 */
+ border-left: 0.2em solid #000;
+ border-radius: 0 0.1em 0.1em 0;
background-color: #000;
color: #fff;
@@ -225,35 +230,34 @@ slug: Learn/Forms/How_to_build_custom_form_controls
```css
.select .optList {
- z-index: 2; /* 我们明确的表示选项列表会始终与向下箭头重叠 */
+ z-index: 2; /* 我们显式定义选项列表始终与向下箭头重叠 */
/* 这会重置 ul 元素的默认样式 */
list-style: none;
margin: 0;
padding: 0;
- -moz-box-sizing: border-box;
box-sizing: border-box;
- /* 这会确保即使数值比组件小,选项列表仍能变得跟组件自身一样大*/
+ /* 这会确保即使数值比控件小,选项列表仍能变得跟控件自身一样宽 */
min-width: 100%;
- /* 万一列表太长了,它的内容会从垂直方向溢出 (会自动添加一个竖向滚动条)
- 但是水平方向不会 (因为我们没有设定宽度,列表会自适应宽度。如果不能的话,内容会被截断) */
+ /* 万一列表太长了,它的内容会从垂直方向溢出(会自动添加一个竖向滚动条)
+ 但是水平方向不会(因为我们没有设定宽度,列表会自适应宽度。如果不能的话,内容会被截断) */
max-height: 10em; /* 100px */
overflow-y: auto;
overflow-x: hidden;
- border: 0.2em solid #000; /* 2px */
- border-top-width: 0.1em; /* 1px */
- border-radius: 0 0 0.4em 0.4em; /* 0 0 4px 4px */
+ border: 0.2em solid #000;
+ border-top-width: 0.1em;
+ border-radius: 0 0 0.4em 0.4em;
- box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); /* 0 2px 4px */
+ box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
background: #f0f0f0;
}
```
-对于选项,我们需要添加一个 `highlight` 类以便能标明用户将要选择的值或者已经选择的值。
+对于选项,我们需要添加一个 `highlight` 类以便能标明用户将要选择或者已经选择的值。
```css
.select .option {
@@ -266,363 +270,1353 @@ slug: Learn/Forms/How_to_build_custom_form_controls
}
```
-这是三种状态的结果:
-
-
-
-
- 基本状态 |
- 活动状态 |
- 打开状态 |
-
-
-
-
-
- {{EmbedLiveSample("基本状态",120,130, "", "Learn/Forms/How_to_build_custom_form_controls/Example_1")}}
- |
-
- {{EmbedLiveSample("活动状态",120,130, "", "Learn/Forms/How_to_build_custom_form_controls/Example_1")}}
- |
-
- {{EmbedLiveSample("展开状态",120,130, "", "Learn/Forms/How_to_build_custom_form_controls/Example_1")}}
- |
-
-
-
- 查看源代码
- |
-
-
-
-
-## 通过 JavaScript 让您的小部件动起来
-
-现在我们的设计和结构已经完成了。我们可以写些 JavaScript 代码来让这个部件真正生效。
-
-> **警告:** 下面的代码仅仅是教学性质的,并且不应该照搬使用。在许多方面,正如我们所看到的,这种方案不具有前瞻性,而且可能在旧浏览器上会不工作。这里面还有冗余的部分,在生产环境下,代码需要优化。
-
-> **备注:** 创建可复用的组件可能是一件需要些技巧的事情。[W3C 网络组件草案](http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html) 是对这类特定问题的答案之一。[X-Tag 项目](http://x-tags.org/) 是对这一规格的实验性实现;我们建议你看看它。
+这是我们的三种状态的结果([在此处查看源代码](/zh-CN/docs/Learn/Forms/How_to_build_custom_form_controls/Example_1)):
-### 它为什么不生效?
+#### 正常状态
-在我们开始之前,要记住一件和 JavaScript 有关的非常重要的事情:在浏览器中,**这是一种不可靠的技术**。当你构建一个自定义组件时,你会不得不得依赖于 JavaScript,因为这是将所有的东西联系在一起的线索。但是,很多情况下,JavaScript 不能在浏览器中运行。
+```html hidden
+
+
Cherry
+
+ - Cherry
+ - Lemon
+ - Banana
+ - Strawberry
+ - Apple
+
+
+```
-- 用户关掉了 JavaScript:这是最不常见的情形。现在只有很少的人会关掉 JavaScript。
-- 脚本没有加载。这是最常见的情形,特别是在移动端上,在那些网络非常不可靠的地方。
-- 脚本是有问题的。你应该总是考虑这种可能性。
-- 脚本和第三方脚本冲突。这可能会由用户使用的跟踪脚本和一些书签工具引发。
-- 脚本与一个浏览器的拓展冲突,或者受其影响。 (比如 Firefox 的 [NoScript](https://addons.mozilla.org/fr/firefox/addon/noscript/) 拓展 或者 Chrome 的 [NotScripts](https://chrome.google.com/webstore/detail/notscripts/odjhifogjcknibkahlpidmdajjpkkcfn) 拓展)。
-- 用户在使用老旧的浏览器,而且你需要的一些功能没有被支持。当你使用一些最新的 API 时,这种情况会经常发生。
+```css hidden
+.select {
+ position: relative;
+ display: inline-block;
+}
-因为这些风险,认真考虑 JavaScript 不生效时会发生什么是很重要的。处理这个问题的细节超出了这篇文章的范围,因为这与你有多么想使你的脚本具有通用性和可复用性更加相关,不过我们将在我们的例子中考虑与其相关的基本内容。
+.select.active,
+.select:focus {
+ box-shadow: 0 0 3px 1px #227755;
+ outline: none;
+}
-在我们的例子中,如果 JavaScript 代码没有运行,我们会回退到显示一个标准的 {{HTMLElement("select")}} 元素。为了实现这一点,我们需要两样东西。
+.select .optList {
+ position: absolute;
+ top: 100%;
+ left: 0;
+}
-首先,在每次使用我们的自定义部件前,我们需要添加一个标准的 {{HTMLElement("select")}} 元素。实际上,为了能将来自我们自定义的表单组件和以及其他部分的表单数据发送出去,这个元素也是需要的。我们随后会详细的解释这一点。
+.select .optList.hidden {
+ max-height: 0;
+ visibility: hidden;
+}
-```html
-
-
-
-```
+ box-sizing: border-box;
-第二,我们需要两个新的 classes 来隐藏不需要的元素 (即,当我们的脚本没有运行时的自定义组件,或是脚本正常运行时的"真正的" {{HTMLElement("select")}} 元素)。注意默认情况下,我们的 HTML 代码会隐藏我们的自定义组件。
+ padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
+ width: 10em; /* 100px */
-```css
-.widget select,
-.no-widget .select {
- /* 这个 CSS 选择器大体上说的是:
- - 要么我们将 body 的 class 设置为"widget",隐藏真实的{{HTMLElement("select")}}元素
- - 或是我们没有改变 body 的 class,这样 body 的 class 还是"no-widget",
- 因此 class 为"select"的元素需要被隐藏 */
- position: absolute;
- left: -5000em;
- height: 0;
- overflow: hidden;
-}
-```
+ border: 0.2em solid #000; /* 2px */
+ border-radius: 0.4em; /* 4px */
-接下来我们需要一个 JavaScript 开关来决定脚本是否运行。这个开关非常简单:如果页面加载时,我们的脚本运行了,它将会移除 `no-widget` class,并添加 `widget` class,由此切换 {{HTMLElement("select")}} 元素和自定义组件的可视性。
+ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */
-```js
-window.addEventListener("load", function () {
- document.body.classList.remove("no-widget");
- document.body.classList.add("widget");
-});
-```
+ background: #f0f0f0;
+ background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
+}
-
-
-
- 无 JS |
- 有 JS |
-
-
-
-
-
- {{EmbedLiveSample("不使用 JS",120,130, "", "Learn/Forms/How_to_build_custom_form_controls/Example_2")}}
- |
-
- {{EmbedLiveSample("使用 JS",120,130, "", "Learn/Forms/How_to_build_custom_form_controls/Example_2")}}
- |
-
-
-
- 查看源代码
- |
-
-
-
-
-> **备注:** 如果你真的想让你的代码变得通用和可重用,最好不要做一个 class 选择器开关,而是通过添加一个组件 class 的方式来隐藏 {{HTMLElement("select")}} 元素,并且动态地在每一个 {{HTMLElement("select")}} 元素后面添加代表页面中自定义组件的 DOM 树。
+.select .value {
+ display: inline-block;
+ width: 100%;
+ overflow: hidden;
-### 让工作变得更简单
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ vertical-align: top;
+}
-在我们将要构建的代码之中,我们将会使用标准的 DOM API 和 JavaScript 来完成要做的所有工作。我们准备使用的特性如下所示:
+.select::after {
+ content: "▼";
+ position: absolute;
+ z-index: 1;
+ height: 100%;
+ width: 2em; /* 20px */
+ top: 0;
+ right: 0;
-1. {{domxref("element.classList","classList")}}
-2. {{domxref("EventTarget.addEventListener","addEventListener()")}}
-3. {{domxref("NodeList.forEach()")}}
-4. {{domxref("element.querySelector","querySelector()")}} 和 {{domxref("element.querySelectorAll","querySelectorAll()")}}
+ padding-top: 0.1em;
-### 构造事件回调
+ box-sizing: border-box;
-基础已经准备好了,我们现在可以开始定义用户每次同我们的组件交互时会用到的所有函数了。
+ text-align: center;
-```js
-// 这个函数会用在每当我们想要停用一个自定义组件的时候
-// 它需要一个参数:
-// select :要停用的带有 'select' 类的节点
-function deactivateSelect(select) {
- // 如果组件没有运行,不用进行任何操作
- if (!select.classList.contains("active")) return;
+ border-left: 0.2em solid #000;
+ border-radius: 0 0.1em 0.1em 0;
- // 我们需要获取自定义组件的选项列表
- var optList = select.querySelector(".optList");
+ background-color: #000;
+ color: #fff;
+}
- // 关闭选项列表
- optList.classList.add("hidden");
+.select .optList {
+ z-index: 2;
- // 然后停用组件本身
- select.classList.remove("active");
-}
+ list-style: none;
+ margin: 0;
+ padding: 0;
-// 每当用户想要激活(或停用)这个组件的时候,会调用这个函数
-// 它需要 2 个参数:
-// select : 要激活的带有'select'类的 DOM 节点
-// selectList : 包含所有带'select'类的 DOM 节点的列表
-function activeSelect(select, selectList) {
- // 如果组件已经激活了,不进行任何操作
- if (select.classList.contains("active")) return;
+ background: #f0f0f0;
+ border: 0.2em solid #000;
+ border-top-width: 0.1em;
+ border-radius: 0 0 0.4em 0.4em;
- // 我们需要关闭所有自定义组件的活动状态
- // 因为 deactiveselect 函数满足 forEach 回调函数的所有请求,
- // 我们直接使用它,不使用中间匿名函数
- selectList.forEach(deactivateSelect);
+ box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
- // 然后我们激活特定的组件
- select.classList.add("active");
+ box-sizing: border-box;
+
+ min-width: 100%;
+ max-height: 10em; /* 100px */
+ overflow-y: auto;
+ overflow-x: hidden;
}
-// 每当用户想要打开/关闭选项列表的时候,会调用这个函数
-// 它需要一个参数:
-// select : 要触发的列表的 DOM 节点
-function toggleOptList(select) {
- // 该列表不包含在组件中
- var optList = select.querySelector(".optList");
+.select .option {
+ padding: 0.2em 0.3em;
+}
- // 我们改变列表的class去显示/隐藏它
- optList.classList.toggle("hidden");
+.select .highlight {
+ background: #000;
+ color: #ffffff;
}
+```
-// 每当我们要高亮一个选项的时候,会调用该函数
-// 它需要两个参数:
-// select : 带有'select'类的 DOM 节点,包含了需要高亮强调的选项
-// option : 需要高亮强调的带有'option'类的 DOM 节点
-function highlightOption(select, option) {
- // 为我们的自定义 select 元素获取所有有效选项的列表
- var optionList = select.querySelectorAll(".option");
+{{EmbedLiveSample("基本状态",120,130)}}
- // 我们移除所有选项的高亮强调
- optionList.forEach(function (other) {
- other.classList.remove("highlight");
- });
+#### 活动状态
- // 我们高亮强调正确的选项
- option.classList.add("highlight");
-}
+```html hidden
+
+
Cherry
+
+ - Cherry
+ - Lemon
+ - Banana
+ - Strawberry
+ - Apple
+
+
```
-这是你需要用来处理组件不同状态的所有代码。
+```css hidden
+.select {
+ position: relative;
+ display: inline-block;
+}
-接下来,我们将这些函数绑定到合适的事件上:
+.select.active,
+.select:focus {
+ box-shadow: 0 0 3px 1px #227755;
+ outline: none;
+}
-```js
-// 我们处理文档加载时的事件绑定。
-window.addEventListener("load", function () {
- var selectList = document.querySelectorAll(".select");
+.select .optList {
+ position: absolute;
+ top: 100%;
+ left: 0;
+}
- // 每个自定义组件都需要初始化
- selectList.forEach(function (select) {
- // 它的'option'元素也需要
- var optionList = select.querySelectorAll(".option");
+.select .optList.hidden {
+ max-height: 0;
+ visibility: hidden;
+}
- // 每当用户的鼠标悬停在一个选项上时,我们高亮这个指定的选项
- optionList.forEach(function (option) {
- option.addEventListener("mouseover", function () {
- // 注意:'select'和'option'变量是我们函数调用范围内有效的闭包。
- highlightOption(select, option);
- });
- });
+.select {
+ font-size: 0.625em; /* 10px */
+ font-family: Verdana, Arial, sans-serif;
- // 每当用户点击一个自定义的 select 元素时
- select.addEventListener("click", function (event) {
- // 注意:'select'变量是我们函数调用范围内有效的闭包。
+ box-sizing: border-box;
- // 我们改变选项列表的可见性
- toggleOptList(select);
- });
+ padding: 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */
+ width: 10em; /* 100px */
- // 如果组件获得了焦点
- // 每当用户点击它或是用 tab 键访问这个组件时,组件获得焦点
- select.addEventListener("focus", function (event) {
- // 注意:'select'和'selectlist'变量是我们函数调用范围内有效的闭包。
+ border: 0.2em solid #000; /* 2px */
+ border-radius: 0.4em; /* 4px */
- // 我们激活这个组件
- activeSelect(select, selectList);
- });
+ box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /* 0 1px 2px */
- // 如果组件失去焦点
- select.addEventListener("blur", function (event) {
- // 注意:'select'变量是我们函数调用范围内有效的闭包。
+ background: #f0f0f0;
+ background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
+}
- // 我们关闭这个组件
- deactivateSelect(select);
- });
- });
-});
-```
+.select .value {
+ display: inline-block;
+ width: 100%;
+ overflow: hidden;
-此时,我们的组件会根据我们的设计改变状态,但是它的值仍然没有更新。我们接下来会处理这件事。
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ vertical-align: top;
+}
-| 实时示例 |
-| ------------------------------------------------------------------------------------------------------ |
-| {{EmbedLiveSample("改变状态",120,130, "", "Learn/Forms/How_to_build_custom_form_controls/Example_3")}} |
-| [查看源代码](/zh-CN/docs/Learn/Forms/How_to_build_custom_form_controls/Example_3) |
+.select::after {
+ content: "▼";
+ position: absolute;
+ z-index: 1;
+ height: 100%;
+ width: 2em; /* 20px */
+ top: 0;
+ right: 0;
-### 处理组件的值
+ padding-top: 0.1em;
-既然我们的组件已经开始工作了,我们必须添加代码,使其能够根据用户的输入更新取值,并且能将取值随表单数据一同发送。
+ box-sizing: border-box;
-实现这一点最简单的方法是使用后台原生组件。这样的一个组件会使用浏览器提供的所有内置控件跟踪值,并且在表单提交时,取值也会像往常一样发送。当有现成的功能时,我们再做重复工作就毫无意义了。
+ text-align: center;
-像前面所看到的那样,出于无障碍的原因,我们已经使用了一个原生的选择组件作为后备显示内容;我们可轻松的将它的值与我们的自定义组件之间的值同步。
+ border-left: 0.2em solid #000;
+ border-radius: 0 0.1em 0.1em 0;
-```js
-// 这个函数更新显示的值并将其通过原生组件同步
-// 它需要 2 个参数:
-// select : 含有要更新的值的'select'类的 DOM 节点
-// index : 要被选择的值的索引
-function updateValue(select, index) {
- // 我们需要为了给定的自定义组件获取原生组件
- // 在我们的例子中,原生组件是自定义组件的‘同胞’
- var nativeWidget = select.previousElementSibling;
+ background-color: #000;
+ color: #fff;
+}
- // 我们也需要得到自定义组件的值占位符,
- var value = select.querySelector(".value");
+.select .optList {
+ z-index: 2;
- // 还有整个选项列表。
- var optionList = select.querySelectorAll(".option");
+ list-style: none;
+ margin: 0;
+ padding: 0;
- // 我们将被选择的索引设定为我们的选择的索引
- nativeWidget.selectedIndex = index;
+ background: #f0f0f0;
+ border: 0.2em solid #000;
+ border-top-width: 0.1em;
+ border-radius: 0 0 0.4em 0.4em;
- // 更新相应的值占位符
- value.innerHTML = optionList[index].innerHTML;
+ box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
- // 然后高亮我们自定义组件里对应的选项
- highlightOption(select, optionList[index]);
-}
+ box-sizing: border-box;
-// 这个函数返回原生组件里当前选定的索引
-// 它需要 1 个参数:
-// select : 跟原生组件有关的'select'类 DOM 节点
-function getIndex(select) {
- // 我们需要为了给定的自定义组件访问原生组件
- // 在我们的例子中,原生组件是自定义组件的一个“同胞”
- var nativeWidget = select.previousElementSibling;
+ min-width: 100%;
+ max-height: 10em; /* 100px */
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.select .option {
+ padding: 0.2em 0.3em;
+}
+
+.select .highlight {
+ background: #000;
+ color: #ffffff;
+}
+```
+
+{{EmbedLiveSample("活动状态",120,130)}}
+
+#### 展开状态
+
+```html hidden
+
+
Cherry
+
+ - Cherry
+ - Lemon
+ - Banana
+ - Strawberry
+ - Apple
+
+
+```
+
+```css hidden
+.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;
+}
+
+.select {
+ font-size: 0.625em; /* 10px */
+ font-family: Verdana, Arial, sans-serif;
+
+ 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, 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;
+}
+
+.select::after {
+ content: "▼";
+ position: absolute;
+ z-index: 1;
+ height: 100%;
+ width: 2em; /* 20px */
+ top: 0;
+ right: 0;
+
+ padding-top: 0.1em;
+
+ box-sizing: border-box;
+
+ text-align: center;
+
+ border-left: 0.2em solid #000;
+ border-radius: 0 0.1em 0.1em 0;
+
+ background-color: #000;
+ color: #fff;
+}
+
+.select .optList {
+ z-index: 2;
+
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ background: #f0f0f0;
+ border: 0.2em solid #000;
+ border-top-width: 0.1em;
+ border-radius: 0 0 0.4em 0.4em;
+
+ box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
+
+ box-sizing: border-box;
+
+ min-width: 100%;
+ max-height: 10em; /* 100px */
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.select .option {
+ padding: 0.2em 0.3em;
+}
+
+.select .highlight {
+ background: #000;
+ color: #fff;
+}
+```
+
+{{EmbedLiveSample("展开状态",120,130)}}
+
+## 通过 JavaScript 让你的控件动起来
+
+现在我们的设计和结构已经完成了。我们可以写些 JavaScript 代码来让这个控件真正生效。
+
+> **警告:** 下面的代码仅仅是教学性质的,不是生产环境的代码,并且不应该照搬使用。这种方案不具有前瞻性,而且可能在旧式浏览器上会不工作。这里面还有冗余的部分,在生产环境下,代码需要优化。
+
+### 它为什么不生效?
+
+在我们开始之前,要记住一件和 JavaScript 有关的非常重要的事情:**在浏览器中,这是一种不可靠的技术**。当你构建一个自定义控件时,你会不得不依赖于 JavaScript,因为这是将所有的东西联系在一起的线索。但是,很多情况下,JavaScript 不能在浏览器中运行。
+
+- 用户禁用了 JavaScript:这是最不常见的情形。现在只有很少的人会禁用 JavaScript。
+- 脚本没有加载:这是最常见的情形,特别是在移动端上,在那些网络非常不可靠的地方。
+- 脚本是有问题的:你应该总是考虑这种可能性。
+- 脚本和第三方脚本冲突:这可能会由用户使用的跟踪脚本和一些书签工具引发。
+- 脚本与一个浏览器的扩展冲突,或者受其影响。(比如 Firefox 的 [NoScript](https://addons.mozilla.org/fr/firefox/addon/noscript/) 扩展或者 Chrome 的 [ScriptBlock](https://chrome.google.com/webstore/detail/scriptblock/hcdjknjpbnhdoabbngpmfekaecnpajba) 扩展)。
+- 用户在使用旧版浏览器,而且你需要的一些特性没有被支持。当你使用一些最新的 API 时,这种情况会经常发生。
+- 在 JavaScript 完全下载、解析和执行前,用户已经开始与内容进行交互。
+
+因为这些风险,认真考虑 JavaScript 不生效时会发生什么是很重要的。处理这个问题的细节超出了这篇文章的范围,因为这与你有多么想使你的脚本具有通用性和可复用性更加相关,不过我们将在我们的示例中考虑与其相关的基本内容。
+
+在我们的示例中,如果 JavaScript 代码没有运行,我们会回退到显示一个标准的 {{HTMLElement("select")}} 元素。包括我们的控件和 {{HTMLElement("select")}};显示哪个取决于 body 元素的类,当加载成功时,脚本会更新 body 元素的类以使得控件生效。
+
+为了实现这一点,我们需要两样东西。
+
+首先,在每次使用我们的自定义控件前,我们需要添加一个标准的 {{HTMLElement("select")}} 元素。即使我们的 JavaScript 按预期工作,这个“额外”的 select 也是有好处的:我们可以使用这个 select 来将来自我们自定义的表单控件以及其他部分的表单数据发送出去。我们随后会详细的解释这一点。
+
+```html
+
+
+
+```
+
+第二,我们需要两个新的类来隐藏不需要的元素:如果脚本未运行,我们会在视觉上隐藏自定义控件;如果脚本正常运行,则隐藏“真正”的 {{HTMLElement("select")}} 元素)。注意默认情况下,我们的 HTML 代码会隐藏我们的自定义控件。
+
+```css
+.widget select,
+.no-widget .select {
+ /* 这个 CSS 选择器大体上说的是:
+ - 要么我们将 body 的类设置为“widget”,隐藏真实的