載說明:原創不易,未經授權,謝絕任何形式的轉載
在我的好奇心驅使下,我想為什么不去查看一些熱門網站,并了解一下它們是如何實現評論組件的布局。起初,我認為這將是一個簡單的任務,但實際并非如此。
我花了很多時間試圖理解這是如何工作的,以及如何通過現代 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 , 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小時內與您取得聯系。