<input type="month">

可用性有限

此特性不是基线特性,因为它在一些最广泛使用的浏览器中不起作用。

类型为 month<input> 元素会创建一个输入字段,允许用户输入月份和年份。此字段允许轻松输入月份和年份。其值是一个字符串,格式为 YYYY-MM,其中 YYYY 是四位数字的年份,MM 是月份。

试一试

<label for="start">Start month:</label>

<input type="month" id="start" name="start" min="2018-03" value="2018-05" />
label {
  display: block;
  font:
    1rem "Fira Sans",
    sans-serif;
}

input,
label {
  margin: 0.4rem 0;
}

该控件的用户界面通常因浏览器而异;目前支持情况参差不齐,只有桌面版 Chrome/Opera 和 Edge 以及大多数现代移动浏览器版本具有可用的实现。在不支持 month 输入的浏览器中,该控件会优雅地降级为 <input type="text">,尽管可能会自动验证输入的文本以确保其格式符合预期。

对于那些使用不支持 month 的浏览器的用户,下面的截图展示了它在 Chrome 和 Opera 中的样子。点击右侧的向下箭头会弹出一个日期选择器,让你选择月份和年份。

Month control on Chrome browser

Microsoft Edge month 控件看起来是这样的

Month control on Edge browser

一个表示输入字段中输入的月份和年份值的字符串,格式为 YYYY-MM(四位或更多位数字年份,后跟一个连字符(-),再后跟两位数字月份)。此输入类型使用的月份字符串格式在月份字符串中描述。

设置默认值

你可以通过在 value 属性中包含月份和年份来为输入控件设置默认值,如下所示:

html
<label for="bday-month">What month were you born in?</label>
<input id="bday-month" type="month" name="bday-month" value="2001-06" />

需要注意的一点是,显示的日期格式与实际的 value 不同;大多数用户代理会根据用户操作系统的区域设置,以适合当地的形式显示月份和年份,而日期 value 始终采用 yyyy-MM 格式。

例如,当上述值提交到服务器时,它将看起来像 bday-month=1978-06

使用 JavaScript 设置值

你也可以使用 HTMLInputElement.value 属性在 JavaScript 中获取和设置日期值,例如:

html
<label for="bday-month">What month were you born in?</label>
<input id="bday-month" type="month" name="bday-month" />
js
const monthControl = document.querySelector('input[type="month"]');
monthControl.value = "2001-06";

附加属性

除了 <input> 元素通用的属性外,月份输入还提供以下属性。

list

列表属性的值是位于同一文档中的 <datalist> 元素的 id<datalist> 为此输入提供了一个预定义值列表,以供用户建议。列表中与 type 不兼容的任何值都不会包含在建议选项中。提供的值是建议,而不是要求:用户可以从这个预定义列表中选择,也可以提供不同的值。

max

要接受的最新年份和月份,采用上面部分讨论的字符串格式。如果输入到元素中的 value 超过此值,则元素将不通过约束验证。如果 max 属性的值不是有效的 yyyy-MM 格式字符串,则元素没有最大值。

该值必须指定一个晚于或等于由 min 属性指定的年月配对。

min

要接受的最早年份和月份,采用与上面描述的相同的 yyyy-MM 格式。如果元素的 value 小于此值,则元素将不通过约束验证。如果为 min 指定的值不是有效的年月字符串,则输入没有最小值。

此值必须是一个年月配对,它早于或等于由 max 属性指定的年月配对。

readonly

一个布尔属性,如果存在,则表示该字段不能被用户编辑。但是,它的 value 仍然可以通过直接设置 HTMLInputElement.value 属性值的 JavaScript 代码来更改。

注意: 由于只读字段不能有值,因此 required 对同时指定了 readonly 属性的输入没有影响。

step

step 属性是一个数字,它指定值必须遵循的粒度,或者特殊值 any,如下所述。只有与步长基数相距整数步长的值才有效。如果指定了 min,则步长基数为 min,否则为 value,如果两者都未提供,则为 0(Unix 纪元,1970-01)。

对于 month 输入,step 的值以月为单位。step 的默认值为 1,表示 1 个月。

字符串值 any 表示不暗示步进,允许任何值(排除其他约束,如 minmax)。实际上,对于 month 输入,它与 1 具有相同的效果,因为选择器 UI 只允许选择整月。

注意:当用户输入的数据不符合步进配置时,用户代理可能会四舍五入到最近的有效值,当有两个同样接近的选项时,优先选择正方向的数字。

使用月份输入

与日期相关的输入(包括 month)乍一看很方便;它们承诺提供一个易于选择日期的 UI,并且无论用户区域设置如何,它们都会将发送到服务器的数据格式标准化。然而,<input type="month"> 存在问题,因为目前许多主流浏览器尚未支持它。

