前言:做爲一名Web開發者,可能你並無對這個「H5」這個字眼投入太多的關注,但實際上它早已不知不覺進入到你的開發中,而且總有一天會讓你不得不正視它,瞭解它並運用它
打個比方:《海賊王》中的主角路飛在「頂上戰爭兩年前」,會在一些危急關頭「不經意」地使用霸王色霸氣,但對」霸氣「的結構體系和具體運用都不太瞭解,這讓他在香波地羣島等諸多重大戰役中大吃苦頭。此後, 他不惜花費兩年時間跟隨雷利修煉霸氣。由於,若是不去了解這個嶄新的戰鬥方法的話,他們在殘酷的新世界一天也生存不了。
爲何學習HTML5?
咳咳, 回到主題,爲何咱們要學HTML5呢?
1.瞭解HTML5的囊括範圍的一大好處是:當你不當心使用了一個H5的東東的時候(例如你試圖經過百度找到的答案解決一個緊張的需求),你會很及時的關注它的兼容性
2.H5有些新增的特性也許你從沒接觸過,也感受無需用到它。但就在不久的未來,你可能就會用到,甚至依賴於它(畢竟這就是HTML的將來)
H5中的知識點分佈
在下面, 我將學習H5中的知識點分紅兩類:主要知識點和針對特定功能的知識點,其中對主要知識點的部分,從學習成本的角度對其進行了難度分級
(僅屬我的觀點!若有改進意見,歡迎討論)
一.主要知識點
(從需求層面上來講,普及範圍相對較廣)
相對容易的部分:
1.在線和離線事件(Online/Offline) (相對容易)
2. 衆多的新增元素 如<output>, <progress>等 (相對容易)
3. history關於歷史狀態管理的API (相對容易)
4 Storage(localStorage和sessionStorage) (相對容易)
相對較難的部分:
5. Web Worker (相對較難)
6. canvas (相對較難)
7. indexedDB (相對較難)
8. 拖放操做 (相對較難)
9. Web Sockets (相對較難)
二. 針對特定功能的知識點
(對需求來講,主要針對某一方面的特殊需求場景)
1. 對音視頻的支持
2. Camera API (操做攝像頭)
3. WebGL (3D圖像)
4. 地理位置定位 (geolocation對象)
本文主要講述H5中主要知識點中,學習成本相對較高的四個點(僅我的觀點):
一.Web Worker
二.canvas
三.indexedDB
四.拖放操做
【注意】由於下面介紹的H5的特性在一些比較老的瀏覽器裏可能遇到兼容性問題,因此你在使用前必需要能力檢測,例如這樣
if(window.Worker){
// 使用Worker
}
Web Worker
Web Worker的機制讓你可以建立一個在後臺線程運行的腳本,這個腳本不會對咱們當前執行任務的腳本形成任何干擾(例如阻塞),同時Web Worker提供了一套API使你可以在當前腳本和後臺腳本間進行數據的互相傳輸(worker)
「一套API, 兩個對象」
咱們如今已知的關於Web Worker的機制是: 有一個當前腳本, 和一個在後臺運行的worker腳本,因此咱們問題的關鍵就落在了這兩個腳本的通訊(數據交互)上
經過
var worker = new Worker("./worker.js");
生成了「兩個對象」(你可能會問:爲何是兩個不是一個呢?請往下看)javascript
「第一個」對象是咱們在當前腳本中經過構造函數顯式建立出來的worker對象,它擁有一套API:postMessage和onmessage,經過postMessage方法能夠向worker腳本(上文worker.js)發送數據, 經過onmessage方法能夠從worker腳本接收數據
「第二個」對象是在Web Worker腳本(上文的worker.js)中隱式建立出來的全局變量對象,它叫DedicatedWorkerGlobalScope(這個時候在work.js全局變量對象是它而不是Window!!),而它也擁有一套API:postMessage和onmessage,經過postMessage方法能夠向當前執行任務的腳本發送數據, 經過onmessage方法能夠從當前執行任務的腳本接收數據
【注意】關於DedicatedWorkerGlobalScope
1. 它是在Web Worker腳本中生成的特殊的全局變量對象,也就是在全局執行環境中使用this指向的不是Window而是它
2. 它不能像Windows那樣經過變量名直接訪問,但在Web Worker腳本中你能經過this取到它
因此如今數據傳遞方向有兩條:
1. 調用當前腳本中worker對象的postMessage方法, 而後在Web Worker腳本(上文的worker.js)中經過onmessage這個回調方法接收數據
2. 調用Web Worker腳本中的this.postMessage方法(this指向DedicatedWorkerGlobalScope),而後在當前腳本中worker對象的onmessage回調方法接收數據
看到這裏可能有點懵,來讓咱們經過一個例子看看1中的數據傳遞:
先看示例吧,這是咱們的目錄結構
├─worker.js
├─main.js
└─index.html
index.html:
<html>
<head>
<meta charset="utf-8" />
<button id="work-button">傳遞數據</button>
</head>
<body>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
main.js:
var button = document.querySelector("#work-button");
if(window.Worker){
var worker = new Worker("./worker.js");
button.onclick = function () {
worker.postMessage("你好,我是當前腳本");
}
}
worker.js:
this.onmessage = function (e) {
console.log('work接收到的數據爲:', e.data);
}
點擊按鈕後,在main.js中調用worker對象的postMessage方法, 這個數據就被髮給了work.js中的全局變量對象DedicatedWorkerGlobalScope, 因此咱們在work,js中經過this.onmessage接收數據並輸出
postMessage中參數傳遞給onmessage中event.data
【注意】postMessage傳遞的參數會被「原封不動」地傳遞給onmessage中event對象的data屬性
例如:
postMessage([1,2,3]) ——> this.onmessage = function (e) { } 中 e.data === [1,2,3]
postMessage({a:1,b: 2}) ——> this.onmessage = function (e) { } 中 e.data === {a:1,b: 2}
當前任務腳本和worker腳本完整的通訊流程
咱們上面的例子展示的是從當前任務腳本向worker腳本傳遞數據,那麼一樣的道理,咱們也能從work腳本向當前任務腳本傳遞數據(方式相同)
例子:
├─worker.js
├─main.js
└─index.html
index.html:
var button = document.querySelector("#work-button");
if(window.Worker){
var worker = new Worker("./worker.js");
button.onclick = function () {
worker.postMessage("你好,我是當前腳本");
worker.onmessage = function (e) {
console.log('當前腳本接收到的數據:',e.data)
}
}
}
worker.js:
this.onmessage = function (e) {
console.log('work接收到的數據爲:', e.data);
this.postMessage("你好,我是worker發來的數據")
}
demo以下
點擊傳遞數據輸出:
canvas
cancas是H5新增的一個標籤,把canvas翻譯過來就是畫布,顧名思義,這是用來」畫畫的「,那畫畫的」畫筆「是什麼呢? 它就是和canvas元素對象對應的一個」上下文對象「(context),這裏的這個上下文對象可能和你印象中的」上下文「有較大的差別,它只是個單純的包含了一系列「繪畫」方法的對象,下面咱們介紹的關於canvas的內容都要圍繞這個"canvas上下文對象"展開
咱們能夠經過這種方式取得canvas上下文對象:
假設這是咱們的HTML:
<canvas id="canvas"></canvas>
這樣取得上下文對象:java
let canvas = document.getElementById("canvas"); // 首先取得canvas元素對象
let ctx = canvas.getContext("2d"); //經過getContext()取得關鍵的上下文對象,2d表示畫布是「平面」的
繪製基本形狀
下面展示的是上下文對象的一些繪製圖形的方法(它們均可以被ctx調用)
fillRect(x, y, width, height) // 繪製一個填充的矩形
strokeRect(x, y, width, height) // 繪製一個矩形的邊框
上面的x,y表明相對於canvas畫布左上角的橫縱座標:
例子:
html部分:
<canvas id="canvas" width="200px" height="100px">
你的瀏覽器不支持canvas
</canvas>
JS部分:
let canvas = document.getElementById("canvas");
if(canvas.getContext){
let ctx = canvas.getContext("2d");
ctx.fillRect(20,20,40,40); // 繪製矩形
}
【注意】. canvas標籤內的內容(例如上面的文本)是否呈現取決於瀏覽器是否支持canvas,若是支持,則不出現,若是不支持,則會呈現出來
demo:
給畫筆添加顏色和樣式
咱們以上面的爲基礎稍做修改:
let ctx = canvas.getContext("2d");
ctx.fillStyle = "#0081F0"; // 給上下文對象這支畫筆添加填充顏色
ctx.fillRect(20,20,40,40);
demo:
繪製文本
let canvas = document.getElementById("canvas");
if(canvas.getContext){
let ctx = canvas.getContext("2d");
ctx.font = "26px serif"; // 設置文字大小和樣式
ctx.fillText("外婆的",20,20); // 「實心」的文本
ctx.strokeText("彭湖灣",20,60); // 「空心」的文本
}
demo:
這裏要稍微提一下, 也許上面的那些繪製圖形,繪製文本的操做對你來講都沒有觸動,由於它們離咱們的直接需求彷佛還有必定的距離,但我想接下來的這幾個上下文API你或許有些興趣。
例如咱們可能有一個需求是載入已有的圖片,對它截圖(裁剪)後保存爲一張新的圖片,這個時候咱們就可使用到canvas的繪製圖片,裁剪圖片,保存圖片的API了
直接繪製已有圖片
經過canvas上下文對象的drawImage方法可直接繪製圖片
drawImage(image, x, y) // 其中 image 是 image 或者 canvas 對象
咱們能夠經過下面的一段代碼動態獲取img元素對象
let img = new Image();
img.onload = function () {
// 運行這個函數的時候能夠確保img已經被加載好了
};
img.src = "./beach.jpg" // 指定src後圖片開始加載
廢話很少說,直接上demo!
在相同目錄下有這麼一張圖片
JS代碼:
let canvas = document.getElementById("canvas");
let img = new Image();
img.onload = function () {
if(canvas.getContext){
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0)
}
};
img.src = "./beach.jpg"
demo:
咱們發現, 圖片加載完成後被寫入了畫布當中!
圖片裁剪功能
canvas上下文對象的clip方法可根據路徑對canvas畫布進行裁剪
讓咱們在原來的基礎上添加一點東西:
let canvas = document.getElementById("canvas");
let img = new Image();
img.onload = function () {
if(canvas.getContext){
let ctx = canvas.getContext("2d");
ctx.beginPath(); // 開始繪製路徑
ctx.arc(100,100,100,0,Math.PI*2,true); // 繪製一個起點(100,100),半徑爲100的圓
ctx.clip(); // 裁剪
ctx.drawImage(img, 0, 0); // 畫圖
}
};
img.src = "./beach.jpg"
【注意】clip方法的調用要在drawImage方法以前,不然不能成功! 也就是說要「先裁剪,再畫圖」
canvas的保存和導出
咱們經過document.getElementById("canvas")取得的畫布對象,有一個toDataURL()方法,可將當前畫布做爲一張圖片,並返回其base64編碼格式的數據,這在保存圖片的時候很是有用
toDataURL接受兩個參數:圖片類型和質量參數
canvas.toDataURL(圖片類型,質量參數)
canvas.toDataURL() // 默認返回的是png圖片
canvas.toDataURL('image/jpeg') // 返回jpeg圖片
canvas.toDataURL('image/jpeg', quality) // 建立一個JPG圖片。你能夠有選擇地提供從0到1的品質量,1表示最好品質
看下面的例子
let canvas = document.getElementById("canvas");
let img = new Image();
img.onload = function () {
if(canvas.getContext){
let ctx = canvas.getContext("2d");
ctx.beginPath(); // 開始繪製路徑
ctx.arc(100,100,100,0,Math.PI*2,true); // 繪製一個起點爲(100,100),半徑爲100的圓
ctx.clip(); // 裁剪
ctx.drawImage(img, 0, 0); // 畫圖
let src = canvas.toDataURL('image/png')
console.log(src);
}
};
img.src = "./beach.jpg"
控制檯輸出了base64格式的數據:
咱們經過網上的還原軟件看看會把這個base64數據還原成什麼圖片:
正是咱們想要的圖片
indexedDB — — H5的「瀏覽器數據庫」
indexedDB是存在於瀏覽器中的數據庫,它和通常的數據庫同樣有寫改刪查的功能,不一樣之處在於:常見的數據庫通常是在服務器上,而且要求咱們的應用在線時才能夠工做,而indexedDB使得在離線的時候讀取數據成爲了可能。下面,我就給你們介紹一下這個「駐紮」在瀏覽器上的特殊的數據庫吧
使用open方法建立/打開數據庫
咱們首先要作的事情,固然是建立(或打開)一個數據庫,這要用到indexedDB對象的open方法
它接收兩個參數: 數據庫名稱和數據庫版本(第二個參數是可選的)
indexedDB.open([ 數據庫名稱 ], [數據庫版本])
調用open方法時候,若是對應名稱的數據庫不存在,則建立一個新的數據庫,若是已存在,則打開已存在的那個數據庫
須要說明的是, indexedDB裏面絕大多數操做都是異步的, 上述的indexedDB.open並不會當即建立一個數據庫, 你須要在異步的回調裏面判斷數據庫是否建立成功,並對可能出現的錯誤作判斷和處理
只有在onsuccess回調中,你才能經過request.result取得建立成功的數據庫
var request = indexedDB.open("XXX", 1);
request.onsuccess = function () {
// request.result === 你經過open建立的數據庫
}
經過open返回的request對象有三個回調:
onsuccess 每次建立/打開數據庫時候都會調用
onerror 建立/打開數據庫發生錯誤的時候調用
onupgradeneeded 數據庫版本變化的時候調用 (onupgradeneeded 是咱們惟一能夠修改數據庫結構的地方)
open一個indexedDB數據庫後,通常在onupgradeneeded回調中初始化(或修改)數據庫結構(劃重點!!)
這包括兩個方面的操做:
1. 經過db.createObjectStore建立對象存儲空間,並取得ObjectStore對象(相似於SQL數據庫中的建表操做)
2. 經過調用ObjectStore.createIndex建立該存儲空間內的索引( 以便於提升查詢時候的速度)
具體的可看下面的例子:
<script type="text/javascript">
if(!window.indexedDB) {
alert("你的瀏覽器還不能支持indexedDB哦!")
}
var request = indexedDB.open("phwDataBase", 1);
var db
request.onsuccess = function () {
// 將成功建立的數據庫對象賦給db
db = request.result;
}
request.onerror = function () {
var errorDescribe = request.errorCode;
// 打印錯誤
console.log(errorDescribe);
}
request.onupgradeneeded = function (e){
// 取得更新後的數據庫對象, 並賦給db
db = request.result;
// 建立名爲people數據存儲空間, 第二個參數裏的keyPath至關於「主鍵」
var objectStore = db.createObjectStore("people", { keyPath: "id" });
// 建立索引, 加快查詢速度
objectStore.createIndex("name", "name", {unique: false});
}
</script>
運行一下, 而後讓咱們看看效果:
打開chrome的Application面板,點擊左欄的Storage下的indexedDB使其展開
就能夠看到咱們新建立的phwDataBase數據庫, 以及它內部的people數據存儲空間了
(右邊展現的是people數據存儲空間的具體內容,由於如今什麼數據都還沒添加,因此key和value兩列下是沒有內容的)
看了上面的代碼你可能會有些疑惑
onupgradeneeded 和onsuccess回調的關係是怎樣的? 爲何咱們必須在.onupgradeneeded中初始化數據庫的結構,而不是在onsuccess中?
這主要是由兩個回調調用的時機決定的:
1.對 onsuccess回調,在每次數據庫建立/打開的時候都會調用(不只是第一次建立的時候會調用,每次打開的時候也都會調用)
2. 對onupgradeneeded回調,在open提供第二個版本參數的前提下:
2.1 第一次調用open方法建立一個新的數據庫的時候,onupgradeneeded必定會被調用
2.2 第二次或之後open該數據庫,只在版本參數改變的時候, onupgradeneeded纔會被調用
【注意】在缺乏第二個版本參數的狀況下,onupgradeneeded永遠不會被調用!!
因此說:
1.open數據庫的時候,最(yi)好(ding)要帶上第二個參數(版本參數)
2. 修改數據庫結構(例如建立和刪除對象存儲空間以及構建和刪除索引。)要在onupgradeneeded回調中運行
(很顯然每次打開都會被調用的onsuccess並不適合用於初始化數據庫結構)
indexedDB的具體操做
首先說一下,在下面的展現例子中,咱們的HTML是這樣的
<button id="add-button">增長數據</button>
<button id="delete-button">刪除數據</button>
<button id="get-button">獲取數據</button>
<button id="show-all-button">遍歷所有數據</button>
<button id="index-button">經過索引獲取數據</button>
demo:
這裏要說明一下的
是,indexedDB的操做是以事務爲基礎的。 因此,對存儲空間(objectStore)的操做都要基於事務來進行。 具體點說,就是須要先經過db.transaction()方法取得transaction對象,而後再經過 transaction.objectStore()方法取得目標objectStore,再而後才能調用objectStore的API進行「寫改刪查」
打個比方, 若是說咱們存儲的數據是糧食的話, objectStore就是一個個並排的糧倉,你能夠往裏面運糧食,也能夠把糧食運出去, 但你對「糧食」作任何行爲前, 都要和糧倉門前的守衛—— transaction(事務)「打聲招呼」,獲得准許才能進入糧倉
有兩個方法要說一下
1. transaction方法
transaction 方法 通常接受兩個參數,並返回一個事務對象。
1.1第一個參數是一個數組, 一個咱們但願事務可以操做的objectStore所組成的數組,若是你但願這個事務可以操做全部的objectStore,那麼傳入空數組[]便可
1.2 第二個參數是一個字符串, 默認是「onlyread」, 若是咱們有須要對數據進行寫操做的需求的話可傳入「readwrite」
例如咱們下面的一行代碼:
var transaction = db.transaction(["people"],"readwrite");
2. transaction.objectStore方法
這個方法接受一個參數: 指定的objectStore的名稱, 方法返回的是獲取到的objectStore
例如咱們下面的一行代碼:
var objectStore = transaction.objectStore("people");
寫操做
寫操做的關鍵在於objectStore.add(XXX);方法,其中XXX是咱們初始化objectStore時候寫入的「主鍵」
也就是 var objectStore = db.createObjectStore("people", { keyPath: "id" }); (這段代碼在上面)中keyPath的值——id
function addData () {
// 確保這個時候異步的open方法已經完成,並取得數據庫對象
if(!db){
return;
}
// 咱們要寫入的數據
var data = [
{id: '1',name:'a', age: 10},
{id: '2',name:'b', age: 20},
{id: '3',name:'c', age: 30}
];
// 建立事務,並使其可讀可寫
var transaction = db.transaction(["people"],"readwrite");
transaction.oncomplete = function () {
alert("添加事務已經完成")
}
transaction.onerror = function () {
alert("出現錯誤")
}
// 經過事務對象取得people存儲空間
var objectStore = transaction.objectStore("people");
for(let d of data) {
// 調用add方法添加數據
objectStore.add(d);
}
}
var addButton = document.getElementById("add-button");
addButton.onclick = addData
demo:
點擊「增長數據」後彈出
再看看application面板下的indexedDB:
咱們已經成功添加了三條數據進去了
刪操做
刪操做的關鍵在於objectStore.delete(XXX);方法,其中XXX是咱們初始化objectStore時候寫入的「主鍵」
也就是 var objectStore = db.createObjectStore("people", { keyPath: "id" }); 中keyPath的值——id
function deleteData () {
if(!db){
return;
}
var transaction = db.transaction(["people"],"readwrite");
var objectStore = transaction.objectStore("people");
objectStore.delete("1");
transaction.oncomplete = function () {
alert("刪除事務已經完成")
}
}
var deleteButton = document.getElementById("delete-button");
deleteButton.onclick = deleteData;
點擊上面的「刪除數據」按鈕(刪除id = 1的數據)
再來看看, id爲1的那一行已經被刪除了
查操做
刪操做的關鍵在於objectStore.get(XXX);方法
function getData () {
if(!db){
return;
}
var transaction = db.transaction(["people"], "readwrite");
var objectStore = transaction.objectStore("people");
var request = objectStore.get("2");
request.onsuccess = function () {
alert(JSON.stringify(request.result));
}
}
var getButton = document.getElementById("get-button");
getButton.onclick = getData;
demo:
點擊「獲取數據」按鈕,彈出
(這裏固定查找id爲2的數據)
遍歷所有數據
遍歷數據須要用到遊標
經過 objectStore.openCursor()可建立一個遊標對象(cursor), 這個cursor對象包含兩個屬性值: key和value
key就是咱們一直說的那個「主鍵」, 而value是咱們存入的時候的那個對象,經過 cursor.continue方法可使得遊標向下移動
function showAllData () {
if(!db){
return;
}
var transaction = db.transaction(["people"], "readwrite");
var objectStore = transaction.objectStore("people");
console.log("遍歷開始")
objectStore.openCursor().onsuccess = function (event) {
var cursor = event.target.result;
if(cursor) {
console.log(cursor.key, cursor.value);
cursor.continue();
}
}
}
var showAllButton = document.getElementById("show-all-button");
showAllButton.onclick = showAllData;
點擊「遍歷所有數據」按鈕,看看控制檯
經過索引查找
咱們經過objectStore.get方法,經過查找主鍵的方式查找對應的對象數據的方式是很快的。
但若是咱們經過非主鍵的數據去查找對應的那個對象就很是慢了,這個時候咱們須要建立一個索引並經過索引來查找, 從而得到較快的速度:
function getByIndex () {
if(!db){
return;
}
var transaction = db.transaction(["people"], "readwrite");
var objectStore = transaction.objectStore("people");
var index = objectStore.index("name");
var request = index.get("c");
request.onsuccess = function (event) {
alert(JSON.stringify(request.result));
}
}
var indexButton = document.getElementById("index-button");
indexButton.onclick = getByIndex;
點擊「經過索引獲取數據」按鈕:
好! 如今讓咱們對indexedDB作一個小小的總結:
1. indexedDB是面向對象的, 與傳統的以二維表爲基礎的數據庫不一樣
2. IndexedDB是一個事務型數據庫系統
3. indexedDB大多數API都是異步的,這意味着調用一個方法你不能立刻獲得關鍵的那個對象,而在對應的success回調中才能取得
拖放事件
一個典型的拖放操做是這樣開始的:用戶用鼠標選中一個可拖動的(draggable)元素,移動鼠標到一個可放置的(droppable)元素,而後釋放鼠標。 在操做期間,會觸發一系列的拖放類型的事件
其中咱們主要關心的事件有三個:
1. ondragstart 發生在可拖拽(draggable)的元素上, 在元素被拖動的時候調用
2. ondragover 發生在可放置(droppable)的元素上, 當某被拖動的對象在可放置對象範圍內(上方)時觸發此事件
3. ondrop 發生在可放置(droppable)的元素上,當釋放鼠標使可拖拽元素「放進」可放置元素內的瞬間觸發。
需思考的問題:
1. 如何使得被拖拽元素可拖拽?(由於元素默認的行爲是不可拖拽的),以及如何使得被放置的容器元素可放置? (由於元素默認是不可放置的)
對前者, 咱們能夠爲元素設置draggable屬性,而且設置爲true
對後者, 咱們能夠在被放置的容器元素中的ondragover事件裏經過event.preventDefault();阻止默認行爲——禁止放置
2.如何實現「脫 — 放」過程的數據傳遞?
這裏首先須要知道的是,當咱們拖動一個圖片到另外一個地方的時候,咱們是不能「直接把圖片拖拽進去」的,也就是說,咱們仍是要經過如下的思路實現拖放:
在被放置的元素中取得被拖拽元素的相關數據(如id),而後經過appendChild之類的API實現添加被拖拽的元素,從而模擬整個拖拽的過程
也就是說, 拖拽其實可分爲三個過程: 拖動—傳遞被拖動元素的數據(如id)—在容器元素中添加該元素
關鍵在於如何在被拖動元素和被放置元素中傳遞數據,這能夠經過event.dataTransfer對象來實現
dataTransfer能夠經過setData方法添加拖動數據,並經過getDate方法取得拖動數據,咱們能夠在
ondragstart事件和ondrop事件中調用這兩個方法, 實現關鍵性的數據傳遞。
具體請看下面的例子:
<div>
<img
id = "myImg"
src="./clock.jpg"
draggable="true"
ondragstart="dragstart(event)"
/>
</div>
<div
id="targetDiv"
ondragover="dragover(event)"
ondrop="drop(event)">
</div>
<script type="text/javascript">
function dragstart (event) {
event.dataTransfer.setData("text/plain", event.target.id)
}
function drop (event) {
// 阻止默認行爲——禁止在瀏覽器中打開新的連接
event.preventDefault();
var imgId = event.dataTransfer.getData("text/plain");
var targetDiv = document.getElementById("targetDiv");
targetDiv.appendChild(document.getElementById(imgId));
}
function dragover (event) {
// 組織默認行爲——禁止放置
event.preventDefault();
}
</script>
拖拽前
拖拽後
參考資料:
【完】