最近因?yàn)楣ぷ餍枰隽它c(diǎn)HTML5可視化研究如下
為什么簡書沒有字體顏色作為一個(gè)設(shè)計(jì)師很不爽
第一次發(fā)文有寫錯(cuò)的地方還請(qǐng)諸位高手多多包涵
問題陳述
設(shè)想,要在頁面中生成大量的簡單圖形,比如10萬個(gè)方塊,并對(duì)它們進(jìn)行拖拽操作。
基本思路有三種方式,傳統(tǒng)Div,Svg,與Canvas。
為了加拖拽,暫時(shí)沒有用canvas。
因?yàn)閏anvas無法生成dom節(jié)點(diǎn),不存在id這種屬性,需要通過判斷鼠標(biāo)位置來獲取元素再進(jìn)行操作。雖然寫個(gè)isMouseinObj()的function也不是不行,但總覺得以后針對(duì)某對(duì)象單獨(dú)處理會(huì)夜長夢多(主要還是懶)。。。不過從生成圖形角度講,canvas理論上是最快的。
因?yàn)樨澤滤罁?dān)心Dom過多死機(jī),準(zhǔn)備先分區(qū)生成方塊,于是:
一個(gè)簡圖,整體劃分如下,設(shè)兩個(gè)input框的值分別為m, n,每個(gè)藍(lán)塊包括n個(gè)綠塊。點(diǎn)擊每個(gè)藍(lán)塊可在下方生成n個(gè)綠塊。
點(diǎn)擊GenAll可一次性生成m*n個(gè)綠塊。點(diǎn)擊Drag可對(duì)綠塊進(jìn)行拖拽。

模擬開始。
Div+Html5 Drag & Drop
首先是用div模擬方塊。因?yàn)楸容^熟悉寫起來也簡單。
- 初始化Div
js循環(huán)批量生成。
for(var i=0;i<n;i++) {
var box = document.createElement("div");
box.className = 'abox';
var id = i+1;
box.id = 'box'+id;
box.innerHTML = 'Box'+id;
$('#boxdetail').append(box);
delete box;
}
Div的拖拽方式可以分成兩種。
- 應(yīng)用插件拖拽
偷懶大法,用jquery-ui的話,只需要一句話。給要拖拽的元素加上draggable()即可。
<script type="text/javascript" src="jquery-ui.js"></script>
$("#drag").click(function(){
alert('now you can drag');
$(".abox").draggable();
});

Jquery-ui的draggble還有很多其他參數(shù),請(qǐng)參考api文檔。
- Html原生事件拖拽
Html5有原生Drag and Drop事件,堪稱神器。
被拖動(dòng)對(duì)象可以觸發(fā)的事件有:
(1)ondragstart:源對(duì)象開始被拖動(dòng)
(2)ondrag:源對(duì)象被拖動(dòng)過程中(鼠標(biāo)可能在移動(dòng)也可能未移動(dòng))
(3)ondragend:源對(duì)象被拖動(dòng)結(jié)束
拖動(dòng)源對(duì)象可以進(jìn)入到上方的目標(biāo)對(duì)象可以觸發(fā)的事件有:
(1)ondragenter:目標(biāo)對(duì)象被源對(duì)象拖動(dòng)著進(jìn)入
(2)ondragover:目標(biāo)對(duì)象被源對(duì)象拖動(dòng)著懸停在上方
(3)ondragleave:源對(duì)象拖動(dòng)著離開了目標(biāo)對(duì)象
(4)ondrop:源對(duì)象拖動(dòng)著在目標(biāo)對(duì)象上方釋放/松手
添加事件的方式也多種多樣,以ondrop為例:
HTML 中:<element ondrop="myScript">
JavaScript 中:object.ondrop=function(){myScript};
JavaScript 中, 使用 addEventListener() 方法:object.addEventListener("drop",myScript);
現(xiàn)在假定一種新情形:綠色方塊只能拖拽到虛線框之內(nèi)。

默認(rèn)情況下,Html元素均不可拖拽,所以需要設(shè)置拖拽元素的draggable屬性為true。同時(shí),默認(rèn)無法將元素放置到其他元素中,所以需要event.preventDefault()設(shè)置允許放置。
本例中,為class為abox的綠方塊添加draggable。
為class為wrap的虛線框添加preventDefault。
之后通過dataTransfer傳輸數(shù)據(jù),實(shí)現(xiàn)box的移動(dòng)。
$("#drag").click(function(){
alert('now you can drag');
$(".abox").attr('draggable',true); //設(shè)置可拖動(dòng)
boxes=$(".abox")
for(i=0;i<boxes.length;i++){ //循環(huán)使每個(gè)box均可拖動(dòng)
boxes[i].ondragstart = function(event) {
event.dataTransfer.setData("Text",event.target.id); //存儲(chǔ)
console.log('drag', event.target.id)
}
}
document.ondragover = function(event) {
if(event.target.className == "awrap"){
event.preventDefault(); //設(shè)置可放置
}
}
document.ondrop = function(event) {
if(event.target.className == "awrap"){
event.preventDefault();
var data=event.dataTransfer.getData("Text");
console.log('data', data);
event.target.appendChild(document.getElementById(data)); //放置
}
}
});

