模态框。一个小弹窗,用于告知你一些重要信息。这有多难?嗯…中等难度吧。这里有很多需要考虑的地方,也有一些棘手的问题需要解决。让我们来数一数。
在 DOM 中的位置?
我通常会在结束标签 </body>
之前放置模态框的 HTML 代码。
<div class="modal" id="modal"></div>
</body>
</html>
这主要出于样式考虑。当你处理覆盖整个页面的 body 元素时,定位模态框会更容易,而不是处理一组未知的父元素,每个父元素都可能具有其自己的定位上下文。
这对屏幕阅读器来说怎么样?我不是无障碍专家,但我听说模态框非常棘手。 Rob Dodson:
任何尝试使模态框无障碍的人都知道,尽管它们看起来很普通,但**模态框实际上是 Web 无障碍性方面的最终 Boss 战**。它们会把你嚼碎然后吐出来。例如,一个合适的模态框需要具备以下功能
- 键盘焦点应移至模态框内部,并在模态框关闭时恢复到之前的 activeElement
- 键盘焦点应被限制在模态框内,以防止用户意外地跳出模态框(也称为“逃离模态框”)
- 屏幕阅读器也应被限制在模态框内,以防止意外逃离
Rob 建议查看 The Incredible Accessible Modal Window 演示。我还看到过 Noah Blon 最近的一个示例 和 Nicolas Hoffman 的一个示例。此外,ARIA 实时区域 在这里也很重要。
如果你自己处理焦点,将模态框放在文档底部似乎是可以接受的。
居中
其中有一个我最喜欢的技巧。那个无需明确知道宽度或高度就能同时垂直和水平居中的技巧
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
这非常适合模态框,因为模态框通常是完全居中的,并且可能具有不同宽度的不同版本。高度更有可能发生变化,因为高度总是与内部内容相关。

如果你完全确定模态框的高度和宽度,可以考虑其他居中方法。我之所以提到这一点,是因为使用 transform 可能会导致文本略微模糊,所以如果你遇到此问题,请查看居中指南中的其他方法(例如负边距)。
固定?
请注意我们使用了 position: fixed;
。这样做是为了,即使用户向下滚动页面,触发模态框,模态框也能像他们没有滚动时一样居中并可见。
我想,总的来说,即使在“移动设备”上,我也认为固定定位现在相当安全。但如果你知道你正在处理相当一部分非常旧的手机,固定定位可能会成为问题,你可能需要考虑使用 position: absolute;
并强制滚动到页面顶部。或者其他什么方法,我不知道;进行测试。
处理宽度
在大屏幕上,典型的模态框的外观不仅居中,而且宽度有限(如上图所示)。
.modal {
/* other stuff we already covered */
width: 600px;
}
这是危险信号区域。我们在大型屏幕上获得了我们想要的效果,但我们知道还有很多屏幕的宽度甚至不到 600px。

使用 max-width
轻松修复
.modal {
/* other stuff we already covered */
width: 600px;
max-width: 100%;
}

处理高度
设置高度更是危险信号。我们知道内容会发生变化!此外,transform 居中技术很乐意在没有滚动条的情况下截断模态框的顶部以节省空间

设置最大高度将再次拯救我们
.modal {
/* other stuff we already covered */
height: 400px;
max-height: 100%;
}

处理溢出
既然我们开始设置高度,就需要考虑溢出。在 .modal
本身上使用 overflow
值很诱人,但这样做有两个问题
- 我们可能希望某些元素不滚动
- 溢出会截断
box-shadow
,而我们可能需要它
我建议使用内部容器
<div class="modal" id="modal">
<!-- things that don't scroll -->
<div class="modal-guts">
<!-- things that scroll -->
</div>
</div>
为了使内部内容滚动,它需要一个高度。这里有多种可能性。一种是将其定位为覆盖整个模态框,然后添加溢出
.modal-guts {
/* other stuff we already covered */
/* cover the modal */
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* spacing as needed */
padding: 20px 50px 20px 20px;
/* let it scroll */
overflow: auto;
}

