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% 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; }