Svg
由于Svg也是直接在Html中生成Dom節(jié)點(diǎn),理論上Div所能實(shí)現(xiàn)的功能它都可以實(shí)現(xiàn),并且繪圖效果更佳。
- 初始化Svg
同Div的生成方式類似,Svg需要先生成個(gè)畫布,并定義長寬。
var svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
svg.setAttribute("height",'100%');
svg.setAttribute("width",'100%');
然后生成Svg元素,并設(shè)置其屬性。
for (var i = 0; i < Count; i++) {
var rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
rect.setAttribute( .... );
svg.appendChild(rect);
}
- 應(yīng)用插件拖拽
svg.draggable.js是個(gè)跟Jquery ui draggable差不多的插件。也是引入之后加個(gè)draggable屬性即可隨意拖拽。
<script type="text/javascript" src="svg.min.js"></script>
<script type="text/javascript" src="svg.draggable.js"></script>
循環(huán)生成Svg方塊并添加拖拽屬性。
var svg = SVG('boxdetail').size(1000, 400);
for (var i = 0; i < n; i++) {
var rect = svg.rect(80, 20);
rect.x(100 * i);
rect.fill('#e1fbdd');
rect.id('box'+i);
rect.draggable({ //設(shè)定只能在畫布大小內(nèi)拖動(dòng)
minX: 0,
minY: 0,
maxX: 1000,
maxY: 400
});
}
但這種方式有個(gè)問題,就是——慢。當(dāng)生成僅10000個(gè)方塊時(shí),效率便低的不可估量。
然而Svg無法應(yīng)用Html5原生的Drag and Drop事件。
- 鼠標(biāo)事件觸發(fā)拖拽
一種基本方法,是利用鼠標(biāo)的mousedown, mousemove, mouseup事件檢測鼠標(biāo)點(diǎn)擊、移動(dòng)、松開,并記錄鼠標(biāo)坐標(biāo)位置,從而改變被拖拽元素坐標(biāo)。
$("#drag").click(function(){
alert('now you can drag');
var selectedElement = null;
var currentX = 0;
var currentY = 0;
$("#mysvg > rect").mousedown(function (e) {
// 選中拖拽元素,記錄原始坐標(biāo)
currentX = e.clientX;
currentY = e.clientY;
selectedElement = e.target;
}).mousemove(function (e) {
// 更新坐標(biāo)
if (selectedElement) {
var dx = parseInt(selectedElement.getAttribute("x")) + e.clientX - currentX;
var dy = parseInt(selectedElement.getAttribute("y")) + e.clientY - currentY;
currentX = e.clientX;
currentY = e.clientY;
selectedElement.setAttribute("x", dx);
selectedElement.setAttribute("y", dy);
}
}).mouseup(function (e) {
// 取消元素選中狀態(tài)
selectedElement = null;
});
});
相比于引用插件,這樣的效率提高了不少。

Div與Svg效率對(duì)比
從結(jié)果上看,在數(shù)量少時(shí),針對(duì)方塊這種簡單圖形的簡單操作Div和Svg均可勝任。然而設(shè)置了總共生成100000個(gè)方塊,發(fā)現(xiàn)單從生成的角度,Svg的渲染用時(shí)大約是Div的1/2(這里指Dom中直接繪制Svg而非通過js插件繪制Svg)。
加上拖拽功能后,用Html5原生拖放事件的Div,及用鼠標(biāo)事件的Svg,明顯快快快快于應(yīng)用js插件拖放的效率。于是乎插件雖然強(qiáng)大但對(duì)于大量節(jié)點(diǎn)的處理實(shí)在過于緩慢。
于是在圖形化上還是應(yīng)用Svg更舒暢一些。
但有一個(gè)尚未解決的問題。
應(yīng)用鼠標(biāo)事件拖動(dòng)Svg,當(dāng)鼠標(biāo)移動(dòng)過快時(shí),mousemove事件無法觸發(fā),導(dǎo)致移動(dòng)效果不能實(shí)現(xiàn)。粗略查了下似乎可以添加透明背景層接收所有觸發(fā)事件,不過還沒有深入研究。
這篇就到此吧。等解決了mousemove的bug再更新后續(xù)。
(′?ω?`)