我们将查看 <input type="month"> 的基本和更复杂的用法,然后在处理浏览器支持一节中提供关于缓解浏览器支持问题的建议)。

月份的基本用法

<input type="month"> 最基本的用法涉及基本的 <input><label> 元素组合,如下所示:

html
<form>
  <label for="bday-month">What month were you born in?</label>
  <input id="bday-month" type="month" name="bday-month" />
</form>

设置最大和最小日期

你可以使用 minmax 属性来限制用户可以选择的日期范围。在以下示例中,我们指定了最小月份 1900-01 和最大月份 2013-12

html
<form>
  <label for="bday-month">What month were you born in?</label>
  <input
    id="bday-month"
    type="month"
    name="bday-month"
    min="1900-01"
    max="2013-12" />
</form>

结果是:

  • 只能选择 1900 年 1 月到 2013 年 12 月之间的月份;超出该范围的月份无法在控件中滚动到。
  • 根据你使用的浏览器,你可能会发现指定范围之外的月份可能无法在月份选择器中选择(例如 Edge),或者无效(参见验证)但仍然可用(例如 Chrome)。

控制输入大小

<input type="month"> 不支持诸如 size 之类的表单大小属性。你需要借助 CSS 来满足尺寸需求。

验证

默认情况下,<input type="month"> 不对输入值应用任何验证。UI 实现通常不允许你输入任何不是日期的内容——这很有用——但你仍然可以提交带有空 month 输入的表单,或者输入无效日期(例如,4 月 32 日)。

为了避免这种情况,你可以使用 minmax 来限制可用日期(参见设置最大和最小日期),此外还可以使用 required 属性使日期填写成为强制性。因此,支持的浏览器会在你尝试提交超出设定范围的日期或空日期字段时显示错误。

我们来看一个例子;这里我们设置了最小和最大日期,并使该字段成为必填项:

html
<form>
  <div>
    <label for="month">
      What month would you like to visit (June to Sept.)?
    </label>
    <input
      id="month"
      type="month"
      name="month"
      min="2022-06"
      max="2022-09"
      required />
    <span class="validity"></span>
  </div>
  <div>
    <input type="submit" value="Submit form" />
  </div>
</form>

如果您尝试在未指定月份和年份(或日期超出设定范围)的情况下提交表单,浏览器将显示错误。现在尝试使用该示例:

这是给那些没有使用支持浏览器的用户的截图:

Month required prompt on Chrome browser

这是上述示例中使用的 CSS。这里我们利用 :valid:invalid CSS 属性根据当前值是否有效来设置输入样式。我们不得不将图标放在输入旁边的 <span> 上,而不是输入本身,因为在 Chrome 中,生成的内容放置在表单控件内部,无法有效设置样式或显示。

css
div {
  margin-bottom: 10px;
  position: relative;
}

input[type="number"] {
  width: 100px;
}

input + span {
  padding-right: 30px;
}

input:invalid + span::after {
  position: absolute;
  content: "✖";
  padding-left: 5px;
}

input:valid + span::after {
  position: absolute;
  content: "✓";
  padding-left: 5px;
}

警告: HTML 表单验证不能替代确保输入数据格式正确的脚本。有人很容易调整 HTML 以绕过验证,或完全删除它。也有可能有人完全绕过您的 HTML,直接将数据提交到您的服务器。如果您的服务器端代码未能验证收到的数据,当提交格式不正确的数据(或数据过大、类型错误等)时,可能会发生灾难。

处理浏览器支持

如上所述,截至撰写本文时,使用日期输入的主要问题是许多主流浏览器尚未完全实现它们;桌面版只有 Chrome/Opera 和 Edge 支持,而移动设备上的大多数现代浏览器也支持。例如,Android 版 Chrome 上的 month 选择器看起来像这样:

Month picker on Chrome for Android

不支持的浏览器会优雅地降级为文本输入,但这在用户界面的一致性(呈现的控件将不同)和数据处理方面都带来了问题。

第二个问题是两者中更严重的一个。如前所述,对于 month 输入,实际值始终规范化为 yyyy-mm 格式。另一方面,在默认配置下,text 输入不知道日期应该采用什么格式,这是一个问题,因为人们书写日期的方式有很多种。例如:

  • mmyyyy (072022)
  • mm/yyyy (07/2022)
  • mm-yyyy (07-2022)
  • yyyy-mm (2022-07)
  • 月份 yyyy (2022 年 7 月)
  • 等等…

解决这个问题的一种方法是在你的 month 输入上添加一个 pattern 属性。即使 month 输入不使用它,如果浏览器退回到将其视为 text 输入,则会使用该模式。例如,尝试在不支持 month 输入的浏览器中查看以下演示:

