使用javascript打造富有個性及物理特性的JSF組件。
在1.4中修正并調整了一些QFaces中的js代碼之后,繼續除bug及增加功能。現在是1.5beta。可以在線看Demo,并且現在效果更好。
打算在1.5中增加一個基本
的ajaxSupport組件或是日歷組件并繼續調優性能,并增加一個style文件,更好的統一下樣式,現在已經慢慢形成自己的風格。在增加功能的
同時考慮實用與性能,用戶友好,及可擴展等。在1.4后的一段時間又使我對編程的認識提高了不少,但不只是編程上的,包括很多其它方面的。
好了,繼續。QFaces的js函數庫最近又增加了不少,這些都是自己慢慢增加上去的,為了打造富有個性的組件,最近又添了兩個主要函數。有朋友問為什么不使用ext或者使用其它現成的js
庫。說真的,我還沒有使用過ext,不是很了解。我喜歡自己寫核心的東西,因為只有自己寫才能把它玩轉得更好。在寫不了的情況下,才會借鑒或使
用,在俱備足夠知識之后,我喜歡重復造輪子。我并不認為重復造輪子就是錯的,即使自己可能造得不好,但并不是絕對沒有收獲。
新增加的兩個主要js函數:
QFaces.accDisplay(id, action, direction, G);
id -> 目標組件id,如某個div,或table的id等。
action -> "show", "hide" 展示方式,默認show, 顯示或隱藏目標組件
direction -> "width","height","both" ,展示方向,默認height,可以從水平,垂直,或同時兩個方向伸展或收縮。
G -> 重力加速度,原為0.006,在幾次調整之后以0.006與高度的比率作為默認, 為什么不是9.8?——因為這里不是地球,是QFaces星球。并且時間也不是以秒為單位,而是毫秒。所以設在0.006左右效果較好。
函數可以讓目標組件如“div”有一個富有彈性的展示效果,使用了自由落體的定理,所以在落下及彈起都有一個勻加速及勻減速的過程,對stepArr數組進行設置可以增加效果。 簡單的使用方式可以這樣: QFaces.accDisplay(id), 其它參數按默認,則將以垂直加速富彈性的方式顯示目標組件。
QFaces.facesMakeFrame(id)
這個函數可以給任意組件id添加一個邊框,包括div,table,可見的,不可見的等,甚至button,input...這個函數相對簡單得多,但是很方便,效果也非常好,在QFaces組件庫下可以直接調用,里面捆綁了8張小png作為組件邊框,這是我在photoshop中做的圖,然后切割而成的,可以很方便的替換,共4個角4個邊。如果單獨使用,那么需要指定這些圖片的位置。并約束一下左上角及右下角的寬高就可以了,。單用CSS也可以做出圓角及陰影效果,但是再怎么做也沒有photoshop做出來的效果好。
函數原形:

