<input type="month">

类型为 month<input> 可以让你容易地创建一个方便输入年份或月份的一个 <input>。输入的值是一个经过“YYYY-MM”格式化的字符串,其中 YYYY 是四位数的年份,而 MM 是月份的数值表示。

尝试一下

通常来说控件的 UI 界面因浏览器的不同而有变化,到目前为止此控件还不被所有浏览器支持,在桌面浏览器中只有 Chrome/Opera 和 Edge 支持;在移动端被大部分现代浏览器所支持。在不支持的浏览器中,这个控件会被优雅的降级到 <input type="text">,不过对输入的文字会有自动的验证,保证它按照预期进行格式化。

对于那些使用不支持 month 控制器的浏览器用户,以下的截图呈现了 Chrome 和 Opera 的月份控制器。单击右侧的向下箭头会显示日期选择器,以便选择年份和月份。

Chrome 浏览器的月份控制器

Edge 的 month 控制器看起来像这样的:

Edge 浏览器的月份控制器

代表月份和年份的字符串,或为空字符串
事件 changeinput
支持的共有属性 autocompletelistreadonlystep
IDL 属性 value
DOM 接口 HTMLInputElement
方法 select()stepDown()stepUp()

一个以 YYYY-MM 形式(4 个或更多位数的年,接一个连字符 -,再接一个两位数月份)表示月份和年份的字符串。月份字符串的格式在月份字符串中有描述。

设置默认值

你可以通过在 value 属性中包含月份和年份来设置该输入控件的默认值,像这样:

html
<label for="bday-month">你在哪个月出生?</label>
<input id="bday-month" type="month" name="bday-month" value="2001-06" />

需要注意的是显示的如期格式不同于实际的 value ;大部分用户代理基于操作系统,根据合适的本地化形式显示月份和年份,而日期的 value 总是会格式化为 yyyy-MM

在向服务器提交上述值的时候它们看起来像这样:bday-month=1978-06

通过 JavaScript 代码设置 value

当然你也可以使用 JavaScript 代码通过 HTMLInputElement.value 属性来获取或设置日期的值。例如:

html
<label for="bday-month">你在哪个月出生?</label>
<input id="bday-month" type="month" name="bday-month" />
js
const monthControl = document.querySelector('input[type="month"]');
monthControl.value = "2001-06";

其他属性

除了 <input> 元素的共有属性外,month 输入还提供以下属性:

list

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

max

使用在章节中讨论的字符串格式指定的所接受的最大年份和月份。如果输入到该元素的 value 超过了这个,则该元素无法通过约束验证。如果 max 属性的值不是格式为 yyyy-MM 的有效字符串,则该元素没有最大值。

该值必须晚于或等于 min 属性所指定的年份—月份对。

min

使用在章节中讨论的字符串格式指定的所接受的最小年份和月份。如果输入到该元素的 value 小于这个,则该元素无法通过约束验证。如果 min 属性的值不是格式为 yyyy-MM 的有效字符串,则该元素没有最小值。

该值必须早于或等于 max 属性所指定的年份—月份对。

readonly

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

备注: 因为只读字段不能有值,所以 required 对指定了 readonly 属性的输入没有任何影响。

step

step 属性指定了值必须满足的粒度,或者是下文描述的特殊值 any。值必须满足基础的步进值,才有效。如果指定了 min 属性,则由 min 属性决定,否则,使用 value 属性的值,如果上述两个值都不存在,则提供适当的默认值。

字符串值 any 意味着不使用步进值,任意值都可以接受(除其他制约因素如 minmax 之外)。

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

对于 month 输入,step 的值以月份为单位,缩放因子为 1(基础数值也是以月份为单位的)。step 的默认值为 1,表示 1 个月。

使用 month 输入

与日期相关的输入(包含 month)乍一看很方便:它们提供了一个简单的用户界面来选择日期,并且它们将发送到服务器的数据格式规范化,无论用户的本地化配置如何。但是,<input type="month"> 还是存在兼容性问题,大多数主流浏览器还没有支持它。

我们先看看 <input type="month"> 的基础和高级的用法,然后再讨论有关缓解浏览器支持问题的建议(请参阅处理浏览器支持问题)。

基本使用方法

最简单的 <input type="month"> 涉及到基础的 <input><label> 的元素组合,像下面这样:

html
<form>
  <label for="bday-month">你在哪个月出生?</label>
  <input id="bday-month" type="month" name="bday-month" />
</form>

设置最大和最小日期

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

html
<form>
  <label for="bday-month">你在哪个月出生?</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"> 不会对输入的值应用任何验证,用户界面实现会屏蔽所有非日期值的输入,尽管这很有用,但是仍然无法完全依赖于该值的合法性。你仍然可以不填入任何值而提交,或输入不合法的日期值(如 4 月 32 日)。

