Fork me on GitHub

Programming Design Notes

介紹一款 CSS 模組化工具 - LESS

| Comments



網站需要一個漂亮的介面來將資訊帶給用戶,開發這個介面除了要使用 HTML Tag 外,亦需要使用 Cascading Style Sheets (CSS) 去令這個介面更美觀。有人說: 你只要複製一次程式碼其實亦是複製了一個 Bug,所以我們在設計程式時也盡量不要將相同的程式碼複製到不同地方,在 Java 可以將一些程式碼包裝成 Object 以便不同地方也使用同一組程式碼,但在 CSS 又怎麼辦? 使用外部的 CSS 檔案其實已經很大程度地減少重複的外觀設置程式碼,但這仍然不足夠。

今次介紹的工具能夠在 CSS 設置一些常用參數,外觀設置程式碼的組合,簡單的算法和使用 Javascript 功能等等。這款工具就是 LESS 了。

以下例子可以對比普通 CSSLESS CSS 的不同。

參數:

普通 CSS:
.header{
background-color: #777;
color: #f5f5f5;
}

a{
color: #777;
}

p.desc{
border: solid 1px #777;
color: #777;
}

以上的 CSS 出現了好幾次 #777 這個顏色設置,如果網頁樣式改變了,需要將灰色設置為淺一點的灰色,只好一個一個 #777 找出來加以修改。

使用 LESS 可以:
@gray: #777;
@smoke-white: #f5f5f5;
.header{
background-color: @gray;
color: @smoke-white;
}

a{
color: @gray;
}

p.desc{
border: solid 1px @gray;
color: @gray;
}

這樣只需將 @gray 參數改變就可以一次轉換所有灰色了。

除了參數還可以組合外觀設置程式碼:

普通 CSS:
.header{
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}

ul.stack li{
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}

LESS CSS:
.border-radius (@radius 5px) {
border-radius: @radius;
-moz-border-radius: @radius;
-webkit-border-radius: @radius;
}

.header{
.border-radius;
}

ul.stack li{
.border-radius(3px);
}

因為不同的瀏覽器設置圓角的程式碼也不同,如果以後又有一款新瀏覽器提供圓角的樣式,就可以加一句到 .border-radius 就完成所有設置,方便得多。
LESS 的組合外觀設置程式碼提供了傳入參數和默認參數。

LESS 可以令 CSS 程式碼更直觀:

普通 CSS:
.header p{
background-color: #777;
}

.header p span{
font-size: 18px;
}

.header p span.brand a{
color: #ccc;
}

.header p span.brand a:hover{
color: #f5f5f5;
}

LESS CSS:
.header{
p{
background-color: #777;

span{
font-size: 18px;

&.brand{
a{
color: #ccc;

&:hover{
color: #f5f5f5;
}
}
}
}
}
}

這樣可以更加清楚知道 .header 內的所有風格。

如要使用 LESS2 種方法:
  • 使用 less.js 在瀏覽器將 LESS CSS 轉換為普通 CSS
  • 下載 Node.js 然後使用 npm 去下載 LESS 並使用 CommandLESS CSS 轉換為普通 CSS

第一個方法在開發環境使用還好,在真實環境建議使用第 2 種方法。

我弄了一個 Node.jsscript 以方便大量轉換 CSS,有興趣可拿去用:
#!/usr/bin/env node

var sys = require('util')
var fs = require('fs');
var less = require('less');

var targetDir = 'public';
var currentDir = 'source';

convertResource(currentDir);

function convertResource(dirName){
fs.readdir(dirName, function(err, files){
if (err){
console.log(err);
return;
}
for (var i = 0; i < files.length; i++){
(function(){
var sourceFile = dirName + "/" + files[i];
var targetFile = sourceFile.replace(currentDir, targetDir);
fs.lstat(sourceFile, function(e, stats){
if (stats.isDirectory()){
if (!isFileExist(targetFile)){
fs.mkdirSync(targetFile);
}
convertResource(sourceFile);
}else{
if (sourceFile.match(/^(.*)(\.css|\.less)$/i)){
complieCss(sourceFile, targetFile.replace(/^(.*)(\.css|\.less)$/i, "$1.css"));
}else{
var is = fs.createReadStream(sourceFile)
var os = fs.createWriteStream(targetFile);
sys.pump(is, os);
}
}
});
})();
}
});
}

function complieCss(sourceFile, targetFile){
fs.readFile(sourceFile, "utf-8", function(e, data){
new(less.Parser)({
paths: [".", sourceFile.substring(0, sourceFile.lastIndexOf('/'))],
filename: sourceFile
}).parse(data, function (err, tree) {
if (err) {
console.log(err);
} else {
try {
css = tree.toCSS({
yuicompress: true
});
if (targetFile) {
if (isFileExist(targetFile)){
fs.unlinkSync(targetFile);
}
var fd = fs.openSync(targetFile, "w");
fs.writeSync(fd, css, 0, "utf8");
}else{
sys.print(css);
}
} catch (e) {
console.log("CSS file complie error. Ignore file: " + sourceFile);
}
}
});
});
}

function isFileExist (path) {
try {
fs.statSync(path);
return true;
} catch (e) {
return false;
}
}

更多的用法可在 LESS 官網找到:
官網: http://lesscss.org/