昨晚我在一个特别大的代码库的地下室里四处搜寻,偶然发现了我们的 normalize.css
,它确保我们所有的标记在不同的浏览器中以类似的方式呈现。我快速浏览了一下,发现了一些针对一个名为 <output>
的奇怪元素的样式,我以前从未见过或听说过。
根据 MDN 的说法,它“代表计算或用户操作的结果”,通常用于表单。让我感到尴尬的是,它并不是规范中新增的奇特元素,因为 Chris 在很久以前的 2011 年 的一篇博文中就使用过它。
但是无论如何!output
是做什么的?我们如何使用它呢?假设我们有一个类型为 range
的输入。然后我们添加一个 output
元素,并使用 for
属性将其与输入关联起来。
<input type="range" name="quantity" id="quantity" min="0" max="100">
<output for="quantity"></output>
查看 CodePen 上的 输入输出 #2 示例,由 CSS-Tricks (@css-tricks) 制作。
它...实际上什么也没做。默认情况下,output
没有任何样式,也不会在浏览器中渲染任何盒子。此外,当我们更改输入的值时,也不会发生任何事情。
我们必须使用 JavaScript 将所有内容连接在一起。没问题!首先我们需要在 DOM 中使用 JavaScript 找到我们的输入,如下所示
const rangeInput = document.querySelector('input');
现在我们可以向它添加一个事件监听器,以便无论何时我们编辑值(通过在我们的输入上左右滑动),我们都可以检测到更改
const rangeInput = document.querySelector('input');
rangeInput.addEventListener('change', function() {
console.log(this.value);
});
this.value
将始终引用 rangeInput
的值,因为我们是在事件处理程序内部使用它的,然后我们可以将该值返回到控制台以确保一切正常。之后,我们可以在 DOM 中找到我们的 output
元素
const rangeInput = document.querySelector('input');
const output = document.querySelector('output');
rangeInput.addEventListener('change', function() {
console.log(this.value);
});
然后,我们编辑事件监听器,以便每次编辑输入的值时,都会更改 output
的值
const rangeInput = document.querySelector('input');
const output = document.querySelector('output');
rangeInput.addEventListener('change', function() {
output.value = this.value;
});
瞧!我们有了它,或者说几乎有了。现在,当你更改输入的值时,我们的输出将反映出来。
查看 CodePen 上的 输入输出 #3 示例,由 Robin Rendle (@robinrendle) 制作。
我们可能应该通过为输出设置默认值来稍微改进一下,这样它就可以在你加载页面时立即显示。我们可以使用 HTML 本身在输出中设置值
<output for="quantity">50</output>
但我认为这不是特别稳健。当我们想更改输入的最小值或最大值时会发生什么?我们也必须始终更改我们的输出。让我们在脚本中设置输出的状态。这是一个名为 setDefaultState
的新函数
function setDefaultState() {
output.value = rangeInput.value;
}
当 DOM 加载完毕后,执行该函数
document.addEventListener('DOMContentLoaded', function(){
setDefaultState();
});
查看 CodePen 上的 输入输出 #4 示例,由 Robin Rendle (@robinrendle) 制作。
现在我们可以对所有内容进行样式设置!但还有一件事。事件监听器 change
很好,但它不会在您左右滑动时立即更新文本。幸运的是,有一个名为 input
的新事件监听器类型,它具有 相当不错的浏览器支持,我们可以用它来代替。以下是我们添加了这个功能后的所有代码
const rangeInput = document.querySelector('input');
const output = document.querySelector('output');
function setDefaultState() {
output.value = rangeInput.value;
}
rangeInput.addEventListener('input', function() {
output.value = this.value;
});
document.addEventListener('DOMContentLoaded', function() {
setDefaultState();
});
查看 CodePen 上的 输入输出 #5 示例,由 Robin Rendle (@robinrendle) 制作。
我们有了它!一个带有输出的输入。
我知道这个元素,但它提出了一个问题:“它到底有什么用!?”为什么不使用
<div>
或其他任何基于文本的容器呢?语义?嗯,谷歌不会读取动态内容,所以我想这没什么用。如果它能够在不需要 JS 的情况下输出值,例如通过“for”属性绑定和功能,就像元素中的委托点击一样,那将非常有用。
但只是一个没有功能的容器?不,我不喜欢。
它是为了可访问性——据说它会自动连接到您使用
for
属性指示的输入,最重要的是,它可以被<label>
标记。如果它能够提供任何内置的互连性,比如...您可以通过范围滑块或修改
output
的值来修改最终值,那将会很棒;因为良好的 UX 已经通过range
和input
的组合实现了这一点。但你是对的...它...什么也不是。它确实为浏览代码的开发人员提供了清晰度,但归根结底,使用任何其他元素的效果一样好,这很遗憾。看来这是一个真正的错失良机。
作为以下形式更有意义
介意分享一下原因吗?
@pat,我认为原因仅仅是内存效率稍微高一点——不需要创建匿名函数来调用
setDefaultState
(并且不执行其他操作),而可以直接传入setDefaultState
。我会这样做
我认为这是最明智的做法。
这是最明智的做法,也就是 DRY 的方法。
我用它来为范围滑块的滑块创建工具提示。
我使用的结构是一个带有范围
input
、label
和output
(后两者通过for
属性与input
绑定)的包装元素。我使用 Pug 生成这个结构,这样我就可以将范围的最小值、最大值和当前值放入变量中(你会马上看到为什么)。在 CSS 中,我将包装元素上的
display
设置为flex
,将flex-direction
设置为column-reverse
,这样label
就会出现在input
之前。然后,我将包装元素上的position
设置为relative
,将output
上的position
设置为absolute
。我还确保input
及其轨道与包装元素具有相同的width
。此时,将
output
上的left
设置为0
会使其左边缘与包装元素的左边缘对齐,与input
的左边缘一致。将left
设置为100%
会使其左边缘与其父元素(包装元素)的右边缘对齐,与input
的右边缘一致。如果我们还设置了transform: translate(-50%)
,那么它的中间垂直轴将与input
的左边缘(left: 0
的情况)或input
的右边缘对齐。但如果我们希望它充当范围滑块的工具提示,那么它需要在与滑块相同的范围内移动。滑块在 Chrome 的 轨道 的
content-box
内移动,在 Firefox 和 Edge 的 input 的content-box
内移动。但是,如果包装元素、实际的范围输入及其范围轨道的width
相同,没有padding
、border
也没有margin
,那么它们的content-box
将相同。这意味着滑块的中心垂直轴在
input
左边缘右侧半个滑块宽度到input
右边缘左侧半个滑块宽度之间移动。因此,我们将包装元素的
width
(由input
及其轨道继承)设置为这两个端点之间的距离($d
)加上滑块宽度($tw
),因为我们在一个端点处有半个滑块宽度,在另一个端点处有第二个半个滑块宽度。我们还将output
的left
设置为半个滑块宽度(.5*$tw
)。然后,我们计算运动的数值范围(输入的最大值和最小值之间的差值),使用内联设置的 CSS 变量计算
output
元素的起始位置(--pos
),然后我们将此位置值添加到translate()
函数中。最后,在 JS 中,我们需要在
input
的值发生变化时更新--val
和output
的值。这使得
output
能够与范围滑块的滑块一起移动。不需要其他 JS 来定位,所有这些都通过一些 CSS 变量魔法完成。你可以查看 此演示(或者如果你喜欢视频,你可以看到我在 CodePenDay 汉堡的 现场编码 中让自己尴尬...显然,我不会拼写属性名称)。
由于一个据说是本月发布版本中已修复的错误,它在当时无法在 Edge 中运行。
不错!
很棒。为什么要监听/响应“change”和“update”事件呢?
它会导致 SR 两次读取更新后的值。
以下是 React 中的实现,仅供参考
很棒——Operator Mono,非常适合编码的字体!
关于该元素的另一个优点是,它隐式地是一个“实时区域”,因此每当范围值更改时,SR 用户都会听到更新的值。
我们可以依靠
output
/input
之间的显式关系,并动态绑定所有元素……