为了避免这种情况,你可以使用 minmax 来限制可用的日期(参见设置最大和最小日期),并使用 required 属性令日期必填。在支持的浏览器中,当你尝试提交超出范围的日期,或空日期值时,会显示错误信息。

我们来看一个例子,这里我们设定了最小和最大的日期,并令该字段必填:

html
<form>
  <div>
    <label for="month"> 你愿意在哪个月拜访(6 月至 9 月)? </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="提交表单" />
  </div>
</form>

如果你尝试在未指定月份或年份,或指定超出给定范围的日期情况下提交表单,浏览器会显示错误。请试试以下实时演示:

对于那些没有使用支持的浏览器的人们来说,以下是该错误的屏幕截图:

Chrome 浏览器中的 Month 控件必填提示

下面是上述例子中使用的 CSS。这里我们使用了 :valid:invalid 这两个 CSS 属性,根据当前值是否有效来为输入添加样式。我们不得不把图标放在 input 旁边的 <span> 上,而不是放在 input 框本身,因为在 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 代码,直接将数据提交给你的服务器。如果你的服务器端代码不能验证它所收到的数据,那么当提交的数据格式不当(或数据过大、类型错误等等)时,灾难就会降临。

处理浏览器支持

如前所述,使用 month 输入的最大问题是大部分主流浏览器还没有实现它们;只有桌面端的 Chrome/Opera、Edge 和大部分的移动端现代浏览器支持它们。例如,安卓版 Chrome 的 month 选择器看起来像这样:

Android 手机上 Chrome 的月份选择器样式

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

第二个问题更为严重;如前所述,month 输入的值总是被规范为 yyyy-mm 的格式。另一方面,对于文本输入,默认情况下,浏览器不知道时间应该是什么格式,而且人们有多种写法,如:

  • mmyyyy (072022)
  • mm/yyyy (07/2022)
  • mm-yyyy (07-2022)
  • yyyy-mm (2022-07)
  • Month yyyy (July 2022)
  • 等等

一个办法是在你的 month 输入上添加 pattern 属性。即使 month 输入不使用它,text 输入回退也会使用。例如,试着在一个不支持 month 输入的浏览器中查看下面的演示:

html
<form>
  <div>
    <label for="month"> 愿意在哪个月访问(6 月至 9 月)? </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="提交表单" />
  </div>
</form>

如果你尝试提交,你会发现,如果你的输入不符合模式 nnnn-nn(其中 n 是 0 到 9 的数字),不支持的浏览器现在会显示一个错误信息(并突出显示输入无效)。当然,这并不能阻止人们输入无效的日期(如 0000-42),或者不正确但遵循格式的日期。

还有一个问题,就是用户不知道到底应该输入什么格式的日期。还有一些事情要做。

目前,以跨浏览器方式处理时间的最佳方法(至少在所有主流浏览器实现它们之前)是让用户在单独的控件中输入月份和年份(特别是在 <select> 元素中,参见下面的示例),或使用 JavaScript 库(例如 jQuery 日期选择器插件)。

示例

在此示例中,我们创建了两组用于选择日期的接口元素:使用 <input type="month"> 创建的原生选择器,以及为不支持 month 输入类型的旧版浏览器准备的两个分别用于选择月份和年份的 <select> 元素。

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,然后立即检查其 type 值。不支持的浏览器将返回 text,与 month 的回退行为相符。如果不支持 <input type="month">,我们将隐藏原生选择器并显示作为回退的选择器 UI。

js
// 获取 UI 元素
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");

// 最初,隐藏回退元素
fallbackPicker.style.display = "none";
fallbackLabel.style.display = "none";

// 测试一个新的 date 输入框是否会回退至 text 输入框
const test = document.createElement("input");

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

// 如果回退了,运行 if 代码块中的代码
if (test.type === "text") {
  // 隐藏原生选择器,显示回退元素
  nativePicker.style.display = "none";
  fallbackPicker.style.display = "block";
  fallbackLabel.style.display = "block";

  // 动态生成年份
  // 月份总是相同的,故将它们硬编码
  populateYears();
}

function populateYears() {
  // 获取当前年份的数值表示
  const date = new Date();
  const year = date.getFullYear();

  // 在年份 <select> 中,令该年和之前的 100 年可选
  for (let i = 0; i <= 100; i++) {
    const option = document.createElement("option");
    option.textContent = year - i;
    yearSelect.appendChild(option);
  }
}

备注: 请记住有些年份有 53 周(见每年的周数)!当你在开发产品应用时应当考虑这个问题。

规范

Specification
HTML
# month-state-(type=month)

浏览器兼容性

Report problems with this compatibility data on GitHub
desktopmobile
Chrome
Edge
Firefox
Opera
Safari
Chrome Android
Firefox for Android
Opera Android
Safari on iOS
Samsung Internet
WebView Android
WebView on iOS
type="month"

Legend

Tip: you can click/tap on a cell for more information.

Full support
Full support
No support
No support
See implementation notes.

参见