按钮
模态框的目的是在执行其他任何操作之前强制执行某个操作。如果你没有强制执行操作,请考虑使用其他UI 而不是模态框。需要有某种方法可以退出模态框。选项按钮很常见(例如“删除”/“取消”)。关闭按钮也很常见。让我们为我们的模态框添加一个关闭按钮。
始终显示关闭按钮似乎很明智,这样用户就不会处于无法直观地了解如何关闭模态框的状态。这就是我们创建不可滚动区域的原因。

处理覆盖层
模态框通常会伴随着一个覆盖整个屏幕的覆盖层。这出于多种原因很有用
- 它可以使屏幕的其余部分变暗(或以其他方式静音),从而强化模态框的“你需要处理它才能离开”的目的。
- 它可以用来阻止模态框外部的点击/交互。
- 它可以作为巨大的关闭按钮。或者“取消”或任何最不显眼的操作。
典型处理方式
<div class="modal" id="modal">
<!-- modal stuff -->
</div>
<div class="modal-overlay" id="modal-overlay">
</div>
.modal {
/* stuff we already covered */
z-index: 1010;
}
.modal-overlay {
/* recommendation:
don't focus on the number "1000" here, but rather,
you should have a documented system for z-index and
follow that system. This number should be pretty
high on the scale in that system.
*/
z-index: 1000;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
使用类关闭(而不是使用类打开)
我发现默认情况下将 .modal
类隐藏起来非常诱人,可能使用 display: none;
。然后要打开它,添加一个 open 类,例如 .modal.open { display: block; }
。
但请注意那里的 display: block;
吗?我认为这是一个问题。display: none;
非常有用,因为它可以从视觉上和辅助技术上隐藏模态框。**在现有 display
值之上应用它比通过猜测值进行覆盖更容易。**这意味着你的 .modal
可以使用 display: flex;
或 display: grid;
或任何其他有用的值。模态框的不同变体可以使用任何它们想要的值,而无需担心它们会被重置为 display: block;
。
.modal {
/* for example... */
display: flex;
}
.modal.closed {
display: none;
}
切换开放性
这是我们目前为止打开和关闭的最基本的方法。
var modal = document.querySelector("#modal");
var modalOverlay = document.querySelector("#modal-overlay");
var closeButton = document.querySelector("#close-button");
var openButton = document.querySelector("#open-button");
closeButton.addEventListener("click", function() {
modal.classList.toggle("closed");
modalOverlay.classList.toggle("closed");
});
openButton.addEventListener("click", function() {
modal.classList.toggle("closed");
modalOverlay.classList.toggle("closed");
});
这还没有处理可访问性问题。请记住,这是我们上面讨论过的一个考虑因素。这是一个演示,它处理移动焦点、捕获焦点以及将焦点返回到它最初所在的位置。
样式考虑演示
查看 Chris Coyier 在 CodePen 上的 Pen 模态样式的注意事项 (@chriscoyier)。
我错过了什么?随时可以复制这个内容并告诉我。
在为灵活高度设置样式时,是否应该使用
max-height:100%
?涵盖了很多方面……不得不说这是一篇关于模态的优秀指南。
确实如此!在 [#article-header-id-4](处理高度) 部分中已经介绍过了。
我想指出的是,高度部分中的
max-width:100%
是否是错别字?:D啊哈!明白了。是的。
您错过的另一个重要的 UX 考虑因素是关于模态打开时背景的滚动。通常,如果您在一个很长的滚动页面上,当模态打开时,您仍然可以滚动背景,这在大多数情况下是不可取的。解决此问题的办法是在打开/关闭模态时,通过类切换向
<body>
添加overflow-y: none
。有趣。为什么您能够滚动页面很重要?因为感觉/看起来很奇怪?我担心隐藏垂直溢出会将它们滚动回顶部,这似乎也很奇怪?
我或许可以提出一个类似于上述可访问性问题的论点;将视口焦点返回到模态打开之前的那个位置。如果您阻止额外的滚动,则可以确保用户在模态结束后会返回到他们之前的位置。
我不确定这是否普遍有用,但如果您使用的是数据库或类似应用程序的界面,我可以看到能够更细粒度地为用户设置导航阶段的好处。因此,可能取决于平台的目标和范围。
另外需要注意的是,向
overflow:hidden
添加会冻结视口当前所在的位置,尽管您可能还需要处理滚动条消失的问题。我最近遇到了这个问题,它有两个问题
如果页面已滚动,当您返回时,它会位于顶部。对于无限滚动页面来说,这完全不可取。
如果您不隐藏模态中的所有内容(例如导航栏),即使如此,页面也会因滚动条可见/不可见而发生水平抖动。
似乎当前的 Chrome(OSX 上的 51.0.2704.103)在主体上隐藏
overflow-y
时不会滚动回顶部。只需在此页面上修改它通过检查器进行测试即可。我认为最好用 JavaScript 来处理这个问题,捕获并取消滚动事件。
我从未遇到过如果将
body
的overflow-y
设置为none
,页面就会滚动回顶部的现象。在 Chrome 中,页面只是停留在它所在的位置。显然,当您删除该规则时,滚动条会像往常一样恢复。是否有特定浏览器会滚动到顶部?我完全同意如果出现这种情况,用户体验会很糟糕。我只是从未见过这种情况。相关地,几个流行的模态库将此样式添加到 body 标签。
这是一个用于测试的笔:http://codepen.io/jakobud/pen/rLdjpX
关于为什么滚动背景是不可取的,我想对我来说,重要的是当模态弹出并在稍后关闭时,我希望页面正好回到它之前的位置。如果我不小心滚动背景而没有立即意识到,那么我必须找到我之前在页面上的位置。
如果您的模态内容足够长以至于有自己的滚动条,则很容易意外地滚动背景(这本身是否是不好的 UX 是另一个话题 ;-)
此外,根据模态叠加层的透明度,用户甚至可能没有意识到他们正在滚动背景。
我以为就是这样,但您的测试运行完美,因此显然我在其他地方有错误导致它跳到开头。非常感谢您在没有看到代码的情况下调试我的代码(
顺便说一句,我很好奇“z-index 的文档化系统”会是什么样子?
也许像这样(只是一种方式);)
这是一个使用 SCSS 管理 z-index 系统的很好的例子:https://www.sitepoint.com/better-solution-managing-z-index-sass/
是否最好使用以下内容而不是 translate
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
见此笔
http://codepen.io/henchmen/pen/PzQpvk
在 IE8 及更高版本中都能正常工作,无需添加前缀。
对我来说很聪明!由于浏览器支持似乎非常好并且模糊文本的风险消失了,因此应该更经常地使用它。
这样做的缺点是它仅在指定固定高度时才有效,这不太理想。
我首选的、非模糊的解决方案是在容器上使用 flexbox 进行垂直居中
以及在浮动窗口上使用
margin: 0 auto;
进行水平居中(这为无法处理 flexbox 的浏览器提供了不错的回退,否则我们只需在容器上使用justify-content: center;
即可将所有居中代码放在一个位置)。我一开始就是这样做的,然后切换到 flexbox 方法。对我来说,它感觉更自然,也是正确的方法。
最近,我还使用这种技术开发了一个小型模态插件。
我可能会更新它,使其更易于访问。
这种方法的关键问题在于您必须硬编码模态的宽度和高度。
left: 50%; top: 50%; translate(-50% -50%)
规则 ;-)确实,但是由于它是一个模态,因此您通常希望在其周围留有边距,以便您可以看到后面的内容,并且用户知道它是一个模态,而不是另一个页面或其他 UI 元素。
在我看来,如果没有边距,它就不是模态,它就变成了滑出或其他东西。
由于您需要边距,因此您可以将顶部、左侧等定义为 5% 或其他值,然后您可以定义最大宽度、最大高度、宽度和高度以获得所有设备的出色外观。因此,这种方法的唯一缺点是,如果您的模态内容很少,则模态会大于其内容。不一定是完美的,但您可以通过简单的设置和没有浏览器问题在所有浏览器中获得一致的外观,不像 translate 方法。
此外,同样在我看来,以一致的大小打开模态比以不同的大小打开模态要好得多,即使有一些空白。当模态居中时尤其如此,因为关闭按钮将始终位于相同的位置,从而提高可用性。如果它改变大小并居中,则关闭按钮将始终位于不同的位置。
flexbox 方法似乎也很好,但是回退会将模态对齐到顶部,对于 IE10-,并且需要各种前缀混乱才能获得一致的支持。同样,如果您追求一致的大小以获得更好的可用性,我的示例更好,因为它可以在 IE8+ 中毫无问题地运行。
糟糕,刚测试过,它在 IE8 中没有垂直居中,但在 9 和 10 中居中。
在 .open 和 .close 上使用模态框和遮罩层的动画效果怎么样?我认为在谈到模态框时,这是真正需要讨论的问题,当你使用 display: none 时,transform: translate(x) 不再起作用,等等等等。我知道你要说什么……“使用 JavaScript” :P 但如果只使用 CSS 并添加/移除类就有更简洁的方法呢?
你可能可以这样做
…(因为这样会产生动画效果)。但可能也应该修改 aria-hidden,以便辅助技术也能将其隐藏。
相关:https://css-tricks.org.cn/snippets/css/toggle-visibility-when-hiding-elements/
我通常使用 line-height 来垂直居中我的模态框,这允许它在底部溢出,并在需要时在它的容器中移动。
我想这可以讨论,但我发现当遮罩层滚动而不是模态框本身时,用户体验更好。但这会改变你实现
max-height
的方式,如果模态框节点在遮罩层节点内部,则会更容易。不过,我不确定这对可访问性有什么影响(如果有的话)。Bootstrap 在 body 上设置
overflow: hidden
以防止模态框外部的内容滚动,然后添加模态框位置的偏移量,这样在打开/关闭模态框期间就不会出现布局偏移,这整个事情就是这样。真是太纠结了。我们也使用平移方法进行居中,但有一个很关键的陷阱一直是个噩梦——Chrome 在处理奇数高度的容器时存在问题,会导致子像素问题——字体可能看起来模糊,内部元素可能会出现奇怪的偏移。处理起来真是让人头疼。
一如既往,Chris 的文章很棒。
鉴于你希望模态框代码位于关闭 body 标签旁边,那么如何在 WordPress 等 CMS 中处理模态框呢?
我最近做了一些模态框设计:http://codepen.io/fauxserious/pen/mEPYrB
之间有什么区别吗?
和
?
这很有趣,我从未真正想过这个问题……
我想……没有。
http://codepen.io/chriscoyier/pen/pbaQvG/
是的,它们之间有很大的区别。
代码,
这里元素的宽度将为 600px,无论容器的宽度如何。当你应用
max-width: 100%
时,如果容器的大小小于元素的宽度,则元素的宽度将变为 100%。然而,
此代码将使元素的宽度等于容器的宽度,直到容器的宽度小于 600px。一旦容器的宽度超过 600px,例如 960px,则元素的宽度将保持 600px。
要将其居中,请应用
margin: auto;
希望这有帮助!
我想我解释错了。Chris 是对的!
忽略我之前的回复。
谢谢!
你认为 HTML5 元素
<dialog>
如何,它在 Blink 中受支持,并且可以使用 polyfill?默认样式仍然很糟糕,你需要自定义 CSS 才能使其在移动设备上正常工作。
你可以添加点击外部关闭的功能。我更喜欢将遮罩层写在 .modal 内部,这样你就可以只用一个简单的更改来切换它们,而不是两个。
我在 Picnic CSS 中创建了一个原生 CSS 模态框 http://picnicss.com/documentation#modal,我建议开发者可以做的一件事是添加一个
ESC
事件监听器来关闭它。感谢这篇文章,我错过了一些东西,所以我会确保对其进行增强。
你错过了什么……好吧,在 iPhone 上,你必须“锁定”主页面滚动。
如果页面可滚动,那么即使顶部有一个模态框,你仍然可以滚动该页面。会导致奇怪的行为。
但你必须使用 JavaScript 来实现。在打开时 = 锁定主页面(使用偏移量隐藏溢出),在关闭时 = 移除锁定。
当然,“-webkit-overflow-scrolling: touch;”也不能错过。
也许模态框遮罩层也可以是一个伪元素 :)
哦,好主意!
我也见过巨大的 box-shadow。我想这取决于第二个元素有多有用。管理两个元素是否更麻烦?或者因为它处理点击事件而有用?
来这里也是想说这个。我之前在模态框中使用过 ::after,效果很好。老实说,我认为从 DOM 的角度来看,将其作为伪选择器更有意义,因为它本身并不属于文档流。它本质上纯粹是表现性的,因此最好通过 CSS 直接包含和控制。
@Elliot,除非你想阻止点击穿透到遮罩层下方的内容(那样它就比仅仅是表现性更有用了)。我喜欢将点击事件绑定到遮罩层以关闭模态框。
我通常会将模态框包装在一个
<div>
中,然后使用该包含的<div>
作为遮罩层,并将模态框“固定”在其中。这样,你就可以根据需要向遮罩层添加点击事件,并且你仍然只需要处理一个显示和隐藏的元素,而不是两个。此外,使用此方法,Z-index 成为一个非问题,根据我的经验,这是 Bootstrap 和其他此类库实现的真正问题。@Todd 当然 :)
我还喜欢做的两件事是点击外部关闭和按 ESC 键关闭。我觉得有些人期望这种功能,但谁知道呢。
我同意你的看法!在我的最新网页上,我实现了 ESC 键和点击外部关闭。我还添加了 Enter 键来接受默认选项——例如,当人们按下网页上的打印图标时,我有一个模态框弹出。然后模态框会提醒人们进行负责任的打印——不要浪费纸张——Enter 键是打印,ESC 键是关闭模态框。
你好,
你认为使用模态框窗口本身的 :before 来制作模态框遮罩层可以吗?我通常这样做,这样我就不必在 HTML 中创建另一个 div,也不必在 JavaScript 代码中考虑它。
希望我的意思表达清楚,请原谅我的英语水平:)。谢谢
哦,我刚刚读了评论:D 我认为这也是一个更好的主意 :)
由于我们将遮罩层元素放在模态框元素旁边,我们可以使用 JavaScript 切换模态框的类名以显示/隐藏模态框,并使用
+
CSS 选择器来显示/隐藏遮罩层一篇关于模态框的精彩文章,Chris,写得很好。
关于 accessible-modal-dialog,不幸的是,它不再维护了(上次提交是在 10 个月前,没有回复问题和 PR)。
我们在 Edenspiekermann 对其进行了 fork,创建了 a11y-dialog,一个超小的但可访问的模态框脚本(没有任何样式考虑,因此你的帖子对任何使用它的人都有帮助)。我们对初始库进行了大量改进,首先是删除了 jQuery 依赖项。:)
如果它有帮助。
关于响应式设计
我想知道哪种方法更好,是将宽度/高度设置为100%,然后限制到某个特定数值,还是像您在这里做的那样——设置一个特定数值,然后限制到100%?两种方案似乎都能达到类似的效果。
您怎么看?能否提供一些选择其中一种方案的理由呢?
(刚刚注意到其他人也问了完全相同的问题。您可以随意删除这条评论 :))
使用
:target
打开模态窗口 https://codepen.io/mihdan/pen/GqdKQp我遇到过几次这种情况。如果您希望模态窗口中的滚动在iOS设备上保持自然状态,您可能需要考虑https://css-tricks.org.cn/snippets/css/momentum-scrolling-on-ios-overflow-elements/
我们可以使用以下代码在元素中避免模糊文本