網頁介面開發-拖拉元素調整排序
0 |
各位老師、各位同學,我今天要示範的網頁介面開發技巧是--拖拉元素調整排序,(停頓兩秒),拖拉元素調整排序...
開始前,請大家先看示範網頁,網頁上會顯示五個<span>方塊,假設其排序有特定意義(例如: 出場順序,決定先用皮卡丘之後再派妙蛙種子之類的),使用者可用滑鼠或手指直接拖曳方塊改變其排列順序,當某個方塊被拖到另一個方塊上,就對調兩個方塊的位置,藉此自由調整所有方塊的排列順序。
另外,網頁還很騷包地貼心地加上兩個方塊交換位置的動畫效果,更直覺地呈現"位置對調"的操作感受。
相信大家都已經試玩體驗過效果了。
拍拍手覺得好酷好炫,但一點也不想知道程式該怎麼寫的同學,可由教室後方離開去福利社買冰棒吃囉! 有興趣了解程式開發細節的苦命同學們,則可以留下來看看程式裡用到的技巧:
- 產生五個<span>方塊的部分,採用的是
陳研希knockout.js MVVM的做法。宣告了一個viewModel函數,透過ko.observableArray()保存資料物件陣列,<div id="dvList" data-bind="foreach: items">將資料物件陣列的每個元素對應成一個<span>,用<span data-bind="text: name">可將屬性當成<span>的內容。 viewModel函數中,var self = this;的做法可視為knockout.js中的Best Practice,請同學抄一下筆記。 - 為突顯knockout.js可即時反應資料變化的特性,我刻意每隔0.5秒塞入一筆資料,讓我們可觀察到Player0到Player4逐一出現的過程。而由於setTimeout塞入資料為非同步作業,為了確保在全部完成後才執行下一步驟,再次使用$.Deferred()的技巧處理同步化。(上回在做Google Maps API呼叫時已用過一次)
- 元素拖拉部分採用的是Kendo UI Web (補充簡介)的Drag & Drop功能。實測發現,用Kendo UI所開發的拖拉功能,不管在PC使用滑鼠操作或在行動裝置進行手指觸控操作都很順暢,不需針對不同裝置特意改寫,十分便捷。(但有些小眉角,例如: 有多個放置目標時如何取得當時的放置目標,需要點小技巧,細節可參見程式註解)
- 至於方塊對調位置的動畫效果,則是將<span>元素先轉成position: absolute,再靠.animate({ left: 新座標 })搞定,對
林志玲jQuery來說不過小菜一碟。移動過程為了讓使用者有方塊是浮起來在空中飛移的錯覺,借用了CSS3的transform: scale(1.1,1.1)讓<span>放大10%,此時早先介紹的HTML5/CSS3瀏覽器支援速查工具派上用場,查詢結果顯示要IE10, FF16才支援不加-ms-, –moz-的寫法,Safari/Chrome則仍需要-webkit-,所以乖乖加上-ms-, –moz-, –webkit-寫成三筆。 - <span>對調順序的部分,用jQuery的.after()就可以解決,但為了定址方便,我偷偷加放了一個空白<span class='item-pos'>,程式碼瞬間簡化許多。
完整程式碼如下,已內含不少註解,但坦白說,因涉及不少進階技巧,程式碼並不好懂,希望註解夠清楚,若有疑問請提出,我再修正補充。
<!DOCTYPE html>
<html>
<head>
<title>Drag to Swap Demo</title>
<script src="../Scripts/jquery-1.7.2.min.js"></script>
<script src="../Scripts/kendo/kendo.web.js"></script>
<script src="../Scripts/knockout-2.1.0.js"></script>
<link href="../Content/kendo/kendo.common.min.css" rel="stylesheet" type="text/css" />
<link href="../Content/kendo/kendo.metro.min.css" rel="stylesheet" type="text/css" />
<script>
//定義ViewModel類別
function viewModel() {
//將this另指派給self變數,之後以其代表View Model本體,
//避免與函數中的this所指的對象混淆
var self = this;
//定義一個集合存放資料
self.items = ko.observableArray();
//定義加入item的方法, 在items中加入具有name及score屬性的物件
self.addItem = function(name, score) {
self.items.push({ name: name, score: score });
};
}
$(function () {
//建立ViewModel
var vm = new viewModel();
//每隔0.5秒加一筆以觀察knockoutJs讓UI即時反應資料變化的效果
//使用jQuery.Deferred處理實現完成時機的同步
function job(i) {
var df = $.Deferred();
setTimeout(function () {
vm.addItem("Player" + i, i * 100);
df.resolve();
}, i * 500);
return df.promise();
}
//建立延遲0-4秒執行的加入item作業
var jobs = [];
for (var i = 0; i < 5; i++) {
jobs.push(job(i));
}
//等待所有job執行完畢,掛上Kendo UI拖拉功能
$.when.apply(null, jobs).then(function () {
var $items = $("span.item");
//加上拖曳特性,hint事件回傳拖曳過程顯示的元素
$items.kendoDraggable({
hint: function (e) {
return e.clone().addClass("drag-item");
}
})
.each(function () {
//由於kendoDropTarget事件中,被放置對象this非亦無事件屬性可存取
//故使用Closure方式將元素存入$item供drop事件存取
var $item = $(this);
//加上放置目標特性才能接成為拖曳的目標
$item.kendoDropTarget({
//拖曳到目標元素上方及離開時改變CSS,提供使用者可以放置的提示
dragenter: function (e) { $item.addClass("drop-item"); },
dragleave: function (e) { $item.removeClass("drop-item"); },
//在目標元素上方鬆開滑鼠或手指離開觸控螢幕時觸發drop事件
drop: function (e) {
//透過以下方式取得拖曳元素及放置元素
var $drag = e.draggable.element;
var $drop = $item;
$drop.removeClass("drop-item");
//拖曳對象與放置目標相同時不處理
if ($drag.text() == $drop.text()) return;
//顯示位置交換動畫
//先將全部元素轉為絕對座標
$items.each(function () {
//先記下座標值,以.data()保存
var $elem = $(this);
$elem.data("pos", $elem.position());
})
.each(function () {
//維持座標位置,但換成絕對座標
var $elem = $(this);
var pos = $elem.data("pos");
$(this).css({
position: "absolute",
top: pos.top + "px",
left: pos.left + "px"
});
});
//$drag與$drop交換位置,使用animate
var dragLeft = $drag.data("pos").left + "px";
var dropLeft = $drop.data("pos").left + "px";
var $moving = $drag.add($drop);
$moving.addClass("move-item");
$drag.animate({ left: dropLeft }, 1000);
$drop.animate({ left: dragLeft }, 1000, function () {
$moving.removeClass("move-item");
//交換位置
var $dragPos = $drag.prev(".item-pos");
$drop.prev(".item-pos").after($drag);
$dragPos.after($drop);
//改回相對座標
$items.css({ position: "", left: "", top: "" });
});
}
});
});
});
//將ViewModel與UI結合
ko.applyBindings(vm);
});
</script>
<style>
html,body { font-size: 9pt; }
.item
{
font-family: Segoe UI; display: inline-block;
border: 1px solid gray;
margin-left: 5px; padding: 5px;
width: 80px; height: 25px;
background-color: #0099FF; color: white;
text-align: center;
}
.drag-item
{
opacity: 0.5;
background-color: #FF3300;
}
.move-item
{
z-index: 9;
-ms-transform: scale(1.1,1.1); /* IE 9 */
-webkit-transform: scale(1.1,1.1); /* Safari and Chrome */
-moz-transform: scale(1.1,1.1); /* Firefox */
}
#dvList { margin-top: 20px; }
.drop-item
{
background-color: Purple;
}
</style>
</head>
<body>
<div id="dvList" data-bind="foreach: items">
<span class='item-pos'></span>
<span class='item'>
<span class='item-name' data-bind="text: name"></span>
(<span class='item-score' data-bind="text: score"></span>)
</span>
</div>
</body>
</html>
Comments
Be the first to post a comment