雖然CSS簡單,但CSS是一門非常有意思的語言,CSS每年都有變化,而且都有不同的博主都在不同的時間段總結一些CSS的新特性。雖然這些新特性無法立刻得到衆多瀏覽器的支持,但總是隨着時間的發展,這些特性都會得到瀏覽器的支持。哪怕未得到支持,也有一些方法讓瀏覽器支持,比如最爲出外的cssnext,就可以讓很多未來的CSS特性就立馬使用,並且不用花太多時間來考慮瀏覽器的兼容性。
接下來要介紹的五個CSS新特性是:
- CSS Display Module Level 3:
display:contents
- CSS Conditional Rules Module Level 3:
@support(...){...}
- CSS Overscroll Behavior Module Level 1:
overscroll-behavior: contain
- CSS Selectors Module Level 4:
:focus-within
,:placeholder-shown
- CSS Containment Module Level 1:
contain:paint
這些CSS特性,估計有些同學已經接觸過了,如果你未接觸過,建議你繼續跟隨着下面的步驟繼續往下閱讀。
案例:創建一個新聞提要(Newsfeed)
通過一個新聞提要爲例,分不同的步驟向大家闡述這個新聞提要是怎麼製作的,以及在製作這個案例的時候,這五個CSS特性是如何在案例中得到運用。
Step1:新聞提要的HTML模板
我們這個案例其實很簡單,並未使用任何JavaScript框架,還是使用原始的HTML結構來做這個Demo。所以我們需要一些簡單的HTML的標籤,幫助我們創建Demo。這裏使用了一個類名爲.container
的div
,該div
包含了一個類名爲.feed
的ul
,然後創建了十個li
,每個li
包含了一個類名爲.card
的div
。
在第五個和第六個li
之間創建了另一個名爲nested
的li
,其包含了一個無序列表ul
,而且包含了三個li
創建三個卡片。
<div class="container">
<ul class="feed">
<li><div class="card">Card 1</div></li>
<li><div class="card">Card 2</div></li>
<li><div class="card">Card 3</div></li>
<li><div class="card">Card 4</div></li>
<li><div class="card">Card 5</div></li>
<li class="nested">
<ul>
<li><div class="card">Card A</div></li>
<li><div class="card">Card B</div></li>
<li><div class="card">Card C</div></li>
</ul>
</li>
<li><div class="card">Card 6</div></li>
<li><div class="card">Card 7</div></li>
<li><div class="card">Card 8</div></li>
<li><div class="card">Card 9</div></li>
<li><div class="card">Card 10</div></li>
</ul>
</div>
在沒有任何樣式的情況之下,你看到的效果是這樣的:
<iframe id="MBvPvX" src="https://codepen.io/airen/embed/MBvPvX?height=400&theme-id=0&slug-hash=MBvPvX&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
Step2:添加樣式
現在要給示例添加一些基本樣式,使其看起來更像一個新聞提要:
body {
background-color: grey;
}
.container {
max-width: 800px;
margin: 0 auto;
}
.card {
background-color: #fff;
padding: 10px;
margin: 10px;
min-height: 300px;
}
最後,在.feed
上使用Flexbox相關的特性,讓每行有兩張卡片:
.feed {
display: flex;
flex-wrap: wrap;
li {
flex: 1 0 50%;
}
}
效果如下:
<iframe id="mjMzGO" src="https://codepen.io/airen/embed/mjMzGO?height=400&theme-id=0&slug-hash=mjMzGO&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
如果你從未接觸過Flexbox相關的知識,強烈建議你花點時間閱讀這些文章。因爲Flexbox發展到今天,已經開始取代
float
來佈局,成爲最主流的佈局方式之一,特別是在移動端上的佈局。
Step03:解決佈局問題
當你向下滾動列表時,你會發現.nested
下的三個li
(對應的是CardA ~ CardC
)影響了整體的佈局效果:
其實我們想要的,或者說理想狀態下,所有的卡片按流的方式排列,但事實並未如此。造成這種現象的原因是Flex容器 —— ul.feed
設置了display:flex
(創建了一個Flex容器),創建Flex容器之後,只會對其子元素(ul.feed > li.card
)有影響,即可子元素自動會變成Flex項目。但不會影響其後代子元素,換句話說,.nested > li
是無法自動變成Flex項目。
通常解決這個問題的唯一方法是更改HTML模板,但有些情況之下,比如說在CMS系統中(假設你沒有修改HTML標籤的權利),那麼面對這種情況,你就會束手無策了。當然,你也許會想到使用JavaScript來處理。或許以前你會這麼想,但時至今日,咱們可以通過新的CSS特性來解決這個問題 —— display:contents
。
“The element itself does not generate any boxes, but its children and pseudo-elements still generate boxes as normal. For the purposes of box generation and layout, the element must be treated as if it had been replaced with its children and pseudo-elements in the document tree.“
大至意思是:“元素本身不產生任何邊界框,而元素的子元素與僞元素仍然生成邊界框,元素文字照常顯示。爲了同時照顧邊界框與佈局,處理這個元素時,要想象這個元素不在元素樹型結構裏,而只有內容留下。這包括元素在原文檔中的子元素與僞元素,比如::before
和::after
這兩個僞元素,如平常一樣,前者仍然在元素子元素之前生成,後者在之後生成。”
那麼display: contents
這一簡單的代碼實際上讓元素表現得好像不存在一樣。但仍然可以看到元素的後代,而且元素自身並不影響佈局。也就是說,.nested
的子元素.card
也將變成Flex項目。
首先刪除現有.feed li
的類名,然後在ul
和li
是使用display: contents
:
.feed ul,
.feed li {
display: contents;
}
這個時候.feed
下所有的.card
都變成了Flex項目(不僅是.feed
下的子元素li
,還包括後代的li
元素):
現在你看到的所有卡片都是有序的排列,但是尺寸不對:
可以通過在.card
上添加flex
屬性來解決這個問題:
.card {
flex: 1 0 40%;
}
這個時候每張卡片的尺寸就又恢復正常了:
<iframe id="KBvbZg" src="https://codepen.io/airen/embed/KBvbZg?height=400&theme-id=0&slug-hash=KBvbZg&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
這個時候就好象ul
不存在了一樣。如果你夠仔細的話,你可以發現flex-basis
的值設置爲40%
了,雖然我們設置了所有元素的box-sizing
的值爲border-box
,但大家都知道,box-sizing
可以影響盒模型的計算,但對margin
不包括在內,所以爲了有足夠的空間放置卡片,把flex-basis
的值重新計算了,也就是大家所看到的40%
。
這個示例也再次向大家說明了display:contents
的神奇之處。當然,這裏並沒有對display:contents
做詳細的介紹,但也足夠向大家展示其強大之處。如果你對該特性感興趣,或者想深入的學習,建議閱讀下面這幾篇文章:
- 如何理解CSS的
display
屬性 - CSS的
display:contents
- 爲什麼是
display:contents
而不是CSS Grid的subgrid
- How
display: contents;
Works - Vanishing boxes with display contents
- More accessible markup with
display: contents
Step04:探索CSS查詢特性
儘管display:contents
實現了我們想要的效果,但它仍然處於W3C的工作草案狀態。目前只在Chrome 65+、Firefox 59+ 中看到效果。
如果你在瀏覽器開發者工具中,禁掉display: contents
,你可以看到你的佈局又開始混亂了。這樣做只是模擬瀏覽器不支持該屬性時的效果。那麼我們接下來能做什麼呢?這就引出了下一個CSS新特性 —— CSS查詢特性。
它的原理有點類似於CSS中的媒體查詢(@media
)一樣,但是它允許你單獨使用CSS表達式,類似於JavaScript語言中的if / else
之類。如果條件符合應用對應塊中的樣式。接下來讓我們把display:contents
作爲查詢特性的條件,然後將對應的CSS樣式放置在{...}
塊中。就像下面這樣:
@supports (display: contents) {
.feed ul,
.feed li {
display: contents;
}
.card {
flex: 1 0 40%;
}
}
<iframe id="QBMYRx" src="https://codepen.io/airen/embed/QBMYRx?height=400&theme-id=0&slug-hash=QBMYRx&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
在CSS中,查詢特性很多時候也被稱爲CSS的條件特性,其主要包括
@media
、@supports
和@viewport
。有關於這方面的介紹可以閱讀@webinista寫的PPT —— 《Conditional CSS》。
可能你第一次接觸到@supports()
的話會感到很好奇,並不知道該屬性的具體使用,如果你願意的話,建議你花點時間閱讀早期整理過的文章《CSS3條件判斷:@supports
》和《說說CSS中的@supports
》。
Step05: 使用not
關鍵讓代碼變得更清晰
在CSS的世界中,像@supports
這樣其實也就是一種漸進增強和優雅降級的方案。我們可以使用@supports
來添加新的樣式,但也可以添加降級所需的一些原始樣式。
如果忽略IE瀏覽器的話,@supports
已得到很好的支持。實際上你可能希望使用的是CSS查詢特性,而不是某一種操作符。它的工作方式和你預期的一樣,因此我們可以通過@supports
的not
關鍵詞對那些不支持display: contents
瀏覽器添加對應的樣式。基於這個原因,我們可以把示例的代碼修改成:
// 支持 display: contents的瀏覽器,採用的是這段代碼
@supports (display: contents) {
.feed ul,
.feed li {
display: contents;
}
.card {
flex: 1 0 40%;
}
}
// 不支持display:contents的瀏覽器,採用下面這段代碼
@supports not (display: contents) {
.feed li {
flex: 1 0 50%;
}
.feed li.nested {
flex-basis: 100%;
}
.feed li.nested ul {
display: flex;
flex-wrap: wrap;
}
}
<iframe id="XBaGZm" src="https://codepen.io/airen/embed/XBaGZm?height=400&theme-id=0&slug-hash=XBaGZm&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
在支持display:contens
的瀏覽器,你將看到的效果如下:
在不支持display:contents
的瀏覽器,看到的效果又像下面這樣:
Step06: 更進一步優化
經過上面的示例,估計你已經體會到了CSS查詢特性的魅力與潛力了,上面用到的僅僅是查查詢特性中的部分功能,更強大的是你可以and
、or
和not
結合起來,讓你的條件表達式更爲強大。比如說,你的降級方案除了考慮display:contents
之外,還會說有可能用戶的瀏覽器對display:flex
也不支持。在這樣的情況之下,咱們可以繼續降級到float
的佈局。
不過我們在這裏不會考慮降級到float
的佈局。但我們可以對display: flex
和display:contents
進行降級處理。這裏會用到@supports
中的and
和not
關鍵詞。上面的代碼就變成像下面這樣:
@supports (display: flex) and (display: contents) {
.feed ul,
.feed li {
display: contents;
}
.card {
flex: 1 0 40%;
}
}
@supports (display: flex) and (not (display: contents)) {
.feed li {
flex: 1 0 50%;
}
.feed li.nested {
flex-basis: 100%;
}
.feed li.nested ul {
display: flex;
flex-wrap: wrap;
}
}
<iframe id="JByVbM" src="https://codepen.io/airen/embed/JByVbM?height=400&theme-id=0&slug-hash=JByVbM&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
甚至你還可以在@supports
中使用CSS的自定義屬性,比如像下面這樣:
@supports (--foo: green) {
...
}
如果你對@supports
或CSS查詢特性相關的知識點還不足夠滿足的話,建議你閱讀下面的文章,深入的學習這方面的知識:
- CSS3條件判斷:
@supports
- 說說CSS中的
@supports
- Conditional CSS
- 在 CSS 中使用特徵查詢
- Conditional CSS using CSS feature queries
- Using Feature Queries in CSS
- How to use CSS Feature Queries
- Basic grid layout with fallbacks using feature queries
- Layout Design with CSS Grid & Feature Queries
- Feature Queries for CSS Grid fallbacks
案例:聊天框
現在我們有了一個漂亮的新聞提要(Newsfeed),接下來在前面的Newsfeed基礎上添加一個小的聊天框,這個聊天框固定在屏幕的右下角。
Step7: 添加聊天框
我們需要一個消息列表和一個文本域字段,方便用戶輸入消息。那麼在<body>
標籤的後面添加這個聊天框所需要的HTML標籤:
<div class="chat">
<div class="messages">
<ul>
<li><div class="message">Message 1</div></li>
<li><div class="message">Message 2</div></li>
<li><div class="message">Message 3</div></li>
<li><div class="message">Message 4</div></li>
<li><div class="message">Message 5</div></li>
<li><div class="message">Message 6</div></li>
<li><div class="message">Message 7</div></li>
<li><div class="message">Message 8</div></li>
<li><div class="message">Message 9</div></li>
<li><div class="message">Message 10</div></li>
</ul>
</div>
<input type="text" class="input">
</div>
在沒有給聊天添加任何樣式的情況下,我們看到的效果是:
<iframe id="pZrBKR" src="https://codepen.io/airen/embed/pZrBKR?height=400&theme-id=0&slug-hash=pZrBKR&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
Step08:給聊天框添加樣式
先給聊天框添加一些基本樣式,讓它看起來有點像聊天框的樣子:
.chat {
background: #fff;
border: 10px solid #000;
bottom: 0;
font-size: 10px;
position: fixed;
right: 0;
width: 300px;
}
.messages {
border-bottom: 5px solid #000;
overflow: auto;
padding: 10px;
max-height: 300px;
}
.message {
background: #000;
border-radius: 5px;
color: #fff;
margin: 0 20% 10px 0;
padding: 10px;
}
.messages li:last-child .message {
margin-bottom: 0;
}
.input {
border: none;
display: block;
padding: 10px;
width: 100%;
}
效果看起來像下面這樣:
<iframe id="yqorwX" src="https://codepen.io/airen/embed/yqorwX?height=400&theme-id=0&slug-hash=yqorwX&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
Step09:滾動鏈接
現在頁面上可以看到已經美化好的聊天框了,這個聊天框有一個可滾動的消息列表和一個文本輸入框,而且位於前面創建子的Newsfeed上面(如果沒有的話,你可以把你的瀏覽器縮小),如下:
看上去是不是不錯。但是你有沒有注意到,當你滾動聊天框中的信息列表到底部的時候,會發生什麼?感興趣的話,親自試一試。咱們做兩個小測試,先滾動頁面body
,看看效果:
然後再聊天框的信息列表中滾動,一直滾動到最底端,滾不動爲止,看看效果以是:
滾動Newsfeed,和我們想象的並沒有差異;但滾動聊天框中的消息列表時,卻不一樣,滾動到消息列表末端時,可以看到頁面body
將開始滾動。這種效果被稱爲滾動鏈接,即Scroll Chaining。
在我們這個示例中,這可能不是什麼大問題,但在某些情況下,它可能就是一大問題了。比如Modal彈框,那就很有必要解決這樣現象。
比較拙的解決方案就是給body
添加overflow:hidden
,但這有可能會影響我們的操作,甚至影響你瀏覽你的頁面。但值得慶幸的是,CSS有一個新特性可以做得更爲完美,體驗更佳,而且使用起來並不複雜,只需要一行代碼即可,那就是CSS的overscroll-behavior
,這個屬性有三個可取值:
auto
:其默認值。元素(容器)的滾動會傳播給其祖先元素。有點類似JavaScript中的冒泡行爲一樣contain
:阻止滾動鏈接。滾動行爲不會傳播給其祖先元素,但會影響節點內的局部顯示。例如,Android上的光輝效果或iOS上的回彈效果。當用戶觸摸滾動邊界時會通知用戶。注意,overscroll-behavior:contain
在html
元素上使用,可以阻止導航滾動操作none
:和contain
一樣,但它也可以防止節點本身的滾動效果
overscroll-behavior
屬性是overscroll-behavior-x
和overscroll-behavior-y
的簡寫,如果你只想控制其中一個方向的滾動行爲,可以使用其中的某一個屬性。
回到我們的示例來,在.messages
類中添加下面這行代碼:
.messages {
overscroll-behavior-y: contain;
}
<iframe id="xJLNWo" src="https://codepen.io/airen/embed/xJLNWo?height=400&theme-id=0&slug-hash=xJLNWo&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
現在你再嘗試一下,在聊天框中的消息列表中上下滾動。此時你再滾動到消息列表末端時,它不再會影響body
的滾動了(頁面的滾動):
如果你想在PWA中實現下拉刷新的效果,比如下拉時刷新Newsfeed,那麼這個屬性就非常方便。只需要在body
或html
元素中添加overscroll-behavior:contain
即可。
值得注意的是,這個屬性還不是W3C標準,而是Web孵化器WICG的一個建議。不過,說不定哪一天,這個特性就進入到W3C工作組中,成爲W3C的一個標準。
有關於這方面的更多介紹,建議閱讀下面幾篇文章:
- 滾動的特性
- CSS
overscroll-behavior
- Take control of your scroll: customizing pull-to-refresh and overflow effects
Step10:摺疊聊天框
目前,聊天框佔據了相當大的空間,如果我們不與其交互的話,會有點分散用戶的注意力。辛運的是,我們可以用CSS的選擇器特性來解決這個問題。這也是CSS的另一新特性,再一次向大家展示了CSS的魔力。
首先調整一下現有的樣式。默認情況下,我們希望聊天框是處理一個摺疊狀態,因此把.message
的max-height
值重置一下,在此設置爲0
,並且把padding
也重置爲0
。因爲這個值剛剛好摺疊了聊天框,而且又不影響其美觀。爲了讓聊天框摺疊和展開時有一個過渡的動畫效果,藉助CSS的transition
屬性來實現。
.messages {
...
max-height: 0;
padding: 0;
transition: max-height 500ms;
}
效果看起來還不錯,如下所示:
<iframe id="Owjeyz" src="https://codepen.io/airen/embed/Owjeyz?height=400&theme-id=0&slug-hash=Owjeyz&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
Step11:當聊天框得到焦點時,展開聊天框
現在我們的聊天框中的信息列表是看不到。因爲我們前面把信息列表摺疊起來了。現在我們要思考的是如何通過CSS來將其展開。這就會用到CSS的另一新特性 —— :focus-within
。
有點類似於:focus
僞類選擇器一樣,但是:focus-within
與其不同之處是,如果元素的任何後代元素得到焦點,它就會被匹配。這就是這個屬性特別之處,因爲它與CSS通常的工作方式相反,通常我們只能根據元素的祖先來選擇元素。
在我們這個示例中,當.chat
區域內的任何內容得到焦點時,重置一下.message
的max-height
和padding
值。請記住,一個元素必須接受鍵盤或鼠標事件或其他形式的輸入,以便接收焦點。比如我們這個示例,點擊<input>
輸入框就符合這個要求,可以達到我們想要的預期效果。
.chat:focus-within .messages {
max-height: 300px;
padding: 10px;
}
<iframe id="xJLoXO" src="https://codepen.io/airen/embed/xJLoXO?height=400&theme-id=0&slug-hash=xJLoXO&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
你現在可以嘗試一下效果。點擊input
讓其得到焦點,可以看到聊天框可以展開,反之聊天框又會摺疊起來:
Step12:進一步突顯:focus-within
的魔力
如果僅僅實現聊天框的摺疊和展開效果,到上一步其實已經完成了。但對於一位有追求的前端,總是在嘗試很多極限性。回到我們的示例中來,如果PM跟你提了一個新需求,當文本輸入框得到焦點之後,除了能展開聊天框之外,還希望聊天框底下的Newsfeed變得模糊。對於這樣的一個效果,怎麼來實現呢?
要實現這樣的效果,其實並不複雜,如果你有做過自定義單選按鈕或複選框(當然是純CSS),你應該會想到解決方案。我們可以使用CSS選擇器中的兄弟組合器~
,就可以很容易的做到這一點。使用~
選擇器有一個前提需要注意,聊天框.chat
需要在Newsfeed(.container
)前面(指的是HTML結構,事實上我們已經這樣做了)。只有這樣才能通過下面的方式讓Newsfeed變得模糊:
.chat:focus-within ~ .container {
filter: blur(5px)
}
<iframe id="RBLojW" src="https://codepen.io/airen/embed/RBLojW?height=400&theme-id=0&slug-hash=RBLojW&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
當然,這可能不是最佳的一個方案,但僅通過CSS的技術手段就能達到預期的效果,已經很酷了。感興趣的話,自己可以體驗一下:
注意,Newfeed添加了
filter
效果,這將會改變元素的層疊順序,造成聊天框在Newsfeed下面。所以需要顯式的.chat
中添加z-index
的值。比如這裏設置了z-index: 1001
。具體原因可以查閱@張鑫旭老師的《深入理解CSS中的層疊上下文和層疊順序》一文。
探索:placeholder-shown
首先要先分清楚,:placeholder-shown
和::placeholder
是不同的兩個東東。神奇的是:placholder-shown
是W3C標準規範的一個屬性,而::placeholder
卻不是。::placeholder-shown
仍然會影響佔位符文本的樣式。
注意:
:placeholder-shown
是一個僞類選擇器(它是一個處於特定狀態的元素);::placeholder
是一個僞元素(一個在DOM中並不存在的可見元素)。
另外,:placeholder-shown
也是新的選擇器之一(CSS Selectors Module Level 4新增了很多種僞類選擇器),它可以匹配任何顯示佔位符文本的輸入。在我們的示例中,文本輸入框(input
)並沒有任何佔位符文本,所以先在HTML中的input
元素中,添加placeholder
,新增佔位符文本。
<input type="text" class="input" placeholder="Enter your message">
然後在input
之後添加一個新的元素,用來幫助用戶操作:
<div class="prompt">Press enter to send</div>
現在給這個幫助信息.prompt
添加一些樣式,默認情況之它是被摺疊起來了。
.prompt {
line-height: 2em;
max-height: 0;
overflow: hidden;
padding: 0 10px;
text-align: right;
transition: max-height 500ms;
}
<iframe id="ZjXKjM" src="https://codepen.io/airen/embed/ZjXKjM?height=400&theme-id=0&slug-hash=ZjXKjM&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
僅從外觀上看,似乎好像沒多出什麼,就是在文本框中多了一個佔位符文本:
雖然沒多大區別,但這爲後續的效果已埋下了一個伏筆。接着往下看。
Step14:使用提示信息可見
此時提示信息處於摺疊狀態,並不可見,我想大家也想到了,要怎麼使用:placeholder-shown
讓其可見?大多數瀏覽器會顯示佔位符文本,直到用戶在input
中輸入真的值。爲了提高用戶使用表單的體驗,如果input
得到對應的焦點之後,佔位符文本並不隱藏,還起着提示作用,是不是更有意思,也對用戶有更好的幫助,畢竟我們不希望用戶發送空的消息,所以我們可以將這種行爲關聯起來,只有在用戶輸入值時才顯示提示信息(也就是.prompt
展開可見)。
:placeholder-shown
表示的是佔位文本符可見的狀態,而提示信息可見的時候,佔位文本符不可見,也就是input
有了一個真正的值。換句話來說,我們需要有一個:placeholder-shown
的反轉(佔位文本符不可見),這個時候我們可以藉助:not()
選擇器來幫我們做這樣的反轉。
.input:not(:placeholder-shown) + .prompt {
max-height: 2em;
}
將max-height
設置爲font-size:10px
的兩倍,這裏使用了2em
,這個時候可以展開提信信息塊。簡單而有整潔。如果這個看似平凡的僞類選擇器能過通過最終的規範,那麼我們將會看到一些巧妙的運用。來到這一步,效果變成:
<iframe id="jpGwmQ" src="https://codepen.io/airen/embed/jpGwmQ?height=400&theme-id=0&slug-hash=jpGwmQ&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
親自體驗一下,你在input
隨便輸入一點內容,哪怕是空格,也能看到提示信息被展示出來了:
不管:focus-within
還是:placeholder-shown
,它們都是CSS選擇器新增加的僞類選擇器,如果感興趣,建議你花些時間對這些方面進行了一瞭解:
- Focusing on Focus Styles
- A CSS Approach to Trap Focus Inside of an Element
- CSS
:focus-within
- CSS
:focus-within
via @w3cplus :placeholder-shown
- Visually validate an input field using CSS
- Intriguing CSS Level 4 Selectors
- The Future Generation of CSS Selectors: Level 4
- 下一代選擇器:CSS4
- 即將推出的CSS4 Level 4 Selectors
- CSS Level 4 Selectors to Watch Out For
Step15:讓它充滿生機
到目前爲止,我們通過簡單的HTML和一些CSS特性完成了一個帶有聊天功能的新聞提要的基本架構,但是目前它是沒有生命的,只是一個純靜態的東西。也就是說用戶並不有用它做任何事情。這個案例包含了一些有趣的CSS新特性,但到現在爲止不能修改DOM。如果想讓這個案例更爲生動,那麼就需要藉助一些JavaScript功能,以便用戶能通過聊天框添加消息。
首先,需要向<input>
和類名.messages
的子元素ul
添加一個ID
,以便JavaScript更好的獲取到對應的元素。同時給input
元素添加一個required
屬性,當用戶未輸入任何信息的時候,表單可以自動較驗。
<ul id="messages" ...
<input type="text" id="input" required ...
然後創建一個名爲script.js
文件,並且放置在</body>
之前。不過我們的案例是在Codepen上做相應的演示,所以無需考慮創建一個單的.js
文件。
Step16:添加一些JavaScript
我們需要給<input>
添加一個事件函數,當監聽到鍵盤的Enter
事件,獲取到input
的值(如果有效)並將其添加到消息列表的末尾,清除字段並滾動到消息的底部。
// 獲取相應的元素
const input = document.getElementById('input');
const messages = document.getElementById('messages');
// 監聽input的鍵盤事件
input.addEventListener('keypress', (event) => {
// 檢查是否按下Enter鍵
if (event.keyCode === 13) {
// 檢查字段是否有效
if (input.validity.valid) {
// 使用該值創建DOM元素
const message = createMessage(input.value);
// 將新創建的DOM元素添加到消息列表
messages.appendChild(message);
// 清除輸入框的值
input.value = '';
// 滾動到消息列表的底部
messages.parentNode.scrollTop = messages.parentNode.scrollHeight;
}
}
});
// 將input的值轉換爲HTML的字符串
function createMessage (value) {
return stringToDom(`<li><div class="message">${value}</div></li>`)
}
// 將字符串轉換爲真實的DOM
function stringToDom (string) {
const template = document.createElement('template');
template.innerHTML = string.trim();
return template.content.firstChild;
}
<iframe id="VBMBWP" src="https://codepen.io/airen/embed/VBMBWP?height=400&theme-id=0&slug-hash=VBMBWP&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
現在,當你在input
中輸入字段並按Enter
鍵時,你將看到你輸入的消息添加到消息列表的底部。
Step17:添加一引起額外的信息
爲了向大家演示最後一個CSS新特性 —— contain,咱們需要做一些設計。我們將實現一個效果,在消息列表頂部的框中發送新消息的時間。當你將鼠標懸停在消息上時,就會有這個效果。
首先,我們需要將這些信息添加到我們的新消息中。我們可以修改createMessage
函數返回的值。
function createMessage (value) {
return stringToDom(`
<li>
<div class="message message--mine" data-timestamp="${new Date().toString()}"> ${value} </div>
</li>
`);
}
你已經注意到了,在message
中新增加了一個類message--mine
。並給這個類添加相應的樣式:
.message--mine {
background: #ff2089;
margin-left: 20%;
margin-right: 0;
}
<iframe id="OwxogG" src="https://codepen.io/airen/embed/OwxogG?height=400&theme-id=0&slug-hash=OwxogG&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
當你新輸入內容,按下Enter
鍵時,新增加的消息列表對應的結構就變成像下圖這樣的:
顯示時間戳
我們的目的是將創建消息的時間戳顯示在消息列表的頂部,我們需要這樣做,以便即使在滾動消息列表時,這個時間戳也總是可見的。這裏我們藉助CSS的僞元素來做:
.message--mine::after {
content: attr(data-timestamp);
}
這個時候你所看到的效果是這樣的:
這個效果並不是我們想要的。我們在樣式上稍做修改,鼠標懸浮到新添加的消息列表上時才能看到時間戳,而且這個時間戳固定在消息區域的頂部。
.message--mine:hover::after {
background: #000;
color: #ff2089;
content: attr(data-timestamp);
left: 0;
padding: 5px;
position: fixed;
top: 0;
width: 100%;
}
這個效果現在變成這樣了:
現在時間戳固定在頁面的頂部,可以繼續優化一下,在.messages
中添加position: relative
。
但也不起作用,那是因爲固定定位是相對於viewport
的,而不是相對於其祖先元素。那麼這個時候,最後一個CSS新特性應該要出場了。
<iframe id="pZWOKb" src="https://codepen.io/airen/embed/pZWOKb?height=400&theme-id=0&slug-hash=pZWOKb&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
Step19:探索Containment
CSS Containment是一個令人興奮的新命題。它有許多選項,可以限制瀏覽器的樣式、佈局和對特定元素的繪製。這在修改DOM時特別有用。在瀏覽器中,哪怕是很小的變化都有可能造成瀏覽器重繪整個頁面,這樣的消費是很貴,即使瀏覽器努力爲我們做了很多優化,頁面的重繪還是對性能有一定影響的。
使用CSS Containment,我們可以把頁面的部分圈起來,然後說“這裏發生了什麼,只在這裏做相應的事”。這也是另一種方法,可以保護元素不受外部的變化而受影響。
CSS的Containment是一個新屬性,使用關鍵字contain
,它支持四個屬性值:
layout
:這個值打開該元素的佈局控制。這確保所包含元素對佈局目的完全不透明;外部不能影響其內部佈局,反之亦然paint
:這個值打開該元素的繪製控制。這確保包含元素的後代節點不顯示在其邊界外,因此,如果一個元素在屏幕外或是不可見的,它的後代節點同樣也被保證是不可見的size
:這個值打開該元素的尺寸控制。這確保包含元素可以無需檢查其後代節點進行佈局。style
:這個值打開該元素的樣式控制。這確保了,對於性能這會不僅僅作用於一個元素及其後代,這些效果也不忽視包含的元素
您還可以結合關鍵字,如contain: layout paint
,這將僅適用於一個元素的這些行爲。但也包含支持兩個額外的值:
contain: strict
意同contain: layout style paint size
contain: content
意同contain: layout style paint
每個值都有點不透明,所以我建議你閱讀規範並在開發者工具中使用它們來查看實際發生的情況。
layout
和paint
是兩個重要的值,因爲它們在需要大量操作DOM時,對性能有一定的優化。然而,在我們的演示中,我們可以利用contain: paint
我助我們進行時間戳定位。
根據規範所描述,當使用paint
時,“元素作爲一個包含絕對定位和固定定位後代的塊”。這意味着,我們可以在.chat
上設置contain: paint
,這樣一來,.chat
中元素的固定定位將基於card
而不是viewport
。你可以通過使用transform: translateZ(0)
獲得相同的效果。
<iframe id="KBXGXX" src="https://codepen.io/airen/embed/KBXGXX?height=400&theme-id=0&slug-hash=KBXGXX&default-tab=result&user=airen" scrolling="no" frameborder="0" height="400" allowtransparency="true" allowfullscreen="true" class="cp_embed_iframe undefined" style="box-sizing: inherit; width: 4860px; overflow: hidden;"></iframe>
嘗試一下下,效果完美了:
CSS Containment是較新的特性,目前只在Chrome 52+版本可以看到。