为什么带有隐藏溢出的元素的 scrollHeight 属性 不正确?
Why is the scrollHeight property for elements with hidden overflow not correct?
为了避免 XY 问题,我应该解释一下我实际上想做什么。我正在尝试动画显示和隐藏 div
。我的策略是将 max-height
设置为 scrollHeight
属性 的计算高度并设置 overflow: hidden
。要隐藏它,我可以将 max-height
设置为 "0px"
,为了显示它,我将它设置回 scrollHeight
,并且我可以在 max-height
属性 上设置动画.
要明确的是,当div
处于hidden
状态时,它有max-height: 0
,因为overflow: hidden
它不会出现。在 visible
状态下,它有一个 max-height
应该正好等于它的正常高度。我还没有找到任何其他方法来做到这一点。
这似乎有效,但如果 div
一开始是隐藏的,它就不起作用。
这是一个 MWE:
切换 1 和切换 2 相同。唯一的区别是 Toggle 1 开始可见,Toggle 2 不可见。您可以看到 Toggle 1 按预期工作,但 Toggle 2 只显示 div
的一半。这是因为 scrollHeight
只是应有的一半。
切换 3 不包括 align-items: center
,它更接近,但仍然不太正确,相差 3px。
form1 = document.getElementById('form1');
console.log('Setting form1 height to ' + form1.scrollHeight);
form1.style.maxHeight = form1.scrollHeight + "px";
function toggle1() {
form1.classList.toggle('hide');
}
form2 = document.getElementById('form2');
console.log('Setting form2 height to ' + form2.scrollHeight);
form2.style.maxHeight = form2.scrollHeight + "px";
function toggle2() {
form2.classList.toggle('hide');
}
form3 = document.getElementById('form3');
console.log('Setting form3 height to ' + form3.scrollHeight);
form3.style.maxHeight = form3.scrollHeight + "px";
function toggle3() {
form3.classList.toggle('hide');
}
.form {
display: flex;
align-items: center;
overflow: hidden;
transition: max-height 0.3s linear;
}
.form3 {
display: flex;
overflow: hidden;
transition: max-height 0.3s linear;
}
.hide {
max-height: 0 !important;
}
.input {
font-size: 28px;
}
.btn {
font-size: 28px;
}
<button onclick="toggle1()">Toggle 1</button>
<div id="form1" class="form">
<input class="input">
<button class="btn">Submit</button>
</div>
<button onclick="toggle2()">Toggle 2</button>
<div id="form2" class="form hide">
<input class="input">
<button class="btn">Submit</button>
</div>
<button onclick="toggle3()">Toggle 3</button>
<div id="form3" class="form3 hide">
<input class="input">
<button class="btn">Submit</button>
</div>
<p>More content</p>
根据 docs,这应该有效。
The Element.scrollHeight read-only property is a measurement of the height of an element's content, including content not visible on the screen due to overflow.
div
没有任何填充或边框,所以看起来这应该是完全正确的。
我不知道这里发生了什么。 这是一个错误吗? 此行为至少存在于 Firefox 90 和 macOS 上的 Chrome 88。
有什么我不理解的基本原理导致这不可能吗?
我不明白为什么 align-items: center
很重要。这表明我没有足够的信息来推断这个问题。
还是我只是缺少其他需要做的事情?
也许这完全可行,但我只是忘记了一些事情。
最后,参考我之前所说的,我真的只想要一个开始隐藏的 div,我可以用动画打开/关闭它。也许有不同的方法可以做到这一点。但在这一点上,我对这个问题投入了足够的精力,我真的很想知道这里发生了什么。
一个解决方法是加载 form2 不带它的隐藏 class 但是使用 JS 立即添加它你已经阅读了 'correct' scrollHeight。
form2.classList.add('hide');
form1 = document.getElementById('form1');
console.log('Setting form1 height to ' + form1.scrollHeight);
form1.style.maxHeight = form1.scrollHeight + "px";
function toggle1() {
form1.classList.toggle('hide');
}
form2 = document.getElementById('form2');
console.log('Setting form2 height to ' + form2.scrollHeight);
form2.style.maxHeight = form2.scrollHeight + "px";
form2.classList.add('hide');
function toggle2() {
form2.classList.toggle('hide');
}
form3 = document.getElementById('form3');
console.log('Setting form3 height to ' + form3.scrollHeight);
form3.style.maxHeight = form3.scrollHeight + "px";
function toggle3() {
form3.classList.toggle('hide');
}
.form {
display: flex;
align-items: center;
overflow: hidden;
transition: max-height 0.3s linear;
}
.form3 {
display: flex;
overflow: hidden;
transition: max-height 0.3s linear;
}
.hide {
max-height: 0 !important;
}
.input {
font-size: 28px;
}
.btn {
font-size: 28px;
}
<button onclick="toggle1()">Toggle 1</button>
<div id="form1" class="form">
<input class="input">
<button class="btn">Submit</button>
</div>
<button onclick="toggle2()">Toggle 2</button>
<div id="form2" class="form">
<input class="input">
<button class="btn">Submit</button>
</div>
<button onclick="toggle3()">Toggle 3</button>
<div id="form3" class="form3 hide">
<input class="input">
<button class="btn">Submit</button>
</div>
<p>More content</p>
我意识到这不是一个完整的解释,但一个观察结果是在 form3 上仍然有一个 flex - 它删除了输入和按钮上的 'natural' 垂直居中,因此高度更小。如果你完全移除 flex 高度(和居中)return.
寻找完整的解释我怀疑,但无法证明,通过 class 设置隐藏,尽管有重要的设置,而最大高度设置为内联以某种方式改变了隐藏的顺序浏览器计算滚动高度。
如果我们查看 scrolling area 的规范,就可以解释 align-items:center
的行为。上面写着垂直滚动区域来自
The element’s top padding edge.
到
The bottom-most edge of the element’s bottom padding edge and the bottom margin edge of all of the element’s descendants' boxes, excluding boxes that have an ancestor of the element as their containing block.
注意不平衡。包括底部填充边缘下方的溢出,但不包括顶部填充边缘上方的溢出。所以当 flex 容器的高度为 0 时,一半的溢出会在顶部 padding 边缘之上而不会被计算在内。因此 20px 而不是 40px。
form3 的 align-items:initial
(即拉伸)行为可能更加微妙。高度来自按钮,输入字段要小得多。因此按钮由按钮元素生成的外框和按钮内的“提交”文本生成的内框组成。内框从外框的顶部边框和填充 (3px) 下方开始,高度为 34px。现在外框被最大高度值减小到 0px 高度,让内框溢出,从而将内框的底部边距边缘放置在 div 的顶部填充边缘下方 37px。按照与上面相同的规则来调整滚动区域的大小,因此 scrollHeight 为 37px。
我推荐@AHaworth 的解决方法来避免这个问题。
以上像素测量值适用于 Firefox。 Chromium 的内容框尺寸略小,但遵循相同的规则。
为了避免 XY 问题,我应该解释一下我实际上想做什么。我正在尝试动画显示和隐藏 div
。我的策略是将 max-height
设置为 scrollHeight
属性 的计算高度并设置 overflow: hidden
。要隐藏它,我可以将 max-height
设置为 "0px"
,为了显示它,我将它设置回 scrollHeight
,并且我可以在 max-height
属性 上设置动画.
要明确的是,当div
处于hidden
状态时,它有max-height: 0
,因为overflow: hidden
它不会出现。在 visible
状态下,它有一个 max-height
应该正好等于它的正常高度。我还没有找到任何其他方法来做到这一点。
这似乎有效,但如果 div
一开始是隐藏的,它就不起作用。
这是一个 MWE:
切换 1 和切换 2 相同。唯一的区别是 Toggle 1 开始可见,Toggle 2 不可见。您可以看到 Toggle 1 按预期工作,但 Toggle 2 只显示 div
的一半。这是因为 scrollHeight
只是应有的一半。
切换 3 不包括 align-items: center
,它更接近,但仍然不太正确,相差 3px。
form1 = document.getElementById('form1');
console.log('Setting form1 height to ' + form1.scrollHeight);
form1.style.maxHeight = form1.scrollHeight + "px";
function toggle1() {
form1.classList.toggle('hide');
}
form2 = document.getElementById('form2');
console.log('Setting form2 height to ' + form2.scrollHeight);
form2.style.maxHeight = form2.scrollHeight + "px";
function toggle2() {
form2.classList.toggle('hide');
}
form3 = document.getElementById('form3');
console.log('Setting form3 height to ' + form3.scrollHeight);
form3.style.maxHeight = form3.scrollHeight + "px";
function toggle3() {
form3.classList.toggle('hide');
}
.form {
display: flex;
align-items: center;
overflow: hidden;
transition: max-height 0.3s linear;
}
.form3 {
display: flex;
overflow: hidden;
transition: max-height 0.3s linear;
}
.hide {
max-height: 0 !important;
}
.input {
font-size: 28px;
}
.btn {
font-size: 28px;
}
<button onclick="toggle1()">Toggle 1</button>
<div id="form1" class="form">
<input class="input">
<button class="btn">Submit</button>
</div>
<button onclick="toggle2()">Toggle 2</button>
<div id="form2" class="form hide">
<input class="input">
<button class="btn">Submit</button>
</div>
<button onclick="toggle3()">Toggle 3</button>
<div id="form3" class="form3 hide">
<input class="input">
<button class="btn">Submit</button>
</div>
<p>More content</p>
根据 docs,这应该有效。
The Element.scrollHeight read-only property is a measurement of the height of an element's content, including content not visible on the screen due to overflow.
div
没有任何填充或边框,所以看起来这应该是完全正确的。
我不知道这里发生了什么。 这是一个错误吗? 此行为至少存在于 Firefox 90 和 macOS 上的 Chrome 88。
有什么我不理解的基本原理导致这不可能吗?
我不明白为什么 align-items: center
很重要。这表明我没有足够的信息来推断这个问题。
还是我只是缺少其他需要做的事情?
也许这完全可行,但我只是忘记了一些事情。
最后,参考我之前所说的,我真的只想要一个开始隐藏的 div,我可以用动画打开/关闭它。也许有不同的方法可以做到这一点。但在这一点上,我对这个问题投入了足够的精力,我真的很想知道这里发生了什么。
一个解决方法是加载 form2 不带它的隐藏 class 但是使用 JS 立即添加它你已经阅读了 'correct' scrollHeight。
form2.classList.add('hide');
form1 = document.getElementById('form1');
console.log('Setting form1 height to ' + form1.scrollHeight);
form1.style.maxHeight = form1.scrollHeight + "px";
function toggle1() {
form1.classList.toggle('hide');
}
form2 = document.getElementById('form2');
console.log('Setting form2 height to ' + form2.scrollHeight);
form2.style.maxHeight = form2.scrollHeight + "px";
form2.classList.add('hide');
function toggle2() {
form2.classList.toggle('hide');
}
form3 = document.getElementById('form3');
console.log('Setting form3 height to ' + form3.scrollHeight);
form3.style.maxHeight = form3.scrollHeight + "px";
function toggle3() {
form3.classList.toggle('hide');
}
.form {
display: flex;
align-items: center;
overflow: hidden;
transition: max-height 0.3s linear;
}
.form3 {
display: flex;
overflow: hidden;
transition: max-height 0.3s linear;
}
.hide {
max-height: 0 !important;
}
.input {
font-size: 28px;
}
.btn {
font-size: 28px;
}
<button onclick="toggle1()">Toggle 1</button>
<div id="form1" class="form">
<input class="input">
<button class="btn">Submit</button>
</div>
<button onclick="toggle2()">Toggle 2</button>
<div id="form2" class="form">
<input class="input">
<button class="btn">Submit</button>
</div>
<button onclick="toggle3()">Toggle 3</button>
<div id="form3" class="form3 hide">
<input class="input">
<button class="btn">Submit</button>
</div>
<p>More content</p>
我意识到这不是一个完整的解释,但一个观察结果是在 form3 上仍然有一个 flex - 它删除了输入和按钮上的 'natural' 垂直居中,因此高度更小。如果你完全移除 flex 高度(和居中)return.
寻找完整的解释我怀疑,但无法证明,通过 class 设置隐藏,尽管有重要的设置,而最大高度设置为内联以某种方式改变了隐藏的顺序浏览器计算滚动高度。
如果我们查看 scrolling area 的规范,就可以解释 align-items:center
的行为。上面写着垂直滚动区域来自
The element’s top padding edge.
到
The bottom-most edge of the element’s bottom padding edge and the bottom margin edge of all of the element’s descendants' boxes, excluding boxes that have an ancestor of the element as their containing block.
注意不平衡。包括底部填充边缘下方的溢出,但不包括顶部填充边缘上方的溢出。所以当 flex 容器的高度为 0 时,一半的溢出会在顶部 padding 边缘之上而不会被计算在内。因此 20px 而不是 40px。
form3 的 align-items:initial
(即拉伸)行为可能更加微妙。高度来自按钮,输入字段要小得多。因此按钮由按钮元素生成的外框和按钮内的“提交”文本生成的内框组成。内框从外框的顶部边框和填充 (3px) 下方开始,高度为 34px。现在外框被最大高度值减小到 0px 高度,让内框溢出,从而将内框的底部边距边缘放置在 div 的顶部填充边缘下方 37px。按照与上面相同的规则来调整滚动区域的大小,因此 scrollHeight 为 37px。
我推荐@AHaworth 的解决方法来避免这个问题。
以上像素测量值适用于 Firefox。 Chromium 的内容框尺寸略小,但遵循相同的规则。