html
<form>
  <div>
    <label for="month">
      What month would you like to visit (June to Sept.)?
    </label>
    <input
      id="month"
      type="month"
      name="month"
      min="2022-06"
      max="2022-09"
      required
      pattern="[0-9]{4}-[0-9]{2}" />
    <span class="validity"></span>
  </div>
  <div>
    <input type="submit" value="Submit form" />
  </div>
</form>

如果您尝试提交它,您会看到,如果您的输入不符合模式 nnnn-nn(其中 n 是 0 到 9 的数字),浏览器现在会显示错误消息(并将输入标记为无效)。当然,这并不能阻止人们输入无效日期(例如 0000-42),或者遵循模式但格式不正确的日期。

还有一个问题是用户不一定知道预期的是众多日期格式中的哪一种。我们还有工作要做。

在所有主流浏览器都支持日期输入一段时间之前,处理表单中日期的最佳跨浏览器方式是让用户在单独的控件中输入月份和年份(<select> 元素很受欢迎;请参见下面的实现),或者使用 JavaScript 库,例如 jQuery 日期选择器插件。

示例

在此示例中,我们创建了两组 UI 元素,每个元素都旨在让用户选择月份和年份。第一个是原生的 month 输入,另一个是一对 <select> 元素,允许独立选择月份和年份,以兼容尚不支持 <input type="month"> 的浏览器。

HTML

请求月份和年份的表单如下所示:

html
<form>
  <div class="nativeDatePicker">
    <label for="month-visit">What month would you like to visit us?</label>
    <input type="month" id="month-visit" name="month-visit" />
    <span class="validity"></span>
  </div>
  <p class="fallbackLabel">What month would you like to visit us?</p>
  <div class="fallbackDatePicker">
    <div>
      <span>
        <label for="month">Month:</label>
        <select id="month" name="month">
          <option selected>January</option>
          <option>February</option>
          <option>March</option>
          <option>April</option>
          <option>May</option>
          <option>June</option>
          <option>July</option>
          <option>August</option>
          <option>September</option>
          <option>October</option>
          <option>November</option>
          <option>December</option>
        </select>
      </span>
      <span>
        <label for="year">Year:</label>
        <select id="year" name="year"></select>
      </span>
    </div>
  </div>
</form>

ID 为 nativeDatePicker<div> 使用 month 输入类型请求月份和年份,而 ID 为 fallbackDatePicker<div> 则使用一对 <select> 元素。第一个请求月份,第二个请求年份。

用于选择月份的 <select> 硬编码了月份名称,因为它们不会改变(不考虑本地化)。可用年份列表根据当前年份动态生成(有关这些函数如何工作的详细解释,请参见下面的代码注释)。

JavaScript

处理选择使用哪种方法以及设置要包含在非原生年份 <select> 中的年份列表的 JavaScript 代码如下所示。

示例中可能最有趣的部分是特性检测代码。为了检测浏览器是否支持 <input type="month">,我们创建一个新的 <input> 元素,尝试将其 type 设置为 month,然后立即检查其类型设置为什么。不支持 month 类型的浏览器将返回 text,因为在不支持时 month 会回退到 text。如果不支持 <input type="month">,我们隐藏原生选择器并显示回退选择器 UI。

js
// Get UI elements
const nativePicker = document.querySelector(".nativeDatePicker");
const fallbackPicker = document.querySelector(".fallbackDatePicker");
const fallbackLabel = document.querySelector(".fallbackLabel");

const yearSelect = document.querySelector("#year");
const monthSelect = document.querySelector("#month");

// Hide fallback initially
fallbackPicker.style.display = "none";
fallbackLabel.style.display = "none";

// Test whether a new date input falls back to a text input or not
const test = document.createElement("input");

try {
  test.type = "month";
} catch (e) {
  console.log(e.description);
}

// If it does, run the code inside the if () {} block
if (test.type === "text") {
  // Hide the native picker and show the fallback
  nativePicker.style.display = "none";
  fallbackPicker.style.display = "block";
  fallbackLabel.style.display = "block";

  // Populate the years dynamically
  // (the months are always the same, therefore hardcoded)
  populateYears();
}

function populateYears() {
  // Get the current year as a number
  const date = new Date();
  const year = date.getFullYear();

  // Make this year, and the 100 years before it available in the year <select>
  for (let i = 0; i <= 100; i++) {
    const option = document.createElement("option");
    option.textContent = year - i;
    yearSelect.appendChild(option);
  }
}

注意:请记住,有些年份有 53 周(请参阅每年周数)!在开发生产应用程序时,您需要考虑这一点。

技术摘要

一个表示月份和年份的字符串,或为空。
事件 changeinput
支持的常见属性 autocomplete, list, readonly, step
IDL 属性 list, value, valueAsDate, valueAsNumber
DOM 接口 HTMLInputElement
方法 select(), stepDown(), stepUp()
隐式 ARIA 角色 没有对应的角色

规范

规范
HTML
# 月-状态-(type=month)

浏览器兼容性

另见