題:
橫向滾動條不顯示;el-table 只有豎向滾動條拉到最底部時,才顯示橫向滾動條;
要達到效果:
不用豎向滾動條到底部,即可實現橫向條顯示;
主要設置:
具體實現:
<template>
<div >
<el-row style="height: 300px;width: 800px;">
<el-table :data="tableData" style="width:100%" :height="400">
<el-table-column prop="date" label="日期" width="100" fixed>
</el-table-column>
<el-table-column prop="name" label="姓名" width="100">
</el-table-column>
<el-table-column prop="address" label="地址" width="100">
</el-table-column>
<el-table-column prop="date" label="日期" width="100">
</el-table-column>
<el-table-column prop="name" label="姓名" width="300">
</el-table-column>
<el-table-column prop="address" label="地址">
</el-table-column>
<el-table-column prop="date" label="日期">
</el-table-column>
<el-table-column prop="name" label="姓名">
</el-table-column>
<el-table-column prop="name" label="姓名" width="100" fixed="right">
</el-table-column>
</el-table>
</el-row>
</div>
</template>
<script>
export default {
components: {},
data () {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀區金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀區金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀區金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀區金沙江路 1516 弄'
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀區金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀區金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀區金沙江路 1516 弄'
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀區金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀區金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀區金沙江路 1516 弄'
},
]
};
},
computed: {},
watch: {},
created () {
},
mounted () {
},
methods: {
},
}
</script>
<style >
</style>
如上設置即可實現所需效果;
文:https://blog.csdn.net/TriDiamond6/article/details/105222289
這個超級炫酷的效果在官網中非常的受歡迎,這種效果可以給用戶帶來視覺沖擊,也給我們的網站帶來了活力。普通的網頁圖片會跟隨著網頁一起滑動,但是視覺差效果圖就會固定在底部,只有圖片所在的窗口上的元素會移動。
僅使用CSS
對你沒有看錯,這個效果只需要用到CSS就能輕易的實現!我們只要使用一個CSS背景圖的屬性background-attachment: fixed,這個特性會把背景相對于視口固定。即使一個元素擁有滾動機制,背景也不會隨著元素的內容滾動。
實現理論:
一、在含有圖片的元素中加入background: url()和background-size: cover(第二個屬性適用于定義圖片為封面,可以讓圖片大小自動適應,在很大的屏幕也會顯示完整的圖片)
二、然后附加固定背景圖的屬性background-attachment: fixed
三、最后給這個元素加入一個高度height: 100%或者任意的高度height: 400px
就那么簡單哦!不用懷疑,馬上上代碼,大家都可以自己去試試哦!
HTML
<div class="wrapper">
<div class="parallax-img"></img>
<p>這里填寫一堆文字就可以了,盡量多一點哦</p>
</div>
CSS
.wrapper {
height: 100wh;
}
.parallax-img {
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
height: 100%;
background-image: url("http://ppe.oss-cn-shenzhen.aliyuncs.com/collections/182/7/thumb.jpg");
}
p {
font-size: 20px;
padding: 1.5rem 3rem;
min-height: 1000px;
// 當你的文字內容不夠,也能撐出足夠的高度來看到效果,當然如果你文字足夠多,就不需要了
}
知識總結
有些童鞋可能沒有被這個震撼到或者還是覺得不夠刺激。那我們再來一個高級例子,上面的例子在滑動的時候圖片是固定死的。如果我們加上JavaScript的助力,我們可以讓窗口的圖片緩慢的跟隨這個頁面滑動,使得效果更有動力和更有沖擊感。
實現理論
首先講一下排版,因為我們需要在我們滑動頁面的時候使用JavaScript偏移圖片,所以我們需要給圖片一個CSS屬性讓我們可以讓圖片可以根據一個速度來往上或者往下移動。這個例子里面我們讓所有圖片包裹在一個div里面,class名為block。這個div給予相對定位屬性position: relative,這個時候我們就可以在里面加入圖片,然后讓圖片絕對定位position: absolute在這個div盒子里面。
但是圖片是可能很大的,我們需要把圖片不超出我們定義個盒子,所以我們的div同時也給予了overflow: hidden和一個高度height: 100%。這樣圖片超出div盒子就會被隱藏。
布局代碼如下:
<div class="block">
<img src="https://unsplash.it/1920/1920/?image=1005" data-speed="-1" class="img-parallax" />
<h2>視差速度 -1</h2>
</div><div class="block">
<img src="https://unsplash.it/1920/1920/?image=1067" data-speed="1" class="img-parallax" />
<h2>視差速度 1</h2>
</div>
html, body{
margin: 0;
padding: 0;
height: 100%;
width: 100%;
font-family: 'Amatic SC', cursive;
}
.block{
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
font-size: 16px;
}
.block h2{
position: relative;
display: block;
text-align: center;
margin: 0;
top: 50%;
transform: translateY(-50%);
font-size: 10vw;
color: white;
font-weight: 400;
}
.img-parallax {
width: 100vmax;
z-index: -1;
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%,0);
pointer-events: none
}
實現這個布局,在你滑動的時候,圖片是不會移動的。因為最后一步就是加入JavaScript的輔助,讓圖片活起來。
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js">
</script><script>
// 循環獲取每一個圖片元素
$(".img-parallax").each(function () {
var img=$(this);
var imgParent=$(this).parent();
function parallaxImg() {
var speed=img.data("speed"); /
/ 獲取每張圖片設置了的偏移速度 var imgY=imgParent.offset().top; // 獲取圖片盒子所在的Y位置 var winY=$(this).scrollTop(); // 獲取當前滑動到的位置 var winH=$(this).height(); // 獲取瀏覽器窗口高度 var parentH=imgParent.innerHeight(); // 獲取圖片盒子的內高度 // 瀏覽器窗口底部的所在位置 var winBottom=winY + winH; // 當前圖片是否進入瀏覽器窗口 if (winBottom > imgY && winY < imgY + parentH) { // 運算圖片需要開始移動的位置 var imgBottom=(winBottom - imgY) * speed; // 運算出圖片需要停止移動的位置 var imgTop=winH + parentH; // 獲取從開始移動到結束移動的%(根據開始與結束像素 + 移動速度) var imgPercent=(imgBottom / imgTop) * 100 + (50 - speed * 50); } img.css({ top: imgPercent + "%", transform: "translate(-50%, -" + imgPercent + "%)", }); } $(document).on({ scroll: function () { parallaxImg(); }, ready: function () { parallaxImg(); }, });});</script>
知識總結
6. 裁剪圖像的動畫
在有CSS3之前裁剪圖片實現也是頗有難度的?,F在我們有了兩個非常方便簡單的CSS3屬性可以實現裁剪,那就是object-fit和object-position, 這兩個屬性可以讓我們改變圖片的大小,但是不影響圖片的長寬比。
當然我們可以使用圖片處理工具或者使用JavaScript等插件來實現圖片裁剪功能。但是因為有了CSS3的屬性,我們不只可以裁剪,我們還可以用裁剪的屬性來做圖片的動態效果。
為了讓我們的例子更加簡單,我們這里使用了<input type="checkbox">復選框元素,這樣我們就可以使用:checked的偽類來觸發啟動效果。所以在例子里面我們完全不需要JavaScript的協助。
實現原理:
一、首先給予圖片一個寬高height: 1080px,width: 1920px。
二、然后用CSS選擇器,鎖定當input被選中后img標簽的樣式變化。當被選中時,給圖片設定一個新的寬高,這里我們給寬高各自500像素:width: 500px,height: 500px。
三、然后我們加上了過渡效果和時間讓圖片改變寬高時有動畫過渡效果:transition: width 2s, height 4s;。
四、最后加上object-fit: cover和object-position: left-top這兩個屬性來保持圖片的寬高比例,這樣就大功告成了!
我們來看看完成的代碼:
勾選裁剪圖片
<input type="checkbox" />
<br />
<img src="https://img-blog.csdnimg.cn/2020032122230564.png" alt="Random"/>
input {
transform: scale(1.5);
/* 只是用來放大復選框大小 */
margin: 10px 5px;
color: #fff;
}
img {
width: 1920px;
height: 1080px;
transition: 0s;
}
/* css選擇器鎖定復選框被選中時的狀態 */
input:checked + br + img {
width: 500px;
height: 500px;
object-fit: cover;
object-position: left-top;
transition: width 2s,
height 4s;
}
知識總結
object-fit — CSS 屬性指定可替換元素的內容應該如何適應到其使用的高度和寬度確定的框。
object-position — 用來切換被替換元素的內容對象在元素框內的對齊方式。
transition — 過渡可以為一個元素在不同狀態之間切換的時候定義不同的過渡效果。
如果有使用過Photoshop的同學對blend混合模式應該是非常熟悉了,我們都知道混合模式是非常強大,也是p圖時非常常用的一個功能。但是你們有沒有想象過可以在瀏覽器的CSS中直接使用呢?對我們不需要設計師給我們做圖,我們前端也可以實現混合模式了。
在CSS中我們不只可以對background背景加入混合模式,我們可以對任何一個元素的自帶背景加入混合模式,讓你可以做出很多之前沒有想過的效果和排版。
往一個元素加入混合模式,我們只需要使用到一個CSS屬性mix-blend-mode即可。
簡單實現原理:
首先我們只需要加一個h1標題標簽
<h1>混合模式:顏色減淡</h1>
然后我們給h1標簽加入mix-blend-mode中的顏色減弱模式color-dodge,但是要注意的是我們需要給body和html加入背景顏色background: white,要不你會發現這個效果會無效。因為h1我們沒有給顏色,會自動往上級繼承,并且混合模式是針對背景顏色的過濾,所以body和html需要有背景顏色才行。
h1 {
mix-blend-mode: color-dodge;
font-family: yahei;
font-size: 5rem;
text-align: center;
margin: 0;
padding: 20vh 200px;
color: #D1956C;
}
html, body {
margin: 0;
background-color: white;
}
body {
background-image: url(https://images.unsplash.com/photo-1505567745926-ba89000d255a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3302&q=80);
background-repeat: no-repeat;
background-size: cover;
min-height: 100vh;
overflow: hidden;
}
換換背景圖和h1標簽的字體顏色就可以弄出各種不同的特效了。
知識總結
CSS Grid和Flexbox讓我們可以更簡便,更容易和更快的實現各式各樣的響應布局,并且讓我們快捷方便的在布局中實現橫向劇中和豎向劇中。但是回想一下以前是頗為困難的。
雖然這些新出的布局方式可以讓我們解決很多以前的布局難題,但是像瀑布流布局這種,就無法用它們簡單來實現了。因為瀑布流一般來說都是寬度一致,但是高度是根據圖片自適應的。并且圖片的位置也是根據在上方圖片的位置而定的。
其實最好實現瀑布流布局的辦法就是用CSS的列屬性套件,這套屬性大多數都是用于排版雜志中的文本列。但是用于布局瀑布流也是特別實用哦。因為以前需要實現瀑布流,就必須有JavaScript的輔助來計算圖片高度然后決定每張圖片的定位和位置,所以現在有了列屬性就可以使用純CSS實現了。
實現原理:
實現這個布局,首選我們需要把所有的內容先包裹在一個div元素里面,然后給這個元素column-width和column-gap屬性。
然后,為了防止任何元素被分割到兩個列之間,將column-break-inside: avoid添加到各個元素中。
神奇的效果就完美實現了,零JavaScript。
我們來看看代碼:
<div class="columns">
<figure>
<img src="https://source.unsplash.com/random?city" alt="" />
</figure>
<figure>
<img src="https://source.unsplash.com/random?night" alt="" />
</figure>
<figure>
<img src="https://source.unsplash.com/random?developer" alt="" />
</figure>
<figure>
<img src="https://source.unsplash.com/random?building" alt="" />
</figure>
<figure>
<img src="https://source.unsplash.com/random?water" alt="" />
</figure>
<figure>
<img src="https://source.unsplash.com/random?coding" alt="" />
</figure>
<figure>
<img src="https://source.unsplash.com/random?stars" alt="" />
</figure>
<figure>
<img src="https://source.unsplash.com/random?forest" alt="" />
</figure>
<figure>
<img src="https://source.unsplash.com/random?girls" alt="" />
</figure>
<figure>
<img src="https://source.unsplash.com/random?working" alt="" />
</figure>
</div>
.columns {
column-width: 320px;
column-gap: 15px;
width: 90%;
max-width: 1100px;
margin: 50px auto;
}
.columns figure {
display: inline-block;
box-shadow: 0 1px 2px rgba(34, 25, 25, 0.4);
column-break-inside: avoid;
border-radius: 8px;
}
.columns figure img {
width: 100%;
height: auto;
margin-bottom: 15px;
border-radius: 8px;
}
知識總結
column-width — CSS屬性建議一個最佳列寬。列寬是在添加另一列之前列將成為最大寬度。
column-width — 該 CSS 屬性用來設置元素列之間的間隔 (gutter) 大小。
column-break-inside — 設置或檢索對象內部是否斷。
我希望這8個前端小技巧和特效對大家有幫助,或多或少有吸收一點新的前端知識。這篇文章提到的內容,其實很多都是值得深挖和學習的。有一些例子我做的比較簡單,但是其實是有無限的可能性。喜歡前端的童鞋們,讓我們繼續在前端領域中一起深挖,讓我們的熱愛無限的燃燒起來吧!
在最后我想給大家講一下我對前端的熱愛和態度。
回想前端這幾年,發展真的是突飛猛進,從前端排版,HTML5+CSS3做H5頁面,到前端組件化,各種UI框架滿天飛。
一開始我隨著熱潮用起了UI框架,起初覺得特別方便,來一個新的項目就直接上一個UI框架,研發速度也非??臁5蔷枚弥陀X得前端開發變成了處理數據,對接接口,實現交互。
某天在閱覽國外的一些前端設計和框架的時候,我突然發現國內多數的前端開發者都不再怎么使用CSS3做出一些很好玩的布局和特效了?,F在市面上的系統和頁面都是千篇一律,普遍都是用一些知名的UI框架搭建系統和APP,基本自己動手去排版已經少之又少。前端已不再是以前的前端,缺少了靈魂。
但是我們回想一下,我們剛剛開始學習前端的時候,讓我們最有成就感,覺得前端特別有意思的那種感覺。就是那種讓我們覺得神乎奇跡,不可思議的布局,特效和交互。那種感覺自己成功實現了很優美,很炫酷的頁面和特效的感覺,讓我們越做越來勁,越做越是興奮。
但是在某些公司,研發部都是要求快速開發,UI設計部門也是受到時間的控制和限制,所以逐步走進了UI框架的限制之中。都是圍繞這一些UI框架來設計和開發系統和應用。
作為一名熱愛前端的開發者,我還是堅持在絕大多數的項目中,自己排版和實現頁面交互特效。然后使用UI框架作為輔助,主要是用來減輕一些小組件和常用組建的快速實現。(可以說我是比較追求完美和外貌協會的程序員 )
————————————————
版權聲明:本文為CSDN博主「三鉆」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/TriDiamond6/article/details/105222289
歷數技術進步的代價時,弗洛伊德遵循的路線使人感到壓抑。他同意塔姆斯的評論:我們的發明只不過是手段的改進,目的卻未見改善。
——尼爾波斯曼《技術壟斷》
雖然開發工具早已經從 preprocessor 進化到了 styled component 甚至是 functional css,但在我看來新的工具并沒有讓我們的樣式代碼寫的更好,只是更快——也可能會讓代碼壞的更快。工具的繁榮并沒有讓那些導致代碼難以維護的根本問題煙消云散,而是更易讓我們對其視而不見。這篇文章旨在回答一個問題:為什么樣式代碼難以寫對,它的陷阱究竟在哪里?
如果一本正經的聊架構,套路多半是按照某些重要的特征依次展開講解。但這些所謂的重要特征其實在編程領域中是放之四海而皆準的,例如“擴展性”、“可復用”、“可維護性”等等,按這種思路聊,空談大于應用。所以我們不如通過解決某個具體的樣式問題,來審視樣式代碼應該如何編寫和組織
下圖是一個非常簡單的 popup 組件,我們會以它的樣式開發過程串起整篇的內容。
我們首先以一種簡單粗暴的方式來實現它,直覺上看,實現這個 popup 只需要三個元素即可:div 是最外面的容器,h1 用于包裹 "Success" 文案,button 用來實現按鈕
<div class="popup">
<div>Success</div>
<button>OK</button>
</div>
我不會完整的寫出它的完整樣式,只大概列出其中一些關鍵屬性
.popup {
display: flex;
justify-content: space-around;
padding: 20px;
width: 200px;
height: 200px;
div {
margin: 10px;
font-size: 24px;
}
button {
background: orange;
font-size: 16px;
margin: 10px;
}
}
第一版實現即完成了。目前看來并沒有什么不妥。
問題不在于實現而是在于維護。接下來我就以一些常見的實際需求變更來看看上面的代碼存在怎樣的問題。
假設現在需要在“Success”下方新增一個元素用于展示成功的具體信息
想當然的我們需要新增一個 div 標簽。但如果這樣的話上面樣式中的 .popup div 樣式就會同時對這兩個 div 產生同樣的效果,這并不是我們希望的,很明顯這兩個元素的樣式是不同的。OK,如果你堅持使用標簽作為選擇器的話,你可以使用偽類選擇器 nth-child 來區分樣式:
.popup {
div:nth-child(1) {
margin: 10px;
font-size: 24px;
}
div:nth-child(2) {
margin: 5px;
font-size: 16px;
}
但如果某一天你認為"Success"應該使用 h1 而非 div 封裝更為恰當的話,那么修改的成本則是:
但如果你一開始就能給 button 和 div 一個確切的 class 名稱,那么當你修改 DOM 元素時也僅僅需要修改 DOM 元素,而無需修改樣式文件了
上面舉得這個例子是水平拓展的情況,也就是說我在某一元素的同一級新增一個元素。縱向拓展也會出現同樣的問題,你可以完全想象的出類似于這樣的選擇器:
.popup div > div > h1 > span {
}
.popup {
div {
div {
span {}
}
}
}
無論是上面代碼中的哪一種情況,樣式是否生效都極度依賴于 DOM 結構。在一連串的 DOM 標簽的層級關系中,哪怕只有一個元素出現了問題(可能是元素標簽類型發生了修改,還有可能是在它之上新增了一個元素)都會導致樣式大面積失效。同時這樣的做法也會讓你復用樣式難上加難,如果你希望復用 .popup div > div > h1 > 的樣式,你不得不將 DOM 結構也拷貝到想要復用的地方。
所以這里我們至少能得出一個結論:CSS 不應該過分的依賴 HTML 結構
而之所以加上“過分”二字,是因為樣式完全無法脫離結構獨立存在,例如 .popup .title .icon 這樣的的依賴關系背后就暗示了 HTML 結構的大致輪廓。
所以我們可以繼續將上面的原則稍作更正:CSS 應該擁有對 HTML 的最小知識。理想情況下一個 .button 樣式無論應用在任何元素上看上去都應該像同一個立體的可點擊按鈕。
上一節中我們開發完畢的組件通常會在頁面上被多處引用,但總存在個別場景需要你對組件稍作修改才得以適配。假設有一個需求是希望把這個 popup 應用在他們的移動端網站上,但為了適配動設備,某些元素的有關尺寸例如長寬內外邊距等都要縮小,你會怎么實現?
我見過的 90% 的解決方案都是以添加父元素的依賴進行實現,也就是判斷該組件是否在某個特定的 class 下,如果是的話則修改樣式:
body.mobile {
.popup {
padding: 10px;
width: 100px;
height: 100px;
}
}
但如果此時你需要給平板設備添加一個新的樣式,我猜你可能會再添加一個 body.tablet { .popup {} } 代碼。又如果移動端網站有兩處需要使用 popup ,那么你的代碼很最終會變成這樣:
body.mobile {
.sidebar {
.popup
}
.content {
.popup
}
}
這樣的代碼依然是難以復用的。如果某位開發者看到了移動端網站 popup 打開的樣式很喜歡,然后想移植到另一處,那么單純引入 popup 組件是不夠的,他還需要找到真正的生效的代碼,將樣式和 DOM 層級都復制粘貼過去。
在一個組件自身已經擁有樣式的情況下,過分的依賴父組件間接的調整樣式,是一種 case by case 的編碼行為,本質上這架空了 popup 自帶樣式。假設 popup 自帶 box-shadow 的樣式屬性,但在有的用例里,box-shadow 可能會被加重,而在有的用例里,box-shadow 又可能會消失,那么它自帶的 box-shadow 根部本就沒有意義了,因為它永遠不會生效。
架空違背了“最小驚訝原則”,給后續的維護者帶來了“驚喜”。如果此時 popup 的設計稿發生了修改,陰影需要減少,則修改它自身的樣式是不會生效的,或者說無法在每一處生效。而至于還有哪些地方無法生效,為什么它們無法生效,維護者并不知道,他同樣需要 case by case 的去查看代碼。這么做無疑增加了修改代碼的成本.
解決這個問題并不像解決 DOM 依賴問題那么簡單,需要我們多管齊下。
想提高代碼的可維護性,分離關注點永遠是屢試不爽的手段??v觀現有的各類組織樣式的方法論,比如 SMASS 或者是 ITCSS,對樣式進行適當的角色劃分是它們的核心思想之一。
我們以一個完整的 popup 樣式為例:
.popup {
width: 100px;
height: 30px;
background: blue;
color: white;
border: 1px solid gary;
display: flex;
justify-content: center;
}
在這一組樣式中,我們看到
根據這些特點和常見的規范,可以考慮從下面幾個維度對樣式進行分離:
從表面上看,這種行為只是將樣式(尺寸)從一個組件轉移到另一個組件(容器)上,但卻從根本上解決了我們上面提到的父元素依賴的困惱。任何想使用 popup 的其他組件,不用再設法關心 popup 組件的尺寸是如何實現的,它只需要關自己。
進一步從深層次上說,它消滅了依賴。你可能沒有注意到,flex 布局的樣式配置遵循的就是這種模式:當你想讓你孩子元素按照某種規則布局的話,你只需要修改父元素和 flex 布局樣式屬性即可,完全不用再在孩子元素的樣式上做出修改。
我個人認為另一個反模式的例子是 text-overflow: ellipsis 屬性,單一的該樣式屬性是不足以自動省略容器內的文字,容器還需要滿足 1) 寬度必須是 px 像素為單位 2) 元素必須擁有 overflow:hidden 和 --tt-darkmode-color: #A3A3A3;">而至于布局功能元素是與父元素為同一元素,還是獨立元素,我傾向于后者,畢竟幾個 markup 代碼并不會給我們添加多少負擔,但清晰的職責劃分卻能給我們將來的維護帶來不少便利。
在這個前提下任何給 popup 添加的布局樣式實際上都意味這你新增了隱性依賴,因為你實際上是在暗示:它在這個父容器下的這個 margin 值看上去剛好。
通常我們不會只需要單一樣式的按鈕,可能還需要帶有紅底白字的錯誤樣式的按鈕,還需要黃底白字的警告樣式按鈕。這種用例常見的解決方案不是新建 N 個不同的按鈕樣式,比如 primary-button, error-button(這樣務必會出現很多公共的 button 代碼),而是在一個 button 樣式的基礎上,通過提供樣式的“修飾”類來達到最終的目的。例如基礎款的按鈕 class 名稱為 button, 如果你想讓它變得帶有警告樣式的話,只需要同時使用 error 的 class 名稱即可。
<div className="button error"></div>
從本質上說這也是一種關注點的分離,只不過從這個角度上看它關心的是“變”與“不變”。我們將“變量”統統轉移到“修飾”類中。
但這種方案在實現時會遇到不少問題,首先是修飾類的設計,例如當我在定義例如 error, primary, warning 的修飾類時,究竟哪些樣式屬性是我可以覆蓋的哪些是不可以,這必須有事前約定。否則某人在寫 error 樣式時,可能會無腦的覆蓋原 button 上的樣式直到看上去滿意為止。它依賴于抽象能力,但糟糕的抽象比不抽象還要難以維護。
組件并非是封裝樣式的唯一單位,在一個網站中,還可能存在諸如 base、reset 這種全局或者說切面性質的樣式屬性。我理想的模塊化樣式應該能夠輕松達到以下的目的:
詮釋這兩點最好的例子是在進行響應式開發時,業內通用的對字體大小適配的解決方案。例如下面這個組件的 html 結構:
<div class="ancestor">
<div class="parent">
parent
<div class="child">
hello
</div>
</div>
</div>
在樣式中我們會設定:
.ancestor {
font-size: 1rem;
}
.parent {
font-size: 1.5em;
}
.child {
font-size: 2em;
}
這樣當我們需要根據設備調整字體大小時,只需要調整根元素 html 字體大小,那么頁面上其他元素就會自我調節了。而如果我們只想調整局部樣式時,我們只需要調整 .ancestor 的字體大小即可,不會影響到其他元素。
你閱讀到這里不難看出來,樣式難寫對的問題在于它太容易影響別的組件,也太容易受別的組件所影響了。絕大部分人遇到的問題是:
解決這個問題的辦法早就有了,那就是樣式的隔離。比如在 Angular 中,它是靠給元素添加隨機屬性并且給樣式附帶上屬性選擇器來實現的,例如你同時創建了 page-title 組件和 section-title 組件,它們都擁有 h1 元素的樣式,但是在編譯之后你看到的 css 分別是:
h1[_ngcontent-kkb-c18] {
background: yellow;
}
h1[_ngcontent-kkb-c19] {
background: blue;
}
這樣所有的 h1 元素樣式都不會被互相影響。
Pre-Processer
無論你主觀上多么想避免以上的所有問題,給樣式一個好的整潔架構。在實現的過程中,我們依然會不小心掉入工具的陷阱中。
再一次回到我們上面提到的 popup 樣式:
.popup {
width: 100px;
height: 30px;
background: blue;
color: white;
}
假如你發現 { background: blue; color: white; } 作為常見樣式出現頻繁,希望對它進行復用,在使用 Sass 編程的前提下很明顯此時你有兩個選擇:@mixin 或者 @extend。
如果采用 mixin,代碼如下:
@mixin common {
background: blue;
color: white;
}
.popup {
@include common;
}
而如果采用 extend:
.common {
background: blue;
color: white;
}
.popup {
@extend .common;
}
第一個問題是,無論你選擇哪種模式,你都很難說開發者是有意在依賴抽象還是在依賴實現。我們可以把 @mixin common 和 .common 解讀為對一種抽象的封裝,但很有可能后續的消費者只是想復用 background 和 color 而已。一旦如此,common 模塊就變得難以修改,因為對任意一個屬性的修改都會影響到未知的若干個模塊。
在 SASS 中雖然我們可以給類名添加參數,把它當作參數相互傳遞,但它與我們實際編程中的變量和函數并不相同:JavaScript 中的函數我們往往只關心它的輸入與輸出,只是定義函數并不會對程序的結果造成影響。而當你在定義樣式類的那個時刻就已經可能對頁面產生了影響,并且其中的每一條屬性都會產生影響。
如果你聽說過“組合優于繼承”,我相信會對這一點有更深刻的體驗。你可以回想繼承體系中存在的副作用,例如繼承打破了對超類的封裝,子類不能減少超類的接口等等,在 SASS 的這類復用關系中都能找到相似的影子。
extend 相比 mixin 更危險的地方在于,它破壞了我們一如既往組織模塊的方式。
例如目前已有一個 page 頁面,其中擁有一組 page-title 的樣式:
.page {
.page-title {
.icon {
width: 10px;
}
.label {
width: 100px;
}
}
}
現在 card-title 想通過 extend 來復用它:
.card-title {
@extend .page-title;
}
那么編譯之后的結果看上去會非常奇怪:
.page .page-title .icon, .page .card-title .icon {
width: 10px;
}
.page .page-title .label, .page .card-title .label {
width: 100px;
}
哪怕你沒有聽說過 BEM,你的編程經驗也應該會告訴你 page 和 card 的樣式應屬于不同的模塊。但事實上編譯后的結果更像是優先考慮復用,從橫切面強行把二者耦合在一起。
而如果你嘗試將公共的 title 樣式抽象為 mixin,再在 page-title 和 card-title 中進行復用:
@mixin title {
.icon {
width: 10px;
}
.label {
width: 100px;
}
}
.page {
.page-title {
@include title
}
}
.card-title {
@include title
}
編譯的結果如下:
.page .page-title .icon {
width: 10px;
}
.page .page-title .label {
width: 100px;
}
.card-title .icon {
width: 10px;
}
.card-title .label {
width: 100px;
}
很明顯 page 和 card 的樣式更涇渭分明。
An Necessary Evil
如果你問我我是否會遵守上面自己寫的每一條原則,我的答案是否定的。在實際開發中我傾向用便捷性換取可維護性。
在編程領域里面唯一不變的就是變化本身,無論在敲鍵盤之前你面向對象設計的多么準確,對組件拆分的多么恰當,任何業務上的變化都有可能讓你所有的設計推倒重來。所以為了保證代碼能夠精確反饋業務知識的合理性,我們需要時常對代碼設計重新設計。
你可以想象整個過程需要重新審視架構,從頭閱讀理解代碼,修改完畢后驗證。執行這一系列步驟需要不小的成本,還不包括其中的試錯,以及因為重構而浪費的添加新功能的機會。更重要的是成本擺在那里,但收益卻并不明顯。
如果你的樣式代碼是基于 design system 之上的,那么你的改動成本會更高。因為你更不可能以個人的視角隨心所欲的改動代碼了,而是要自上而下的用整個產品的設計語言來衡量修改的合理性。
另一個更實際的問題是,代碼從來不是依靠個人來維護。當這一套理論在團隊內并沒有達成共識,或者是大家只在理論層面了解過而實操時并不在意時,少數人的精心付出終究會化為泡影。代碼在理想狀態下應該最大成度上摒棄“人”這個因素成為流水線上工業化的產品。所以當我發現某個框架只有要求人們閱讀完數十頁最佳實踐有關的文檔才能寫出符合官方標準的好代碼時,那么現實工作中好代碼出現的概率基本為0——在規范輸出代碼上,一則有效的 eslint 規則比十頁文檔都要強。而在本篇中敘述的各種原則屬于后者。
然而 css 代碼被寫的亂七八糟又會怎樣呢?產品壞了是肯定的,但相比其他 bug 有意思的事情是:
基于上面的三點,同時考慮到當下技術棧繁雜學習成本高,腳本開發工作量大,交付壓力重,樣式架構的正確性想當然是被犧牲掉的那一個。
最后重申我不鼓勵這樣的行為,這只是屈服于現實壓力下其中的一種可能性而已。如果你所在的項目資源充足,以及大家有決心把事情做對,那也未嘗不可。
Functional CSS
在我看來還有一類實踐是游離于以上體系之外的,比如 tailwind 和 tachyons 。之所以將它們稱之為“函數式”樣式,是因為在這些框架不提供組件化、語義化的樣式,比如 .card, .btn,而提供的是“工具類(utility class)”,比如 .overflow-auto,.box-content,它們 類似于函數式編程中沒有副作用的純函數。當你需要給你元素添加樣式時,只需要給這個元素添加對應的 class 名稱即可:
<div class="overflow-auto box-content float-left"></div>
之所以說這種實踐游離于以上體系之外,是因為它打破了我上面所說的前提:樣式和 DOM 結構之間存在依賴關系。在這種編程模式下,因為不再存在“級聯”關系,所以每個元素的樣式都是獨立的,互不影響。
如此看來這種模式簡直就是天堂,本文里提及的所有問題都可以避免了:父元素依賴、角色耦合、預處理器里糾結的復用。
但仔細想想,這種方式是不是很 inline style 類似?用 inline style 也能解決我們所說的上述所有問題。我們是不是又回到了起點?
除了上面的問題外,我不再給出進一步推薦或者反對意見的原因在于,一方面這種實踐存在很大的爭議。另一方面我缺乏使用這類框架的經驗。這里經驗的判斷標準不是“是否用過”,而是“是否長期投入到多人協作的大型項目中”——“長期”、“多人”、“大型”這幾個關鍵詞很重要。因為我們在做技術選型的時候,更多要考慮和現有項目的契合度、團隊的適應成本,以及評估長遠來看它能給我們帶來巨大的好處是否能抵消替換它的成本。這些經驗是我缺乏的。
文/Thoughtworks 李光毅
更多精彩洞見,請關注公眾號Thoughtworks洞見
*請認真填寫需求信息,我們會在24小時內與您取得聯系。