载说明:原创不易,未经授权,谢绝任何形式的转载
在我的好奇心驱使下,我想为什么不去查看一些热门网站,并了解一下它们是如何实现评论组件的布局。起初,我认为这将是一个简单的任务,但实际并非如此。
我花了很多时间试图理解这是如何工作的,以及如何通过现代 CSS(如 :has、size container queries 和 style queries)来改进它。在本文中,我将引导您了解我的思考过程,并分享我在其中所得到的发现。
以下是我们将要构建的布局。乍一看,它可能看起来很简单,但其中有很多微小的细节。
我们有一个评论,可以嵌套两个更深层次。我在本文中将这些称为“深度”。
图中展示了深度是如何根据每个评论的嵌套级别而变化的。
在深入细节之前,我更愿意先着手处理布局,并确保它能很好地运作。这样的做法旨在探索现代CSS解决该问题的潜力。
首先要记住的是HTML标记。评论的结构很适合使用无序列表<ul>。
<ul>
<li>
<Comment />
</li>
<li>
<Comment />
</li>
</ul>
< Comment /> 充当评论组件的占位符。这是两条评论的列表的HTML,没有任何回复。
如果对其中一条评论进行回复,那么将会添加一个新的 <ul>。
<ul>
<li>
<!-- Main comment -->
<Comment />
<ul>
<!-- Comment reply -->
<li>
<Comment />
</li>
</ul>
</li>
<li>
<Comment />
</li>
</ul>
从语义角度来看,上面的描述是合理的。
我将标题称为“评论包装器”,以免混淆评论组件本身的含义。在下一节中,我将解释我构建布局以处理评论回复的缩进或间距的想法。
请考虑以下标记:
<ul>
<li>
<!-- Main comment -->
<Comment />
<ul>
<!-- Comment reply -->
<li>
<Comment />
</li>
</ul>
</li>
<li>
<Comment />
</li>
</ul>
我更倾向于将所有的间距和缩进处理都保留在 <li> 元素上,因为它们充当了评论组件的容器。
我首先考虑的是在 <ul> 和 <li> 元素上使用数据属性。
<ul>
<li nested="true">
<!-- Main comment -->
<Comment />
<ul>
<!-- Comment reply -->
<li>
<Comment />
</li>
</ul>
</li>
<li>
<Comment />
</li>
</ul>
在CSS中,我们可以这样做:
li[data-nested="true"],
li[data-nested="true"]
li {
padding-left: 3rem;
}
虽然前面提到的方法是有效的,但是我们可以通过使用CSS变量和样式查询来进一步改进。
我们可以检查容器中是否添加了CSS变量--nested: true,并根据此对子元素进行样式设置。
考虑以下标记,我在 <ul> 元素中添加了内联CSS变量--nested: true。
<ul>
<li style="--nested: true;">
<!-- Main comment -->
<Comment />
<ul style="--nested: true;">
<!-- Comment reply -->
<li>
<Comment />
</li>
</ul>
</li>
<li>
<Comment />
</li>
</ul>
使用样式查询,我可以检查CSS变量是否存在,并根据其来为 <li> 元素添加样式。目前,这个特性只在 Chrome 的实验性版本 Canary 中得到支持。
@container style(--nested: true) {
/* Add spacing to the 2nd level <li> items. */
li {
padding-left: 3rem;
}
}
你提到为什么我更喜欢使用样式查询而不是数据属性的原因:
另一个解决方案是使用CSS子网格(subgrid)来构建嵌套评论布局。坦率地说,这将需要更多的CSS代码,但是探索新的CSS特性的潜力是非常有趣的。
让我简要地解释一下子网格(subgrid)来给您一个概念。考虑以下CSS网格:
<ul>
<li class="main">
<!-- Main comment -->
<Comment />
<ul>
<!-- Comment reply -->
<li>
<Comment />
<ul>
<li>
<Comment />
</li>
</ul>
</li>
</ul>
</li>
</ul>
li.main {
display: grid;
grid-template-columns: 3rem 3rem 1fr;
}
这将被添加到 <ul> 列表的第一个直接 <li> 元素中。这个网格看起来会像这样:
目前,在CSS网格中,不能将主网格传递给子项目。在我们的情况下,我希望将网格列传递给第一个 <ul>,然后再传递给该 <ul> 的 <li>。
幸好,CSS子网格(subgrid)使得这种操作成为可能。目前,它仅在Firefox和Safari浏览器中可用。Chrome浏览器也在朝这个方向发展!
请参考以下示意图:
首先,我们需要设置主网格如下所示。我们有3列。
@container style(--nested: true) {
li.main {
display: grid;
grid-template-columns: 3rem 3rem 1fr;
.comment {
grid-column: 1 / -1;
}
}
}
.comment 组件将始终跨越整个宽度。这就是为什么我添加了 grid-column: 1 / -1。这意味着:“从第一列到最后一列,让评论组件横跨全部列”。这样做有助于避免在嵌套的每个深度中手动输入列号。类似于这样:
/* Not good */
@container style(--nested: true) {
li.main .comment {
grid-column: 1 / 4;
}
ul[depth="1"] .comment,
ul[depth="2"] .comment {
grid-column: 1 / 3;
}
很好!接下来的步骤是将深度为1的评论放置在主网格内,然后添加子网格并定位内部的 <li> 元素。
@container style(--nested: true) {
ul[depth="1"] {
grid-column: 2 / 4;
display: grid;
grid-template-columns: subgrid;
> li {
grid-column: 1 / 3;
display: grid;
grid-template-columns: subgrid;
}
}
}
最后,我们需要定位深度为2的列表(depth=2)。
@container style(--nested: true) {
ul[depth="2"] {
grid-column: 2 / 3;
}
}
这个解决方案确实有效,但我对其中所有的细节并不太喜欢。一个简单的内边距就可以解决问题。
为了更清楚地显示评论和回复之间的关联,我们可以在主评论和回复之间添加连接线。Facebook团队使用了一个 <div> 元素来处理这些连接线。但是,我们能否尝试一些不同的方法呢?
请参考以下示意图:
你的第一反应可能会误导你:「嗨,这看起来就像一个带有左边框和底部边框以及左下角的边框半径的矩形。」
li:before {
content: "";
width: 30px;
height: 70px;
border-left: 2px solid #ef5da8;
border-bottom: 2px solid #ef5da8;
border-bottom-left-radius: 15px;
}
一开始我在上面的CSS代码中添加了height:..,但我意识到这样做不行。因为我无法准确知道连接线的高度。这是因为在CSS中无法直接根据内容动态调整高度。问题出在这里:我需要确保连接线的底部与第一个回复的头像对齐。
于是我想到可以使用伪元素来实现这个目的。如果那条弯曲的连接线可以分成两部分呢?
我们可以将连接线添加到主评论上,而弯曲的元素则用于表示回复。
接下来,如果我们有另一个回复针对第一个回复呢?以下是一个图示,展示了连接线是如何运作的:
在CSS中,我们需要使用伪元素来实现连接线的效果。在开始编写CSS代码之前,我想强调一下,这条线或弯曲部分将根据整行来定位。
这是我们要解决的第一个挑战。如果主评论有回复,我们需要为其添加连接线。我们可以使用CSS的 :has 伪类来检查一个 <li> 元素是否包含一个 <ul>,如果是,则应用所需的CSS样式。
请参考以下HTML代码:
<ul style="--depth: 0;">
<li style="--nested: true;">
<Comment />
<ul style="--depth: 1;">
<li>
<Comment />
</li>
</ul>
</li>
<li>
<Comment />
</li>
</ul>
我们需要为每个 <Comment /> 元素应用以下条件的样式:
下面是如何将上述条件翻译为CSS代码。CSS变量 + 样式查询 + :has 伪类=一个强大的条件样式。
@container style(--depth: 0) or style(--depth: 1) {
li:has(ul) > .comment {
position: relative;
&:before {
content: "";
position: absolute;
left: calc(var(--size) / 2);
top: 2rem;
bottom: 0;
width: 2px;
background: #222;
}
}
}
上面的例子展示了为什么我更喜欢使用样式查询而不是HTML数据属性来处理评论和回复的深度。
接下来,我们需要为深度为1的回复添加连接线和弯曲元素。这次,我们将使用 <li> 元素的 :before 和 :after 伪元素。
@container style(--depth: 1) {
li:not(:last-child) {
position: relative;
&:before {
/* Line */
}
}
li {
position: relative;
&:after {
/* Curved element */
}
}
}
最后,我们需要为深度为2的每个 <li> 添加弯曲元素,同时在深度为2的所有 <li> 中除了最后一个之外,都需要添加连接线。我们需要按照以下逻辑进行操作:
弯曲元素是一个带有边框和左下角半径的矩形。让我来解释一下:
@container style(--depth: 2) {
li {
position: relative;
&:after {
content: "";
position: absolute;
inset-inline-start: 15px;
top: -2px;
height: 20px;
width: 28px;
border-inline-start: 2px solid #000;
border-bottom: 2px solid #000;
border-end-start-radius: 10px;
}
}
li:not(:last-child) {
&:before {
/* Line */
}
}
}
请注意,我在边框(border)和边框圆角(border-radius)方面使用了逻辑属性。这样做有助于在文档语言为RTL(从右到左)时动态翻转用户界面。我将在文章后面详细介绍这个内容。
如果出于某种原因我们需要隐藏连接线,那么通过样式查询(style queries)来实现这一点就像切换CSS变量的开关一样简单。
通过将所有与深度相关的样式查询嵌套在 --lines: true 的样式查询内部,我们可以确保只有在设置了该 CSS 变量时才会显示连接线。
@container style(--lines: true) {
@container style(--depth: 0) {
}
@container style(--depth: 1) {
}
@container style(--depth: 1) {
}
@container style(--depth: 2) {
}
}
你可能会认为上面所有的内容都只是用于主要的布局和连接线。是的,没错!我甚至还没有考虑评论组件。
让我们仔细看一下评论组件:
乍一看,这似乎是使用 flexbox 的绝佳场景。我们可以通过 flexbox 将头像和评论框显示在同一行上。
<div class="comment">
<div class="user"></div>
<!-- Because an additional wrapper doesn't hurt. -->
<div>
<div class="comment__body"></div>
<div class="comment__actions">
<a href="#">Like</a>
<a href="#">Reply</a>
</div>
</div>
</div>
请注意,上面的HTML代码非常基础,它并不代表生产级别的代码,只是用来帮助解释CSS的内容。
.comment {
--size: 2rem;
display: flex;
gap: 0.5rem;
}
.avatar {
flex: 0 0 var(--size);
width: var(--size);
height: var(--size);
border-radius: 50%;
}
这是基本的布局,但实际应用中可能会更加复杂。然而,在本文中,我将仅专注于需要解释的独特和重要的内容。
接下来,我们会讨论评论主体组件的一些考虑事项。
评论组件的这部分将需要处理以下内容:
我在这篇文章中无法详细展示上述所有内容,因为可能需要写一本书来完整讲述。
我将重点介绍一些我认为适合使用现代CSS的有趣技巧。
在回复嵌套在评论中时,用户头像的大小将变小。这样做有助于在视觉上更容易区分主评论和回复。
使用样式查询是非常适合这种情况的。
.user {
flex: 0 0 var(--size);
width: var(--size);
height: var(--size);
}
.comment {
--size: 2rem;
@container style(--depth: 1) or style(--depth: 2) {
--size: 1.5rem;
}
}
评论可能包含从左到右(LTR)或从右到左(RTL)的语言。根据内容的语言,文本对齐应该有所区别。感谢 dir=auto HTML 属性,我们可以让浏览器自动处理这一点。
<div class="comment">
<div class="user"></div>
<div>
<div class="comment__body">
<p dir="auto"></p>
</div>
<div class="comment__actions"></div>
</div>
</div>
通过使用 CSS 逻辑属性,我们可以构建评论组件,使其能根据文档的方向进行自适应调整。同样的原理也适用于连接线。
当用户添加仅由表情符号组成的评论时,评论容器将会有一些变化:
这是使用CSS :has伪类的一个绝佳用例。
.comment:has(.emjois-wrapper) {
background: var(--default);
padding: var(--reset);
}
现代CSS的潜力一直让人兴奋不已。尝试用新的方式思考已经构建的组件或布局,是学习新知识的绝佳途径。我在整个过程中学到了很多新东西,并享受了整个过程。
由于文章内容篇幅有限,今天的内容就分享到这里,文章结尾,我想提醒您,文章的创作不易,如果您喜欢我的分享,请别忘了点赞和转发,让更多有需要的人看到。同时,如果您想获取更多前端技术的知识,欢迎关注我,您的支持将是我分享最大的动力。我会持续输出更多内容,敬请期待。
sweetalert2是一个漂亮的、响应式、可定制的替代JAVASCRIPT原生的弹出框插件。sweetalert2相比sweetalert更加强大,但它不是sweetalert的扩展,它是一个全新的插件,且支持三大流行前端框架React、Vue、Angular。
https://github.com/sweetalert2/sweetalert2
https://sweetalert2.github.io/
提供了很多安装方式
npm install --save sweetalert2
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@8"></script>
注意:如果想要兼容IE11,还得引入polyfill.js
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.js"></script>
// ES6 Modules or TypeScript import Swal from 'sweetalert2' // CommonJS const Swal=require('sweetalert2')
Swal.fire('基本信息弹框')
Swal.fire( '标题下有文字', '标题下的文字?', 'question' )
Swal.fire({ type: 'error', title: '标题', text: '出错啦!', footer: '<a href>为什么会出错?</a>' })
Swal.fire({ title: '<strong>HTML <u>示例</u></strong>', type: 'info', html: '你可以使用自定义的html<a href="https://wwwbaidu.com">百度一下<a>', showCloseButton: true, showCancelButton: true, focusConfirm: false, confirmButtonText: '好的', confirmButtonAriaLabel: '看起来不错', cancelButtonText: '取消', cancelButtonAriaLabel: '取消', })
Swal.fire({ position: 'top-end', type: 'success', title: '你的修改以保存', showConfirmButton: false, timer: 1500 })
Swal.fire({ title: '确定要删除么?', text: "删除后将无法撤销!", type: 'warning', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: '确定', cancelButtonText:'取消' }).then((result)=> { if (result.value) { Swal.fire( '删除成功!', '文件已被删除', 'success' ) } })
Swal.fire({ title: '标题', text: '自定义图片', imageUrl: 'https://unsplash.it/400/200', imageWidth: 400, imageHeight: 200, imageAlt: 'Custom image', animation: false })
Swal.fire({ title: '自定义宽度、边框和背景', width: 600, padding: '3em', background: '#fff url(/images/trees.png)', })
let timerInterval Swal.fire({ title: '自动关闭的弹框!', html: '我会在<strong></strong> 秒后关闭.', timer: 2000, onBeforeOpen: ()=> { Swal.showLoading() timerInterval=setInterval(()=> { Swal.getContent().querySelector('strong') .textContent=Swal.getTimerLeft() }, 100) }, onClose: ()=> { clearInterval(timerInterval) } }).then((result)=> { if ( // Read more about handling dismissals result.dismiss===Swal.DismissReason.timer ) { console.log('I was closed by the timer') } })
Swal.fire({ title: '提交用户名', input: 'text', inputAttributes: { autocapitalize: 'off' }, showCancelButton: true, confirmButtonText: '提交', cancelButtonText: '取消', showLoaderOnConfirm: true, preConfirm: (login)=> { return fetch(`//api.github.com/users/${login}`) .then(response=> { if (!response.ok) { throw new Error(response.statusText) } return response.json() }) .catch(error=> { Swal.showValidationMessage( `请求出错: ${error}` ) }) }, allowOutsideClick: ()=> !Swal.isLoading() }).then((result)=> { if (result.value) { Swal.fire({ title: `${result.value.login}'s avatar`, imageUrl: result.value.avatar_url }) } })
Swal.mixin({ input: 'text', confirmButtonText: '下一步', showCancelButton: true, cancelButtonText:'取消', progressSteps: ['1', '2', '3'] }).queue([ { title: '问题1', text: '使用modal很简单?' }, '问题2', '问题3' ]).then((result)=> { if (result.value) { Swal.fire({ title: '所有问题回答完成!', html: '你的答案是: <pre><code>' + JSON.stringify(result.value) + '</code></pre>', confirmButtonText: 'Lovely!' }) } })
这里就简单介绍这些示例,更多示例详见官方文档
https://github.com/sweetalert2/ngx-sweetalert2
https://github.com/sweetalert2/sweetalert2-react-content
https://github.com/sweetalert2/sweetalert2-webpack-demo
https://github.com/sweetalert2/sweetalert2-parcel-demo
https://github.com/avil13/vue-sweetalert2
https://github.com/realrashid/sweet-alert
sweetalert2是原本sweetalert的升级版,功能更加强大,文档更加全面,写法更加先进,是Web开发中常用的插件,当然同样优秀的还有很多,比如国产的layer.js也很好用,选择一个适合自己的就成,今天的介绍就到这里,希望能对你有所帮助,如果还有更好的推荐,欢迎到评论区留言,谢谢!
*请认真填写需求信息,我们会在24小时内与您取得联系。