一、背景
在越來越重視用戶體驗的今天,換膚功能也慢慢被重視起來。一個web系統用戶可以選擇一個自己喜歡的系統主題,在用戶眼里還是會多少加點分的。我們很開心的是easyui v1.3.4有自帶default gray black bootstrap metro五款皮膚,但是它並不像bootsrap提供了很完整的css框架,不能提供項目需要的所有的css,所以還需要自己編寫控件之外的一些css。給系統換膚時,easyui控件都沒問題,問題就在於自己編寫的這部分css怎么實現換膚,當然,最簡單的辦法就是為每一款主題都寫對應的一份自定義css然后在項目中加載,這樣是可以實現。
但是我覺得這樣有點羅嗦了,當你添加新的css或修改css時,你要同時修改N份css,每一個主題對應一份,而且easyui除了這5款默認的主題還有其它主題或者我們還可以自定義主題,那這樣修改css就更不現實了。所以我們就想到的動態css,也就是css預處理技術。
二、CSS預處理技術
CSS 預處理器技術已經非常的成熟,常用的預處理器框架有:
1、Less 官網:http://lesscss.org/
2、Sass 官網:http://sass-lang.com/
3、Stylus 官網:http://learnboost.github.io/stylus/
我研究比較多的只有less,后兩者也只是了解了下,所以這里我還是選用less來實現
我們先來看看用less帶來了哪些方便
1、用Less我們可以實現用變量去寫css,可以很方便的實現換膚功能(只要改變變量的值即可)
@the-border: 1px; @base-color: #111; @red: #842210; #header { color: (@base-color * 3); border-left: @the-border; border-right: (@the-border * 2); } #footer { color: (@base-color + #003300); border-color: desaturate(@red, 10%); }
2、Less提供了很多很有用的函數比如lighten darken fadein fadeout…等等,比如把字體A顏色設置為#1382CE,字體B跟A同一顏色,只是比較淡:
@fontcolor:#1382CE;
.font1 { color: @fontcolor; } .font2 { color: lighten(@fontcolor,30%);
}
3、我們也可以自己定義自己的less函數,比如我們寫一個背景漸變的css,我們可以定義一個漸變的函數,如下:
.gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) { background: @color; background: -webkit-gradient(linear, left bottom, left top, color-stop(0, @start), color-stop(1, @stop)); background: -ms-linear-gradient(bottom, @start, @stop); background: -moz-linear-gradient(center bottom, @start 0%, @stop 100%); background: -o-linear-gradient(@stop, @start); filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@stop,@start)); }
然后我們只需要這樣用:
.head-north { .gradient(colorDefault,colorFrom, colorTo); }
我就簡單介紹這些,less還有很多用法,大家自己去探究。
三、系統換膚實現思路
當然不光是引入less就完了,事情遠沒有這么簡單,我們知道既然是dynamic css那么一定是需要編譯的,less最終也是只是編譯生成css代碼,那么就有一個問題,什么時候編譯
1、使用編譯工具比如koala、SimpleLess等,在項目發布前編譯好放在項目中
2、前端解析編譯,需要在項目中引入less.js
3、后台動態解析,在java環境下的編譯引擎比較多,.net下好像我就找到一個dotless,而且實現的還不是很完整,只能說是less v1.5的部分實現。
先分析下這三種方式,第一種用編譯工具,就是我發布項目要做的事情變多了,我一向比較懶,喜歡簡單的,萬一我忘記了怎么辦,總覺得是多了一趟事情。
第二種前端實時解析,這種其實是很理想的一種方便,也很方便,但是帶來的一個問題就是前端的效率,如果css少還好說,多了肯定會影響效率的。
第三種呢,后台動態編譯,后台只編譯一次后緩存起來,對服務器基本沒有影響,這樣很好,問題是我這個框架是.net的,是dotless實現不完整,但是我們不一定會用到less所有的功能,有基本功能就夠用了,比如條件判斷等更高級的使用,我們可以在處理前自己先預處理一下,再給less類庫去解析。
好吧,那么我就選擇第三種在后台動態解析了。
具體思路:
1、根據當前用戶選擇的theme取得easyui.css文件並根據特征獲得主題的相關變量@body-background-color或@body-text-color等等,這些變量我在我自定義的css中會常用到,取得這些變量是很容易做到的。
2、利用這些變量,我們可以theme.less中編寫自定義的css
3、利用asp.net mvc4.0中的bundle中來處理less
4、頁面引用輸出css
四、具體實現
1、引入dotless類庫
2、定義easyui中的變量,應該包括以下變量
/*common*/ @border-color @border-radius @font-size @shadow-background-color @mask-background-color @toolbar-background-color @toolbar-border-color @split-color @split-proxy-color /*Header*/ @header-background-color @header-text-color @header-gradient-used @header-gradient-from @header-gradient-to /*body*/ @body-background-color @body-text-color /*grid*/ @grid-header-background-color @grid-header-gradient-from @grid-header-gradient-to @cell-border-color @alt-background-color /*state*/ @selected-background-color @selected-text-color @selected-border-color @hover-background-color @hover-text-color @hover-border-color @invalid-background-color @invalid-border-color @invalid-text-color /*menu*/ @menu-background-color @menu-text-color @menu-border-color /*button*/ @button-background-color @button-selected-color @button-text-color @button-gradient-used @button-gradient-from @button-gradient-to @button-radius @button-split-color1 @button-split-color2
這些變量要通過用戶的theme取得easyui.css文件並解析這個文件去給這些less變量賦值
3、自定義自己的動態css,下面是我的項目中theme.less文件的片段
.z-body { background:@body-background-color; } .z-toolbar,.z-toolbar-dialog{ border-color:@border-color; background:@header-background-color; } .z-txt { border-color:@border-color; background:white; }
.head-left, .head-right, .head-right a
{ color: $when(@theme=gray,default,black,bootstrap| #fff | #000); } .head-north {
.gradient(@selected-background-color,@header-background-color, @selected-background-color); } .head-south,.head-south a { background:@header-background-color; color: lighten(@body-text-color,30%); }
……
4、在項目中的BundleConfig.cs中的RegisterBundles中注冊bundles
using System; using System.IO; using System.Web; using System.Web.Hosting; using System.Web.Mvc; using System.Web.Optimization; using Zephyr.Utils; namespace Zephyr.Web.Mvc { public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { var dirBase = new DirectoryInfo(HttpContext.Current.Server.MapPath( string.Format("~/Content/js/easyui/{0}/themes",AppSettings.EasyuiVersion))); var dirs = dirBase.GetDirectories(); foreach (var dir in dirs) { if (dir.Name == "icons") continue; var theme = dir.Name; var themeBundle = new Bundle(string.Format("~/Content/css/theme/{0}", theme)).Include( "~/Content/css/less/elements.less", "~/Content/css/less/theme.less"); themeBundle.Transforms.Add(new EasyuiLessTransform(theme)); themeBundle.Transforms.Add(new LessTransform()); themeBundle.Transforms.Add(new CssMinify()); bundles.Add(themeBundle); } } } }
這里在bundle的Transforms中添加了三個BundleTransform處理,
其中EasyuiLessTransform是我對easyui變量及自定義條件判斷$when的處理
LessTransform則是調用dotless庫解析less代碼
using System; using System.Web.Optimization; using dotless.Core; namespace Zephyr.Web.Mvc { public class LessTransform : IBundleTransform { public void Process(BundleContext context, BundleResponse response) { var compiled = Less.Parse(response.Content); if (string.IsNullOrEmpty(compiled)) throw new Exception("less文件中語法有錯誤!"); response.Content = compiled; response.ContentType = "text/css"; } } }
第三個CssMinify則是System.Web.Optimization下面的對css混淆壓縮處理。
5、在頁面中引用,razor頁面中只需要以下代碼即可
@Styles.Render("~/Content/css/theme/" + AppLoginer.Theme)
至此,換膚功能已完成,我們可以看看效果
6、metro風格,這款很干凈簡潔,我自己很喜歡
六、后述
這樣一來,這個功能就算是很靈活了,就算是以后再加入一款新主題,代碼也完全不用修改,而且想效果更好點還可以p幾張題頭的圖片換上。
總體效果當然和專業美工做的當然沒法比,不過做做業務管理系統忽悠忽悠客戶已經足夠了。