去年年底学过Sass之后基本就一直在实际工作中用Sass写CSS代码,感觉很方便代码的组织,对CSS的模块化也很有帮助。和Sass类似的还有Less,我也简单了解了一下,发现两者的核心语法有很多相似的地方,学过一种之后另一种也能很快上手。
Sass的学习和使用都并不难,基本上一天就能掌握,所以这篇手记不完全是从零开始的介绍,主要想记录一下自己学习中觉得比较重要需要理解的地方以及遇到的一些坑。
Sass的概念和优势
Sass——Syntactically awesome style sheets。
Sass是一个CSS预处理器,首先编写Sass代码,经过编译等处理变成CSS。
Sass的几点优势:
- 可以根据页面结构和功能模块将CSS代码分别写到多个较小的文件里,然后通过编译再合并成一个CSS文件,这样无论在开发和修改的时候都能更快更方便地找到需要设置和修改样式的元素;
- 通过使用变量能方便地统一修改一些公共的值如颜色/字体大小等,减少工作量;
- 能将一些公共样式代码组织到一起实现复用,减少代码量
Sass开发环境的搭建
Sass最初是使用Ruby语言开发的,通常需要首先安装Ruby。因为之前简单学习了一下node.js和gulp,这里我选择了另外一种方式。
首先是安装node.js开发环境,然后安装gulp。接着安装node-sass包,这个能够帮我们在node.js环境下编译Sass,最后是安装gulp-sass这个gulp插件。这些都安装好之后就能基于gulp来设计我们Sass开发的工作流:
const gulp = require('gulp');
const plugins = require('gulp-load-plugins')();
const gutil = require('gulp-util');
const cleanCSS = require('gulp-clean-css');
const combiner = require('stream-combiner2');
const browserSync = require('browser-sync').create();
const reload = browserSync.reload;
const handleError = function(err) {
var colors = gutil.colors;
console.log('\n');
gutil.log(colors.red('Error!'));
gutil.log('fileName: ' + colors.red(err.fileName));
gutil.log('lineNumber: ' + colors.red(err.lineNumber));
gutil.log('message: ' + err.message);
gutil.log('plugin: ' + colors.yellow(err.plugin));
};
/*开发阶段可以做SASS的编译*/
/*开发阶段,不用gulp-clean-css进行压缩*/
/*配置SASS任务*/
gulp.task('watchsass', function() {
/*监听Sass文件,并在修改后自动编译成CSS,然后通知浏览器并注入CSS*/
gulp.watch('src/sass/**/*.scss', function (event) {
var paths = plugins.watchPath(event, 'src/sass/', 'src/css/');
// 文件操作的事件类型
gutil.log('File ' + paths.srcPath + ' was ' + gutil.colors.green(event.type));
// 文件输出路径
gutil.log('Dist ' + paths.distPath);
// 用stream-combiner2合并多个stream,只需添加一个错误监听,而且不会让gulp停止运行
var combined = combiner.obj([
// 从src/Sass/取得Sass文件
gulp.src(paths.srcPath),
// 这里开启sourcemap后会生成sourcemap文件,可以在firefox(要在开发者工具的设置里样式编辑器部分勾选显示原始来源)和chrome(同样要在开发者工具的设置里勾选enable JavaScript sourcemaps和enable css sourcemaps)自带的开发者工具里直接调试Sass了
plugins.sourcemaps.init(),
// 编译SASS,outputStyle选项表示输出CSS的风格,默认是nested,对sass里的嵌套在编译成CSS后也会缩进,expanded会在输出CSS的每一个选择器设置之间空一行,compact也在每个选择器之间空一行,但是每个选择器的样式设置都只写在一行里—(就是花括号里的所有样式设置都写在一行),compressed输出压缩后的CSS
plugins.sass({outputStyle: 'expanded'}),
// 添加浏览器前缀,注意这一步要放到编译Sass的后面,否则Sass文件中'//'开头的注释编译会报错
plugins.autoprefixer(['last 2 Chrome versions', 'Firefox > 20', 'ie 6-8', 'last 2 Opera versions', 'last 2 Safari versions']),
plugins.sourcemaps.write('./'),
// 输出到src/css
gulp.dest(paths.distDir),
// 调用Browsersync的reload方法通知浏览器文件修改并注入CSS
reload({ stream: true })
])
// 对合并后的stream添加事件监听
combined.on('error', handleError)
return combined;
})
});
// 先执行监听和编译Sass的任务,然后启动Browsersync,并监听src/路径下所有html、css和js文件(js文件还需要js-hint等前置的任务,可以防止watchjs任务里)
gulp.task('reload',['watchsass'], function() {
browserSync.init({
// 设置监听的文件,以baseDir设置的根目录为起点,单个文件就用字符串,多个文件就用数组
files: ["src/*.html", "src/css/*.css", "src/js/*.js"],
// 启动静态服务器,默认监听3000端口,设置文件监听路径为src/
server: {
baseDir: "./src"
},
// 在不同浏览器上镜像点击、滚动和表单,即所有浏览器都会同步
ghostMode: {
clicks: true,
scroll: true
},
// 更改控制台日志前缀
logPrefix: "sass with gulp",
// 设置监听时打开的浏览器
browser: "firefox",
// 设置服务器监听的端口号
port: 8080
});
});
gulp.task('default', ['reload'], function() {
console.log('gulp is running~');
});
以上是我目前常用的Sass工作流,功能包括编译Sass,自动添加浏览器前缀,生成source map文件方便在浏览器开发者工具中调试,编译中错误提示以及结合browser sync监听html/css/js自动刷新浏览器等,基本包括了Sass开发阶段的常用功能。关于gulp和browser sync的使用可以参考我之前的两篇手记Gulp基础及其在前端工作流自动化实践中的应用和Browsersync结合gulp和nodemon实现express全栈自动刷新。
Sass文件组织Sass通过局部文件的方式来实现CSS代码的模块化,可以根据功能和模块来对局部文件进行命名和分类。目前比较常用也是我在用的一种文件组织形式如下:
sass/
-base/
--_reset.scss # Reset/normalize
...
-components/
-- _buttons.scss # Buttons
--_carousel.scss # Carousel
-- _cover.scss # Cover
--_dropdown.scss # Dropdown
--_navigation.scss # Navigation
...
-helpers/
--_variables.scss # Sass Variables
-- _mixins.scss # Sass Mixins
...
-layout/
--_grid.scss # Grid system
-- _header.scss # Header
-- _footer.scss # Footer
--_sidebar.scss # Sidebar
-- _forms.scss # Forms
...
-index.scss # primary Sass file
base文件夹下主要放置样式重置的文件;helpers文件夹下主要放置变量文件(_variables.scss)和其他文件中用到的混合(_mixins.scss),这些Sass语法相关的文件后面讲解语法的部分会介绍;layout文件夹下主要放置布局相关的样式文件,比如布局容器,栅格系统,浮动样式等;components文件夹下主要放置页面中一个一个功能独立的模块的样式。
Sass局部文件要以“_”下划线开头,否则编译的时候会生成独立的CSS文件,我们的最终目的是通过编译生成index.css这个单一的样式表文件,因此还要在index.scss里加载其他Sass局部文件,例如:@import '_reset.scss'
,更简短的形式如下:@import 'reset'
。
搭建好了Sass开发环境,组织好了Sass局部文件,其实就可以开始Sass开发了,把以前的CSS文件分成多个文件,后缀改为scss就可以按照Sass的方式来组织。不过如果只把Sass用来组织CSS代码就没有完全发挥Sass的强大功能,而这些功能就需要Sass不同于CSS的一些语法来实现。下面就来了解一下Sass的基本语法和功能。
- 变量与数学计算
在CSS中会遇到要重复多次使用相同值的情况,比如,网站页面设计的时候会有一套主要的配色,在页面中的大部分元素中会重复使用这些颜色值,这时就可以定义一个变量并赋值,比如,要创建一个按钮统一颜色的变量,可以用如下代码:$button_color: #ff4200;
,根据前面文件组织方式部分里讲到的,我们可以把所有变量放在一个_variables.scss文件里,便于变量的管理和修改,这样,当设计师想要修改button的颜色时,我们不用到CSS文件里找到每个设置button颜色的地方去修改,只用在_variables.scss文件里修改$button_color
变量的值就能统一修改。
Sass还能进行数学计算,比如要把一个元素的下外边距设为上外边距的一半,可以先定义一个变量表示上外边距:$margin-top: 20px;
,设置下外边距时就可以用另一个变量:$margin-bottom: $margin-top / 2;
,这样在修改的时候就只用修改上外边距的值,减少了工作量。注意运算符和变量还有数值之间最好有一个空格。 - 嵌套
嵌套语法主要是为了改进CSS中的后代选择符。这也是Sass和CSS的一个显著的不同点。通过嵌套语法可以明确选择符之间的结构关系,使HTML结构上相关的代码更紧凑,提高CSS代码的可读性和降低修改难度。
看到许多介绍Sass的文章都提到嵌套不能滥用,我也是在开发工作中实际使用了一段时间才对这一点有深刻体会,之前因为觉得嵌套能把代码写在一起,很方便,所以经常用超过三层甚至更深层的嵌套,后来发现这样编译生成的CSS代码带来了两个问题:一是后代选择符过长,代码冗余量加大,因为每嵌套多一层,都要在后代选择符中加一项;二是需要在CSS中直接修改样式时难度加大,因为在公司里经常需要帮后端工程师修改样式,当后代选择符太长时,选择符的特指度就会很高,要覆盖原有的样式就要用特指度更高的选择符,这样会降低CSS代码的质量。
我目前感觉嵌套需要有限制地使用,最好不要超过三层,主要用在两个地方比较好:一是HTML结构上有明确的结构或者语义关系的元素,比如无序列表中的ul
和li
/表格中的table tr td
等这样在结构或者语义上关系明确的,二是hover before after
这类伪类或者伪元素,通过嵌套也能很好地表明和其他元素的关系。 -
继承与扩展
扩展语法和CSS里的群组选择符对应,在CSS里,多个元素要应用一组相同的样式就可以使用群组选择符,从而避免书写重复的样式,减少代码量,不过,如果群组选择符里的部分元素还有自己独特的样式,就要单独写选择器书写它独特的样式。有了扩展语法,我们就可以用占位选择符把重复的样式写到一起,然后让其他元素继承这一部分样式,来看一个例子:%btn { display: inline-block; padding: 1em; border-radius: 3px; } .btn-order { @extend %btn; background-color: green; color: white; } .btn-delete { @extend %btn; bacdground-color: red; color: white; } .btn-cancel { @extend %btn; background-color: #888; color: black; }
从这个例子里可以看到,相对于使用群组选择符,扩展能减少选择符的书写量,并且能把一个选择符的所有CSS属性写在一起,减少修改样式时的工作量。这个例子也很好地展示了扩展语法的使用场景,那就是只扩展功能相关的元素,比如这里都是按钮功能的元素。如果滥用扩展功能,生成的CSS文件里可能会有很多不相关元素组成的群组选择符,这也会降低CSS代码的可读性。
- 混入
混入和扩展有些类似,都是用来指代一组想要重复多次使用的CSS属性,减少重复的样式代码书写,最开始学习Sass的时候我有些不懂混入和扩展的区别,经过实践并且看了一些文章了解到两者主要有两点不同:一是编译后扩展会对继承扩展样式的选择符进行合并,变成群组选择符,减少实际CSS代码量,而混入则不会合并选择符,即使是完全相同的一组CSS代码,也会重复写在不同的选择符中,这样其实没有起到减少实际CSS代码的效果。
了解了混入与扩展的区别,我也更明确了混入的用途,因为混入可以像编程语言中的函数一样设置形参,传入实参,所以混入可以用于拥有类似样式但样式具体值不同的一组功能相关的元素。还是以前面提到的一组按钮样式为例,btn-order btn-delete和btn-cancel
三种按钮都设置了background-color和color
属性,但是具体的颜色值不同,这时就可以用一个带参数的混入把这两个属性写到一起:
@mixin color($bgc, $cc) {
backgroung-color: $bgc;
color: $cc;
}
之前的按钮样式代码就可以修改为:
%btn {
display: inline-block;
padding: 1em;
border-radius: 3px;
}
.btn-order {
@extend %btn;
@include color(green, white);
}
.btn-delete {
@extend %btn;
@include color(red, white);
}
.btn-cancel {
@extend %btn;
@include color(#888, black);
}
可以看到,合理使用混入,也能很好地减少代码量,方便修改。不过如果你只是想复用一组完全相同的CSS代码给不同元素,也许扩展更适合。
总结Sass并不是只有这些内容,不过日常使用的核心内容就是上面这些了,Sass还是需要编译成CSS,所以我也看到过有的人不喜欢使用Sass,还是坚持用CSS进行开发,但我觉得Sass提出的一些思想是值得借鉴的,如果你选择使用Sass,希望上面我的一些经验能对你有帮助。
参考资料
- 《CSS——the missing manual》 4th edition
- Architecture for a Sass Project
- Sass Guidelines——中文版
- Sass 进阶
- sass 中 @include 与 @extend 的区别
本作品采用知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。要查看该许可协议,可访问 http://creativecommons.org/licenses/by-nc-sa/4.0/ 或者写信到 Creative Commons, PO Box 1866, Mountain View, CA 94042, USA。
共同学习,写下你的评论
评论加载中...
作者其他优质文章