将原生日期输入样式设置为自定义的、无库的日期选择器
默认外观<input type="date">
默认功能<input type="date">
自定义输入
实现目标(标记)
检测没有日期选择器弹出窗口的浏览器
监听onchange
事件
GitHub 存储库
谢谢
对于Helppo用户,我希望提供一种比纯文本输入更便捷的编辑日期和日期时间值的方法。这当然不是什么新鲜事;大多数网站都使用日期选择器来实现这一目的。
作为开发者,有很多现成的选项可供选择。例如,react-datepicker(在 Helppo 的案例中,其包大小几乎翻了一番),每周安装量约为 72 万。或者pikaday(虽然存在很多问题和 PR,但其原理很有前景),每周安装量约为 21 万。
我想看看普通人是否<input type="date">
能胜任这份工作。我有几个要求和/或自由,取决于你怎么看待它:
-
因为 Helppo 应该 (a) 能够以来自用户数据库的任何格式显示日期值,并且 (b) 用户想要输入新值的格式不应受到限制,所以我希望日期选择器成为纯文本字段旁边的单独元素。
-
datepicker 元素应该可以通过 CSS 设置样式。
-
我认为时间选择器没有必要。根据我的经验,时间选择器的 UI 通常比普通的文本框更难用。(如果 Helppo 用户开始请求这个功能,我当然会考虑。)
考虑到这些,我开始尝试并浏览 StackOverflow。
默认外观<input type="date">
MDN 提供了一个非常有用的 wiki 页面,<input type="date">
其中包含日期选择器在各种浏览器上的截图。我在列表底部添加了几个我自己的截图。
我发现在 Safari 中(我听说在 IE 中)日期输入就像常规文本输入一样,并且没有日期选择器。
在 Chrome 上:
Mozilla 贡献者的屏幕截图根据CC-BY-SA 2.5获得许可。
在 Firefox 上:
Mozilla 贡献者的屏幕截图根据CC-BY-SA 2.5获得许可。
在边缘:
Mozilla 贡献者的屏幕截图根据CC-BY-SA 2.5获得许可。
在 Chrome (MacOS) 上:
我截图的。
在 Safari 浏览器 (MacOS) 上:
没有!Safari 没有日期选择器。
我截图的。
如上图所示,默认的日期选择器是一个文本输入框,其中日期以特定于语言环境的格式显示。点击输入框或其中的日历图标,都会打开一个日期选择弹出窗口。这与 datepicker JS 库的工作原理类似。
为了突出不同的组件:
虽然 JS 库允许自定义样式和功能,但如果<input type="date">
您不太了解这一点,那就另当别论了。没有开关可以启用周数或年份下拉菜单;浏览器要么渲染这些东西,要么不渲染。
默认功能<input type="date">
另一个担忧是,当通过 JavaScript 读取时,输入值在不同的浏览器之间是否一致。
对于Helppo来说,我始终希望它采用相同的格式(YYYY-MM-DD),但显然只要能够一致地进行解析,格式是什么并不重要。
幸运的是,这不是问题!根据MDN 页面,读取input.value
结果应该遵循一致的格式:
const dateInput = document.querySelector('.date-input');
console.log(dateInput.value); // "2020-10-26"
这与输入中的可见格式是否基于语言环境无关。
为了确保万无一失,我在 MacOS 上的各种浏览器中对其进行了测试和验证。
基于此结果,我们可以自信地监听onchange
事件<input type="date">
并获得相同的日期格式,无论平台如何。
自定义输入
首先,如果你的应用程序需要一个日期选择器,我强烈建议直接使用<input type="date">
上面提到的那个!既然这个开箱即用的解决方案如此有效,那么添加一个日期选择器库确实应该有它的理由。
然而,正如开头提到的,对于Helppo,我有一个要求,日期选择器应该是一个在常规文本输入框旁边的额外按钮。具体如下:
所以我需要将其转换<input type="date">
成类似按钮的元素。
我们的出发点:
- 有些浏览器(Firefox、Edge)没有切换按钮,点击输入框时只会弹出窗口
- 某些浏览器(Chrome)会在您点击切换按钮时打开弹出窗口
- 某些浏览器(Safari、IE)根本不打开弹出窗口
对于第一点,我们应该能够将日期输入框隐藏起来,并将其拉伸到我们想要显示的切换按钮上方。这样,当用户点击切换按钮时,他们实际上点击的是触发弹出窗口的日期输入框。
对于第 2 点,我们应该尝试做同样的事情,但针对日期输入框的切换部分。我开始搜索,并在 StackOverflow 上找到了这个答案,它针对 Webkit 浏览器实现了同样的功能。
对于第 3 点,我们根本不应该显示切换按钮,因为点击它不会有任何反应。理想情况下,这应该在不使用用户代理嗅探或任何其他硬编码浏览器检测方法的情况下实现。
实现目标(标记)
以下是我们在 HTML 中构建切换按钮的方式:
<span class="datepicker-toggle">
<span class="datepicker-toggle-button"></span>
<input type="date" class="datepicker-input">
</span>
关于可访问性的注意事项:由于我们只使用了不包含文本内容的 span,因此这段 HTML 中唯一可操作的元素是输入框。文档的焦点流程与此处只有一个常规输入框时相同。如果您使用不同类型的元素(例如
<img>
),请确保您的元素保持可访问性。
以下是包装元素和我们想要显示的可视按钮的基本样式:
.datepicker-toggle {
display: inline-block;
position: relative;
width: 18px;
height: 19px;
}
.datepicker-toggle-button {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml;base64,...');
}
注意
.datepicker-toggle-button
:此元素的样式完全取决于你的应用。本例中,为了简单起见,我仅使用了背景图片。
此时,我们只需要在按钮元素旁边显示日期输入,因为它位于 DOM 中:
这里开始使用特定于浏览器的 CSS,用于将输入的可操作部分拉伸到可见按钮上。
根据前面提到的SO答案:
.datepicker-input {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
box-sizing: border-box;
}
.datepicker-input::-webkit-calendar-picker-indicator {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
cursor: pointer;
}
适用于 Chrome(MacOS),通常具有切换功能:
适用于没有切换开关的 Firefox (MacOS):
我也请朋友在Windows上的Firefox上进行验证,他们给我发了一张类似上面的截图。
我无法在 Edge 上验证它,但基于在 Edge 上单击输入会触发弹出窗口的事实,它的工作方式应该与 Firefox 类似,因为我们将不可见的输入拉伸到整个按钮上。
最后,我也在 iOS Safari 上对其进行了测试,日期选择器按预期打开:
检测没有日期选择器弹出窗口的浏览器
如上所述,某些浏览器(Safari、IE)没有内置日期选择器功能。日期输入框的功能与普通文本输入框相同。对于这些浏览器,我们根本不应该显示该按钮,因为它只会停留在那里而不执行任何操作。
我研究了如何自动检测浏览器支持情况。我研究了一些想法:
- 检测特定 Shadow DOM 的存在。参见下方截图。如果我们能够检查特定于日期选择器元素(例如
input.shadowRoot.querySelector("[pseudo='-webkit-calendar-picker-indicator']")
)的存在就太好了,但遗憾的是,对于输入元素(shadowRoot
isnull
;更多信息请访问MDN)而言,这是不可能的。我目前还没有发现任何解决方法。
-
读取
document.styleSheets
并确定我们的伪元素 CSS 选择器是否已成功应用。这是一个基于我从未见过的代码的疯狂想法,当然也没有得到任何结果。它CSSStyleSheet
不包含任何关于样式如何应用或是否有效的信息。 -
继续上一点……尝试读取伪元素的样式,并观察不同浏览器结果的差异,怎么样?我为伪元素添加了一个足够模糊的 CSS 样式:
.datepicker-input::-webkit-calendar-picker-indicator { font-variant-caps: titling-caps; }
,然后尝试读取它:(window.getComputedStyle($0, '::-webkit-calendar-picker-indicator').fontVariantCaps
同样使用了 的变体和省略冒号)。不幸的是,这总是返回输入元素的样式值,而不是伪元素的样式值。这可能是因为我们无法访问影子 DOM。例如,上述方法对于:before
伪元素有效,但似乎不适用于我们的用例。 -
检查
<input type="date">
值是否被浏览器自动过滤。事实证明,这才是关键。事后看来,我应该先检查一下这一点,但当时我感觉这显然行不通,因为我假设日期输入框仍然会进行格式化/过滤。结果发现并非如此。
因此,最终检查日期选择器支持就像设置输入值并检查浏览器是否接受它一样简单:
const input = document.createElement('input');
input.type = 'date';
input.value = 'invalid date value';
const isSupported = input.value !== 'invalid date value';
我验证了它在 Safari 中有效。
监听onchange
事件
剩下的就是当用户从弹出窗口中选择日期时更新文本输入的值。这非常简单:
const textInput = document.querySelector('.text-input');
const dateInput = document.querySelector('.datepicker-input');
dateInput.addEventListener('change', event => {
textInput.value = event.target.value;
// Reset the value so the picker always
// opens in a fresh state regardless of
// what was last picked
event.target.value = '';
});
重置日期输入值的另一种方法是使其与文本输入保持同步,这会变得有点复杂,但仍然很简单:
dateInput.addEventListener('change', event => {
textInput.value = event.target.value;
});
textInput.addEventListener('input', event => {
const value = textInput.value.trim();
dateInput.value = value.match(/^\d{4}-\d{2}-\d{2}$/) ? value : '';
});
GitHub 存储库
我已将此解决方案打包到可重复使用的迷你库中:
也在 npm 上发布为native-datepicker
:
该代码库包含如何在 React 代码库以及原生 JavaScript 中使用代码的示例。API 概览:
const picker = new NativeDatepicker({
onChange: value => {
// ...
},
});
someElement.appendNode(picker.element);
还包括一个现成的 React 组件:
<NativeDatepicker
className="custom-class"
value={value}
onChange={value => {
// ...
}}
/>
它还知道日期时间输入(即,如果文本输入包含“2020-10-26 23:35:00”之类的值,则库将在更改时仅替换其日期部分)。
如果您发现该软件包有用,那就太好了。如果有任何问题,请在 GitHub 仓库中提交问题!
谢谢
感谢阅读!
还要感谢 MDN 和 StackOverflow 评论者为本次探索提供的基础。
如果您还没有看过,欢迎随时查看Helppo,我最初就是为它构建了这个组件。Helppo 是一个命令行工具(用 NodeJS 编写),可以帮您为数据库搭建一个管理界面。只需运行npx helppo-cli --help
即可开始使用。