QFaces.accDisplay = function(id, action, direction, G)
QFaces.accDisplay = function(id, action, direction, G) {
var mydiv = QFaces.getComponent(id);
if (mydiv.locked) {
return;
} else {
mydiv.locked = true; // 鎖定source,避免快速無限點擊。
}
// 這里表示了從原始寬高度的0倍 -> 運行到原始寬高 -> 然后跳回原來的0.8倍 -> 再回到原來的寬高度。
// 配合自由落體 (S = v0t * 1/2att),看起來就是一個彈跳過程。
var stepArr = new Array();
if (action == "show" || action == null) {
stepArr[0] = 0;
stepArr[1] = 1.2;
stepArr[2] = 0.8;
stepArr[3] = 1;
} else if (action == "hide") {
stepArr[0] = 1;
stepArr[1] = 1.2;
stepArr[2] = 0;
}
// 初始化變量.高度與G的比例 250xp : 0.006S
var size = QFaces.getSize(id);
var stretchObj = new StretchObject();
stretchObj.source = mydiv;
stretchObj.action = (action == null ? "show" : action); // 確定方向
stretchObj.originalLeft = mydiv.offsetLeft;
stretchObj.originalWidth = size[0];
stretchObj.originalHeight = size[1];
stretchObj.stepArr = stepArr;
stretchObj.interval = 10;
// 以一個比率動態改變加速度大小,避免大區塊的動畫時間過長
// stretchObj.G = 0.006;
stretchObj.G = (stretchObj.originalHeight * 0.006 / 250);
if (direction == null || direction == "height" || direction == "both") {
stretchObj.displayHeight = true;
}
if (direction == "width" || direction == "both") {
stretchObj.displayWidth = true;
}
if (G != null) {stretchObj.G = G;} // 如果用戶指定了加速度
stretchObj.init(); // 初始化
travel(stretchObj); // Start display
function travel(obj) {
// 累計單步的執行時間,并計算距離。
obj.stepTimeUsed += obj.interval;
obj.stepDistance = obj.v0 * obj.stepTimeUsed + 0.5 * obj.G * obj.stepTimeUsed * obj.stepTimeUsed;
// 設置source的位置,高寬度。
obj.setSize(obj.stepWidth + obj.stepDistance, obj.stepHeight + obj.stepDistance);
// 運行完畢后解鎖source,并跳出。
if (obj.isOver()) {
obj.setSize(obj.originalWidth * obj.stepArr[obj.stepArr.length - 1], obj.originalHeight * obj.stepArr[obj.stepArr.length - 1]);
// 如果是收縮,則清理數據后隱藏,并且設回原始位置
if (obj.action == "hide") {
obj.source.style.left = obj.originalLeft + "px";
obj.source.style.width = "";
obj.source.style.height = "";
obj.source.style.display = "none";
}
obj.source.locked = false;
return;
}
// 單步運行完畢
if (obj.isStepOver()) {
obj.stepOver();
}
QFaces.setTimeout(travel, obj.interval, obj);
}
function StretchObject() {
this.source = null; // 綁定的界面控件
this.action = null;
this.originalLeft = null; // 原始的位置偏移
this.originalWidth = null; // 原始寬度
this.originalHeight = null; // 原始高度
this.displayWidth = false;
this.displayHeight = false;
this.stepArr = null; // 要運行的數組
this.v0 = null;
this.v1 = null;
this.step = 1;
this.stepWidth = 0; // 步寬,每次方向改變時都會改變。
this.stepHeight = 0; // 同上
this.stepTimeTotal = 0; // 每次落下或彈起所需要的總時間。
this.stepTimeUsed = 0; // 每次落下或彈起的時間累計。
this.stepDistance = 0; // 在stepTimeUsed下所運行的距離
this.G = 0.006; // 重力加速度,為什么不是9.8米/秒?因為這里不是地球 ————是QFaces星球
this.interval = 10;
this.isOver = function() {
return (this.step > this.stepArr.length - 1);
}
this.isZoomOut = function() {
return (this.stepArr[this.step] > this.stepArr[this.step - 1]);
}
this.isStepOver = function() {
if (this.stepTimeUsed >= this.stepTimeTotal) {return true;}// 這里防止無限止伸縮。
var currentHeight = this.stepHeight + this.stepDistance;
if (this.isZoomOut()) {
return currentHeight >= this.originalHeight * this.stepArr[this.step];
} else {
return currentHeight <= this.originalHeight * this.stepArr[this.step];
}
}
this.setSize = function(width, height) {
if (this.displayWidth && width >= 0)
this.source.style.width = width + "px";
if (this.displayHeight && height >= 0)
this.source.style.height = height + "px";
if (this.displayWidth && width >= 0) {// 重新設置source的位置偏移。
this.source.style.left = (this.originalLeft + (this.originalWidth - width) * 0.5) + "px";
}
}
this.calculateStepTimeTotal = function() {
if (this.stepArr[this.step] != null) {
var nextS = Math.abs(this.originalHeight * (this.stepArr[this.step] - this.stepArr[this.step - 1]));
if (this.isZoomOut()) { // 下降過程 s = 0.5gtt;
this.stepTimeTotal = Math.sqrt(2 * nextS / this.G);
} else {// G = (v1 - v0)/t
this.stepTimeTotal = Math.abs((this.v0 - this.v1) / this.G);
}
} else {
this.stepTimeTotal = 0;
}
}
this.stepOver = function() {
var isZoomOut_previous = this.isZoomOut();
this.stepWidth = this.originalWidth * this.stepArr[this.step];
this.stepHeight = this.originalHeight * this.stepArr[this.step];
this.setSize(this.stepWidth, this.stepHeight);
this.step += 1; // 進入下一階段
this.stepDistance = 0;
this.stepTimeUsed = 0;
// 特別情況(漸縮):如果前一階段是下降,并且整個過程是漸縮 。 (s = v0t + 0.5gtt)
if (this.action == "hide") {
if (isZoomOut_previous) {
var zoomInS = Math.abs(this.originalHeight * (this.stepArr[this.step] - this.stepArr[this.step - 1]));
var zoomInT = Math.sqrt(2 * zoomInS / this.G);
this.v0 = this.G * zoomInT; // 用整段最長距離計算從最大位置縮回none時的初速度,瞬速。
this.v0 = this.v0 * -1; // 上升過程,v0必須是負的
this.v1 = 0;
}
} else {// 正常漸展開的過程。
if (isZoomOut_previous) {
this.v0 = this.v1 * -1; // 在落地返彈瞬間,速度達最大,并且末速度變成反彈之后的始速度
this.v1 = 0; // 下一階段的反彈末速度為0
} else {// 從上升到下降
// 計算下一下降過程需要經過的距離。
var tempS = Math.abs(this.originalHeight * (this.stepArr[this.step] - this.stepArr[this.step - 1]));
var tempT = parseInt(Math.sqrt((2 * tempS / this.G))); // 計算下降需要經過的時間。
var tempGT = this.G * tempT; // 計算末速度。
this.v0 = 0;
this.v1 = tempGT;
}
}
// 計算下一段所需要的時間
this.calculateStepTimeTotal();
}
this.init = function() {
this.source.style.display = "block";
this.source.style.overflow = "hidden";
this.source.style.position = "absolute";
this.source.style.zIndex = QFaces.zIndex++;
this.stepWidth = this.originalWidth * this.stepArr[this.step - 1];
this.stepHeight = this.originalHeight * this.stepArr[this.step - 1];
this.originalLeft = this.source.offsetLeft; // 必須保存原始偏移。
if (this.displayWidth)
this.source.style.width = this.stepWidth + "px";
if (this.displayHeight)
this.source.style.height = this.stepHeight + "px";
var s = this.originalHeight * (this.stepArr[this.step] - this.stepArr[this.step - 1]);
if (this.isZoomOut()) { // 加速
this.v0 = 0;
this.v1 = this.G * parseInt(Math.sqrt((2 * s / this.G)));
} else { // 減速
this.v0 = this.G * parseInt(Math.sqrt(Math.abs(2 * s / this.G))) * -1;
this.v1 = 0;
}
// 計算下一級所需要的時間.
this.calculateStepTimeTotal();
}
}
}

