Stylus介紹及特點
基於Node.js的css的預處理框架,其本質上做的事情與 Sass/LESS 等類似, 可以以近似腳本的方式去寫CSS代碼,創建健壯的、動態的、富有表現力的CSS,默認使用 .styl 的作為文件擴展名,支持多樣性的CSS語法。Stylus比LESS更強大,而且基於nodejs比Sass更符合我們的思路。
Stylus的特點如下
- 基於js
Node.js是一個Javascript運行環境(runtime),是對Google V8引擎進行了封裝,V8引擎執行Javascript的速度非常快,性能非常好。對於不了解Node.js的開發人員,不會增加太多學習成本。Stylus基於Node.js,換而言之,就是借助JavaScript讓CSS更富有表現力,更動態,更健壯!而且還有專門的JavaScript API。
- 支持Ruby之類框架
雖然Stylus基於Node.js,但是依然支持Ruby之類框架,還有FireBug插件FireStylus, sublimetext插件,便於開發、調試。
- 功能強大,使用靈活,支持多樣性的CSS語法
Stylus的功能比LESS強大,不遜於Sass。在用法上,支持傳統的CSS,而且相對於傳統的用法,更加簡潔、靈活,像省掉花括號、冒號,分號,甚至使用混合的CSS編程,Stylus都可以接受。
Stylus的優缺點
【優點】
解決樣式覆寫的問題,尤其是mixin式復用
使用純CSS,我們可以抽象出一些常用的布局CSS屬性組合,通過CSS的類組合來達成常見的mixin式復用,然而這種方案存在一些問題,例如:
當頁面重構時,需要頻繁修改class name,這個問題在后端人員掌握着視圖層的時候格外突出,前后端耗費很多溝通成本;
在約束上下文的時候非常無力,比如“只有在ul下面的img.db允許是display:block”的規則,寫成“ul img.db { display: block; }”就完全跑偏了,它違背了創建這個.db類時的本意,造成了代碼的可讀性和可維護性下降。
如果你要改動規則,需要同時修改HTML和CSS,也可能造成新的樣式問題。
而通過Stylus可以建立一種新的代碼風格,只允許CSS Class代表UI模塊的抽象,這樣一來,改動樣式時不至於通知后端改模板,然后在CSS Class內部實現mixin。而這正是CSS的短板,CSS體系內的用法只有復制粘貼。
可緩解多瀏覽器兼容造成的冗余
進入CSS3的時代,舊式CSS hack如filter,新式兼容前綴如-webkit-等,都是冗余,修改的時候也需要修改多處,不容易維護。在Stylus里面,寫個函數就能解決,多次復用也不需要看到如此之多的hack。
提高效率,節約成本
用Stylus開發CSS可以提高效率,它類似於一種CSS的方言,可以用更精簡的語法表達更多的意思。比如,Stylus中可以使用變量,比如和 UED 同學訂好各種樣式的規范,做好變量后開發中直接使用,避免頁面中的各種雜亂樣式。當樣式需求有變動時,也可以重新給變量賦值,一下改掉相關樣式,不用再一點一點的改。
使CSS開發更加靈活
Stylus可以使用變量、條件、循環,兼容傳統的CSS樣式,等等,可以讓CSS的開發和修改更加靈活。
【缺點】
開發過程增加步驟
CSS的好處在於簡便、隨時隨地被使用和調試,使用Stylus,增加了預編譯CSS的步驟,讓我們開發工作流中多了一個環節,調試也多了個步驟。
增加學習成本。
雖然Stylus簡單易學,可以兼容傳統CSS,但是當開發和維護團隊都從CSS過渡到Stylus時,還是需要一點學習成本的,而且初學者使用起來,不一定能明顯提高效率。
語法:
1. 選擇器
Stylus就跟CSS一樣,允許你使用逗號為多個選擇器同時定義屬性。
textarea, input
border 1px solid #eee
使用新行是一樣的效果:
textarea
input
border 1px solid #eee
等同於:
textarea,
input {
border: 1px solid #eee;
}
父級引用
字符&
指向父選擇器。下面這個例子,我們兩個選擇器(textarea
和input
)在:hover
偽類選擇器上都改變了color
值
textarea input color #A7A7A7 &:hover color #000 等同於: textarea, input { color: #a7a7a7; } textarea:hover, input:hover { color: #000; }
消除歧義
類似padding - n
的表達式可能既被解釋成減法運算,也可能被釋義成一元負號屬性。為了避免這種歧義,用括號包裹表達式:
pad(n) padding (- n) body pad(5px)
編譯為:
body { padding: -5px; }
然而,只有在函數中才會這樣(因為函數同時用返回值扮演混合或回調)。
有Stylus無法處理的屬性值?unquote()
可以幫你:
filter unquote('progid:DXImageTransform.Microsoft.BasicImage(rotation=1)')
生成為:
filter progid:DXImageTransform.Microsoft.BasicImage(rotation=1)
2.變量
我們可以指定表達式為變量,然后在我們的樣式中貫穿使用:
font-size = 14px body font font-size Arial, sans-seri
變量甚至可以組成一個表達式列表:
font-size = 14px font = font-size "Lucida Grande", Arial body font font sans-serif
編譯為:font: 14px "Lucida Grande", Arial sans-serif;
標識符(變量名,函數等),也可能包括$
字符。例如:
$font-size = 14px body { font: $font-size sans-serif; }
屬性查找
Stylus有另外一個很酷的獨特功能,不需要分配值給變量就可以定義引用屬性。下面是個很好的例子,元素水平垂直居中對齊(典型的方法是使用百分比和margin負值),如下:
#logo position: absolute top: 50% left: 50% width: w = 150px height: h = 80px margin-left: -(w / 2) margin-top: -(h / 2)
我們不使用這里的變量w和h, 而是簡單地前置@字符在屬性名前來訪問該屬性名對應的值: #logo position: absolute top: 50% left: 50% width: 150px height: 80px margin-left: -(@width / 2) margin-top: -(@height / 2)
3. 插值
Stylus支持通過使用{}
字符包圍表達式來插入值,其會變成標識符的一部分。例如,-webkit-{'border' + '-radius'}
等同於-webkit-border-radius
.
比較好的例子就是私有前綴屬性擴展:
vendor(prop, args) -webkit-{prop} args -moz-{prop} args {prop} args border-radius() vendor('border-radius', arguments) box-shadow() vendor('box-shadow', arguments) button border-radius 1px 2px / 3px 4px 變身: button { -webkit-border-radius: 1px 2px / 3px 4px; -moz-border-radius: 1px 2px / 3px 4px; border-radius: 1px 2px / 3px 4px; }
選擇器插值
插值也可以在選擇器上起作用。例如,我們可以指定表格前5行的高度,如下:
table for row in 1 2 3 4 5 tr:nth-child({row}) height: 10px * row 也就是: table tr:nth-child(1) { height: 10px; } table tr:nth-child(2) { height: 20px; } table tr:nth-child(3) { height: 30px; } table tr:nth-child(4) { height: 40px; } table tr:nth-child(5) { height: 50px; }
4.運算符
運算符優先級:從最高到最低
[] ! ~ + - is defined ** * / % + - ... .. <= >= < > in == is != is not isnt is a && and || or ?: = := ?= += -= *= /= %= not if unless
一元運算符, !,not, -, +,以及~
-5px // => -5px --5px // => 5px not true // => false not not true // => true
not的優先級較低
a = 0
b = 1
!a and !b
// => false
// 解析為: (!a) and (!b)
用:
not a or b
// => false
// 解析為: not (a or b)
范圍.. ...
同時提供包含界線操作符(..
)和范圍操作符(...
),見下表達式:
1..5 // => 1 2 3 4 5 1...5 // => 1 2 3 4
加減乘除余 + - * / % :二元加乘運算其單位會轉化,或使用默認字面量值。例如,5s - 2px
結果是3s
20mm + 4in // => 121.6mm "foo " + "bar" // => "foo bar" "num " + 15 // => "num 15" 2000ms + (1s * 2) // => 4000ms 5s / 2 // => 2.5s 4 % 2 // => 0
當在屬性值內使用/
時候,你必須用括號包住。否則/
會根據其字面意思處理(支持CSS的line-height
)。
font: 14px/1.5;
但是,下面這個卻等同於14px ÷ 1.5: font: (14px/1.5); 只有/操作符的時候需要這樣。
指數:**
相等與關系運算:== != >= <= > <
相等運算符可以被用來等同單位、顏色、字符串甚至標識符。這是個強大的概念,甚至任意的標識符(例如wahoo
)可以作為原子般使用。函數可以返回yes
和no
代替true
和false
(雖然不建議)。
別名:
== is != is not != isnt
5 == 5 // => true 10 > 5 // => true #fff == #fff // => true true == false // => false wahoo == yay // => false wahoo == wahoo // => true "test" == "test" // => true true is true // => true 'hey' is not 'bye' // => true 'hey' isnt 'bye' // => true (foo bar) == (foo bar) // => true (1 2 3) == (1 2 3) // => true (1 2 3) == (1 1 3) // => false
只有精確值才匹配,例如,0 == false
和null == false
均返回false
.
真與假
Stylus近乎一切都是true
, 包括有后綴的單位,甚至0%
, 0px
等都被認作true
.
不過,0
在算術上本身是false
.
表達式(或“列表”)長度大於1被認為是真。
true
例子:
0% 0px 1px -1 -1px hey 'hey' (0 0 0) ('' '')
false:
0 null false ''
邏輯操作符:&& || 和 or
邏輯操作符&&
和||
別名是and
/ or
。它們優先級相同。
存在操作符:in
檢查左邊內容是否在右邊的表達式中。
元組同樣適用:
vals = (error 'one') (error 'two') error in vals // => false (error 'one') in vals // => true (error 'two') in vals // => true (error 'something') in vals // => false
混合書寫適用例子:
pad(types = padding, n = 5px) if padding in types padding n if margin in types margin n body pad() body pad(margin) body pad(padding margin, 10px) 對應於: body { padding: 5px; } body { margin: 5px; } body { padding: 10px; margin: 10px; }
條件賦值:?= :=
條件賦值操作符?=
(別名?:
)讓我們無需破壞舊值(如果存在)定義變量。該操作符可以擴展成三元內is defined
的二元操作。
例如,下面這些都是平起平坐的:
color := white color ?= white color = color is defined ? color : white
如果我們使用等號=, 就只是簡單地賦值。 color = white color = black color // => black
但當使用?=,第二個相當就嗝屁了(因為變量已經定義了): color = white color ?= black color // => white
實例檢查:is a
Stylus提供一個二元運算符叫做is a
, 用做類型檢查。
15 is a 'unit' // => true #fff is a 'rgba' // => true 15 is a 'rgba' // => false 另外,我們可以使用type()這個內置函數。 type(#fff) == 'rgba' // => true
注意:color
是唯一的特殊情況,當左邊是RGBA
或者HSLA
節點時,都為true
.
變量定義:is defined 用來檢查變量是否已經分配了值。
該操作符必不可少,因為一個未定義的標識符仍是真值,如:
body if ohnoes padding 5px 當未定義的時候,產生的是下面的CSS: body { padding: 5px; } 顯然,這不是我們想要的,如下書寫就安全了: body if ohnoes is defined padding 5px
三元
num = 15 num ? unit(num, 'px') : 20px // => 15px
5.混合書寫(Mixins)
混入 混入和函數定義方法一致,但是應用卻大相徑庭。
例如,下面有定義的border-radius(n)
方法,其卻作為一個mixin(如,作為狀態調用,而非表達式)調用。
當border-radius()
選擇器中調用時候,屬性會被擴展並復制在選擇器中。
border-radius(n) -webkit-border-radius n -moz-border-radius n border-radius n form input[type=button] border-radius(5px) 編譯成: form input[type=button] { -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; }
我們可以利用arguments
這個局部變量,傳遞可以包含多值的表達式。
border-radius() -webkit-border-radius arguments -moz-border-radius arguments border-radius arguments
現在,我們可以像這樣子傳值:border-radius 1px 2px / 3px 4px
!
另外一個很贊的應用是特定的私有前綴支持——例如IE瀏覽器的透明度:
support-for-ie ?= true opacity(n) opacity n if support-for-ie filter unquote('progid:DXImageTransform.Microsoft.Alpha(Opacity=' + round(n * 100) + ')') #logo &:hover opacity 0.5 渲染為: #logo:hover { opacity: 0.5; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); }
父級引用
混合書寫可以利用父級引用字符&
, 繼承父業而不是自己築巢。
例如,我們要用stripe(even, odd)
創建一個條紋表格。even
和odd
均提供了默認顏色值,每行也指定了background-color
屬性。我們可以在tr
嵌套中使用&
來引用tr
,以提供even
顏色。
stripe(even = #fff, odd = #eee) tr background-color odd &.even &:nth-child(even) background-color even 然后,利用混合書寫,如下: table stripe() td padding 4px 10px table#users stripe(#303030, #494848) td color white 另外,stripe()的定義無需父引用: stripe(even = #fff, odd = #eee) tr background-color odd tr.even tr:nth-child(even) background-color even 如果你願意,你可以把stripe()當作屬性調用。 stripe #fff #000
混合書寫中的混合書寫
自然,混合書寫可以利用其它混合書寫,建立在它們自己的屬性和選擇器上。
例如,下面我們創建內聯comma-list()
(通過inline-list()
)以及逗號分隔的無序列表。
inline-list() li display inline comma-list() inline-list() li &:after content ', ' &:last-child:after content '' ul comma-list()
渲染:
ul li:after { content: ", "; } ul li:last-child:after { content: ""; } ul li { display: inline; }