QFaces.facesMakeFrame = function(id)
QFaces.facesMakeFrame = function(id) {
var sourceObj = ((typeof id) == "string") ? QFaces.getComponent(id) : id;
QFaces.assertNull(sourceObj, "facesMakeFrame : Object not found " + id);
var sourceTemp = QFaces.getComponent(sourceObj.id); // sourceTemp == null 表明組件仍未被加入到組件樹中。
var parentObj = sourceTemp != null ? sourceTemp.parentNode : (document.body ? document.body : document.documentElement);
var targetObj = QFaces.ele("div");
var src_0_0 = QFaces.getComponent("name.huliqing.qfaces.images.frame_top_left").value;
var src_0_1 = QFaces.getComponent("name.huliqing.qfaces.images.frame_top_center").value;
var src_0_2 = QFaces.getComponent("name.huliqing.qfaces.images.frame_top_right").value;
var src_1_0 = QFaces.getComponent("name.huliqing.qfaces.images.frame_left").value;
var src_1_2 = QFaces.getComponent("name.huliqing.qfaces.images.frame_right").value;
var src_2_0 = QFaces.getComponent("name.huliqing.qfaces.images.frame_bottom_left").value;
var src_2_1 = QFaces.getComponent("name.huliqing.qfaces.images.frame_bottom_center").value;
var src_2_2 = QFaces.getComponent("name.huliqing.qfaces.images.frame_bottom_right").value;
var img_0_0 = QFaces.ele("img");
var img_0_2 = QFaces.ele("img");
var img_2_0 = QFaces.ele("img");
var img_2_2 = QFaces.ele("img");
var _table = QFaces.ele("table");
var _arr = QFaces.eleTable(_table, 3, 3); // 創建一個3,3表格
img_0_0.setAttribute("src", src_0_0);img_0_0.style.width = img_0_0.style.height = "100%";
img_0_2.setAttribute("src", src_0_2);img_0_2.style.width = img_0_2.style.height = "100%";
img_2_0.setAttribute("src", src_2_0);img_2_0.style.width = img_2_0.style.height = "100%";
img_2_2.setAttribute("src", src_2_2);img_2_2.style.width = img_2_2.style.height = "100%";
sourceObj.style.display = "block"; // 這里必須顯示出來,否則在包裝后可能看不到正確的大小
_arr[0][0].appendChild(img_0_0);
_arr[0][1].style.cssText = "background:url('" + src_0_1 + "') repeat-x";
_arr[0][2].appendChild(img_0_2);
_arr[1][0].style.cssText = "background:url('" + src_1_0 + "') repeat-y";
_arr[1][1].appendChild(sourceObj); _arr[1][1].style.background = sourceObj.style.background; // 讓背景色互相匹配。
_arr[1][2].style.cssText = "background:url('" + src_1_2 + "') repeat-y";
_arr[2][0].appendChild(img_2_0);
_arr[2][1].style.cssText = "background:url('" + src_2_1 + "') repeat-x";
_arr[2][2].appendChild(img_2_2);
_arr[0][0].style.cssText = "width:25px;height:22px"; // 約速左上角列的寬高度
_arr[2][2].style.cssText = "width:25px;height:25px;";// 約速右下角
_table.style.border = _table.style.padding = _table.style.margin = _table.cellSpacing = _table.cellPadding = "0";
_table.style.height = "100%"; // 當targetObj有伸縮效果時,讓table也跟著自動改變
_table.setAttribute("id", sourceObj.id + ":frame:table");
targetObj.setAttribute("id", sourceObj.id + ":frame");
targetObj.appendChild(_table);
parentObj.appendChild(targetObj);// 必要的,把新組件加到原始父組件下,或是document.body下
return targetObj;
}
這兩個函數都在IE6,7,8,FireFox3,
Chrmoe下測試通過,其它的沒有測試。另人意外的是chrmoe的性能非常好,從網頁裝載或js上的速度都是最快的。性能比較如下:Chrmoe > FireFox > IE,
IE6下的效果及性能是最差的。IE7,8也沒有好到哪里去。IE8標準模式存在Bug,IE8的兼容模式還可以。FireFox的友好程度最高。Chrmoe可以繼續體驗,性能感覺很好。
另外QFaces.js新增了不少其它輔助函數。以下是兩個使用了該函數的組件,現在組件有了浮動的陰影,能完美隨意拖動,并且富有彈性的伸縮展示(在1.4中是漸隱漸現的展示效果)可以看到加了邊框的效果比1.4好了很多。
在線演示:
http://huliqing-qfaces.appspot.com/qfaces-example/ui-tree.faces
http://huliqing-qfaces.appspot.com/qfaces-example/ui-saveState.faces
- huliqing@huliqing.name
- http://www.huliqing.name