JS学习21(离线应用与客户端储存)
2016-06-01 23:16
411 查看
Web应用与传统客户端最大的区别就是需要连接网络,没有网络整个应用就无法运行,这个一直是Web应用最大的痛点之一。
HTML5为了解决这个问题添加了对离线应用的支持。开发离线Web应用有几个关键点。确保应用知道设备是否能上网以便下一步执行正确的操作,然后应用还必须能访问一定的资源。最后必须有一块本地空间用于保存数据,无论是否能上网都能读写数据。
还有两个事件:online和offline,这两个事件会在网络状态变化时在window对象上触发。
支持离线检测的浏览器有IE6+、navigator.onLine、Firefox3、Safari4、Opera10.6、Chrome、iOS 3.2版Safari、Android 版 WebKit。
这里仅简单的列出了要下载的文件。描述文件的选项非常多,想要进一步了解的话给大家一个网址咯。Go offline with application cache
要将描述文件与页面关联起来,可以使用,下面的属性。
同时,有相应的JS API提供给开发者来获取其状态。
这个API的核心是applicationCache对象,这个对象有一个status属性,表示应用缓存的状态:
0:无缓存
1:闲置,应用缓存未更新
2:检查中,正在下载描述文件并检查更新
3:下载中,应用缓存正在下载描述文件中指定的资源
4:更新完成,应用缓存已经更新了资源,且所有资源下载完毕,可以通过swapCache()来使用了
5:废弃,应用缓存的描述文件已经不存在了,页面无法再访问应用缓存
同时针对上面的状态也有相应的事件:
checking,查找更新时触发
error,检查更新或下载资源期间发生错误时触发
noupdate,检查描述文件发现没有更新时触发
downloading,开始下载资源时触发
progress,下载的过程中不断触发
updateready,下载完毕且可以使用swapCache()时触发
cached,应用缓存完整可用时触发
在页面刚刚加载时,会自动检查有没有描述文件是否更新,并根据具体情况触发上述事件。
有用的方法有两个:
update(),会去检查描述文件是否更新,就像页面刚刚加载那样,并触发相应的事件
swapCache(),在新缓存可用时可以调用这个方法来启用新应用缓存
这里就设置了一个以name为名称,value为值的一个cookie。
浏览器会储存这样的会话信息。并在这之后通过为每一个请求添加Cookie HTTP头部将信息发送回服务器:
这个信息对于服务器来说就可以唯一验证请求的身份
限制
cookie在性质上是绑定在特定域名下的。当设定了一个cookie,再给创建它的域名发送请求时都会包含这个cookie,而发向其他域的请求中并不会包含这个cookie。这个限制保证了cookie只能让批准的接受者访问。
每个域的cookie总数是有限的,各浏览器不同,最小的规定一个域有30个cookie,大小一般不超过4095B。
cookie的构成
cookie由浏览器保存的一下几块信息构成:
名称:一个唯一确定cookie的名称
值:储存在cookie中的字符串值
域:这个cookie对哪个域有效,如果这个域包含子域,那对子域同样有有效。如果设定是没有明确指定,这个值会被认为是设置cookie的那个域
路径:用于指定向域中的哪个路径发送cookie,例如,你可以指定cookie只发送到www.baidu.com/img,那再访问www.baidu.com时就不会发送cookie。及时它们同域
失效时间:cookie应该被删除的时间戳,默认浏览器会话结束就删除
安全标志:指定后,cookie只有在使用SSL连接时才会发送到服务器
设置时像这样:
JS中的cookie
JS中访问cookie的接口是BOM的document.cookie。
获取时,这个属性返回字符串,包括当前页面可用的(根据cookie的域,路径,失效时间等等)所有cookie的名称和值组成的键值对。
这些是经过URI编码的值。要使用decodeURIComponent()解码。
设置时和使用HTTP头部设置一样:
由于在JS中使用cookie不是很直观,写个工具类比较好。
子cookie
因为cookie有单域名下数量的限制,一些开发人员使用了一种称为子cookie的概念,子cookie是存放在单个cookie中更小段的数据,也就是使用cookie值来储存多个名值对。最常见的格式如下:
这样对于获取到cookie就又多了一层障碍,为了更好的操纵cookie当然要在来个工具类咯~
JS不能访问的cookie
为了保证cookie的安全,有的cookie会不允许JS获取
提供一种在cookie之外储存会话数据的途径
提供一种储存大量可以夸会话存在的数据的机制
Storage类型
可以以名值对的方式储存字符串值,有如下方法:
clear()
getItem(name)
key(index)
removeItem(name)
setItem(name, value)
sessionStorage对象
这个对象储存特定于某个会话的数据,这也就意味着这里的数据只保存到浏览器关闭。不过刷新页面时这里的数据是可以存住的。存在这里的数据只能由最初储存数据的页面访问。sessionStorage 其实是Storage的一个实例,所以上面的方法同样可用。
globalStorage对象
这个对象的目的是跨会话存储数据,但是有域的限制,在储存数据时,首先要指定的就是域:
globalStorage不是Storage的实例globalStorage[“wrox.com”]才是哦。
这个域名下的所有子域可以访问这里的数据。
对每个空间访问的限制是根据域名,协议,端口来限制的,同一个域名。使用HTTP访问就访问不到HTTPS时存的数据。端口号不同也是一样。
localStorage对象
这个对象是为了取代globalStorage而存在的。这个也是跨会话的,不需要指定域名,只有完全相同的域名才能访问,子域名都不行。
Storage事件
对storage对象进行的任何修改都会触发storage事件,这个事件的event有如下属性:
domain
key
newValue
oldValue
各浏览器对这个事件的支持并不全面
大小限制
各浏览器对Storage大小的限制并不相同,不过都是根据域名来区分的。
IndexedDB设计的操作完全是异步进行的。每次对数据库的操作都会返回一个相应的IDBRequest对象的实例来代表这次请求。在这个实例上可以设置事件,等待成功或失败事件被触发,在里面做相应的操作。IndexedDB是全局对象。API不稳定,有的浏览器为其加了前缀。
数据库
打开数据库,把数据库名传入,存在会打开,不存在会创建并打开。打开的数据库的请求是一个IDBRequest对象,通过事件来观察请求是否成功。成功就会返回一个IDBDatabase对象。
可以给database设置一个版本号,同样是返回一个IDBRequest,同样的操作模式。
对象储存空间
建立了与数据库的连接后,就可以使用对象储存空间(相当于表,其中的对象相当于表中的纪录)。
创建对象储存空间,需要两个信息,这个空间的名字,以及Key Path和Key Generator,这两个值确定了这个空间中储存的每个记录以什么来标识。
No No:This object store can hold any kind of value, even primitive values like numbers and strings. You must supply a separate key argument whenever you want to add a new value.
Yes No:This object store can only hold JavaScript objects. The objects must have a property with the same name as the key path.
No Yes:This object store can hold any kind of value. The key is generated for you automatically, or you can supply a separate key argument if you want to use a specific key.
Yes Yes:This object store can only hold JavaScript objects. Usually a key is generated and the value of the generated key is stored in the object in a property with the same name as the key path. However, if such a property already exists, the value of that property is used as key rather than generating a new key.
创建空间需要在打开数据库时返回的IDBRequest上的onupgradeneeded事件中进行,否则会报错的。这个事件会在新创建数据库或更新数据库版本号时(open()时传入更高的版本号,数据库的版本就会自己更新)被触发。
在创建了空间后,就可以用add()或put()来向其中添加要保存的对象。对于添加唯一标识已经存在的对象,add会报错,put则会更新原有值。
事务
在创建好空间后,就相当于数据库的结构已经确定了,在这之后对数据的操作就要通过事务了。
创建事务需要指定针对哪个储存空间以及读写模式,可以一下打开多个储存空间。
取得事务索引后使用objectStore()访问储存空间,然后就可以使用add()、put()、get()、delete()、clear()。这五个方法都会返回一个请求对象,通过事件来操作
HTML5为了解决这个问题添加了对离线应用的支持。开发离线Web应用有几个关键点。确保应用知道设备是否能上网以便下一步执行正确的操作,然后应用还必须能访问一定的资源。最后必须有一块本地空间用于保存数据,无论是否能上网都能读写数据。
离线检测
为检测设备是离线还是在线,HTML5定义了navigator.onLine这个属性值为true表示设备可以上网。还有两个事件:online和offline,这两个事件会在网络状态变化时在window对象上触发。
EventUtil.addHandler(window, "online", function(){ alert("online"); }); EventUtil.addHandler(window, "offline", function(){ alert("Offline"); });
支持离线检测的浏览器有IE6+、navigator.onLine、Firefox3、Safari4、Opera10.6、Chrome、iOS 3.2版Safari、Android 版 WebKit。
应用缓存
application cache,这是专门为了开发离线Web应用而设计的,它从浏览器缓存中分出来一部分,想要在这个部分中保存数据,可以使用一个描述文件,列出要下载和缓存的资源。这里有个简单的例子:CACHE MANIFEST #Comment file.js file.css
这里仅简单的列出了要下载的文件。描述文件的选项非常多,想要进一步了解的话给大家一个网址咯。Go offline with application cache
要将描述文件与页面关联起来,可以使用,下面的属性。
<html manifest="/offline.manifest">
同时,有相应的JS API提供给开发者来获取其状态。
这个API的核心是applicationCache对象,这个对象有一个status属性,表示应用缓存的状态:
0:无缓存
1:闲置,应用缓存未更新
2:检查中,正在下载描述文件并检查更新
3:下载中,应用缓存正在下载描述文件中指定的资源
4:更新完成,应用缓存已经更新了资源,且所有资源下载完毕,可以通过swapCache()来使用了
5:废弃,应用缓存的描述文件已经不存在了,页面无法再访问应用缓存
同时针对上面的状态也有相应的事件:
checking,查找更新时触发
error,检查更新或下载资源期间发生错误时触发
noupdate,检查描述文件发现没有更新时触发
downloading,开始下载资源时触发
progress,下载的过程中不断触发
updateready,下载完毕且可以使用swapCache()时触发
cached,应用缓存完整可用时触发
在页面刚刚加载时,会自动检查有没有描述文件是否更新,并根据具体情况触发上述事件。
有用的方法有两个:
update(),会去检查描述文件是否更新,就像页面刚刚加载那样,并触发相应的事件
swapCache(),在新缓存可用时可以调用这个方法来启用新应用缓存
EventUtil.addHandler(applicationCache, "updateready", function(){ applicationCache.swapCache(); });
数据存储
随着Web应用的出现,也产生了应该直接在客户端上储存用户信息能力的要求,包括用户的登陆信息,偏好设置或其他数据。最最开始解决这个问题的方案是cookie。Cookie
Cookie最初是用来在客户端储存会话信息的。该标准要求服务器对任意HTTP请求发送Set-Cookie HTTP头作为相应的一部分,其中包含会话的信息。例如:HTTP/1.1 200 OK Content-type: text/html Set-Cookie: name=value Other-header: other-header-value
这里就设置了一个以name为名称,value为值的一个cookie。
浏览器会储存这样的会话信息。并在这之后通过为每一个请求添加Cookie HTTP头部将信息发送回服务器:
GET /index.html HTTP/1.1 Cookie: name=value Other-header: other-header-value
这个信息对于服务器来说就可以唯一验证请求的身份
限制
cookie在性质上是绑定在特定域名下的。当设定了一个cookie,再给创建它的域名发送请求时都会包含这个cookie,而发向其他域的请求中并不会包含这个cookie。这个限制保证了cookie只能让批准的接受者访问。
每个域的cookie总数是有限的,各浏览器不同,最小的规定一个域有30个cookie,大小一般不超过4095B。
cookie的构成
cookie由浏览器保存的一下几块信息构成:
名称:一个唯一确定cookie的名称
值:储存在cookie中的字符串值
域:这个cookie对哪个域有效,如果这个域包含子域,那对子域同样有有效。如果设定是没有明确指定,这个值会被认为是设置cookie的那个域
路径:用于指定向域中的哪个路径发送cookie,例如,你可以指定cookie只发送到www.baidu.com/img,那再访问www.baidu.com时就不会发送cookie。及时它们同域
失效时间:cookie应该被删除的时间戳,默认浏览器会话结束就删除
安全标志:指定后,cookie只有在使用SSL连接时才会发送到服务器
设置时像这样:
HTTP/1.1 200 OK Content-type: text/html Set-Cookie: name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.wrox.com path=/; secure Other-header: other-header-value
JS中的cookie
JS中访问cookie的接口是BOM的document.cookie。
获取时,这个属性返回字符串,包括当前页面可用的(根据cookie的域,路径,失效时间等等)所有cookie的名称和值组成的键值对。
name1=value1;name2=value2;name3=value3
这些是经过URI编码的值。要使用decodeURIComponent()解码。
设置时和使用HTTP头部设置一样:
document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas") + "; domain=.wrox.com; path=/";
由于在JS中使用cookie不是很直观,写个工具类比较好。
var CookieUtil = { get: function (name){ var cookieName = encodeURIComponent(name) + "=", cookieStart = document.cookie.indexOf(cookieName), cookieValue = null; if (cookieStart > -1){ var cookieEnd = document.cookie.indexOf(";", cookieStart); if (cookieEnd == -1){ cookieEnd = document.cookie.length; } cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd)); } return cookieValue; }, set: function (name, value, expires, path, domain, secure) { var cookieText = encodeURIComponent(name) + "=" + encodeURIComponent(value); if (expires instanceof Date) { cookieText += "; expires=" + expires.toGMTString(); } if (path) { cookieText += "; path=" + path; } if (domain) { cookieText += "; domain=" + domain; } if (secure) { cookieText += "; secure"; } document.cookie = cookieText; }, unset: function (name, path, domain, secure){ this.set(name, "", new Date(0), path, domain, secure); } }; CookieUtil.set("book", "Professional JavaScript"); alert(CookieUtil.get("book")); CookieUtil.unset("book"); alert(CookieUtil.get("book"));
子cookie
因为cookie有单域名下数量的限制,一些开发人员使用了一种称为子cookie的概念,子cookie是存放在单个cookie中更小段的数据,也就是使用cookie值来储存多个名值对。最常见的格式如下:
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5
这样对于获取到cookie就又多了一层障碍,为了更好的操纵cookie当然要在来个工具类咯~
var SubCookieUtil = { //获取这个name的cookie中所有的子cookie并放入一个对象中 getAll: function(name){ var cookieName = encodeURIComponent(name) + "=", cookieStart = document.cookie.indexOf(cookieName), cookieValue = null, cookieEnd, subCookies, len, i, parts, result = {}; if (cookieStart > -1){ cookieEnd = document.cookie.indexOf(";", cookieStart); if (cookieEnd == -1){ cookieEnd = document.cookie.length; } cookieValue = document.cookie.substring(cookieStart + cookieName.length, cookieEnd); if (cookieValue.length > 0){ subCookies = cookieValue.split("&"); for (i=0, len=subCookies.length; i < len; i++){ parts = subCookies[i].split("="); result[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); } return result; } } return null; }, //使用getAll方法返回的子cookie的对象,找到想要的子cookie get: function (name, subName){ var subCookies = this.getAll(name); if (subCookies){ return subCookies[subName]; } else { return null; } }, //将所有子cookie和相应参数序列化存进cookie里 setAll: function(name, subcookies, expires, path, domain, secure){ var cookieText = encodeURIComponent(name) + "=", subcookieParts = new Array(), subName; for (subName in subcookies){ //这里使用hasOwnProperty来确定不会循环到原型链中其实不是子cookie的属性 if (subName.length > 0 && subcookies.hasOwnProperty(subName)){ subcookieParts.push(encodeURIComponent(subName) + "=" + encodeURIComponent(subcookies[subName])); } } if (subcookieParts.length > 0){ cookieText += subcookieParts.join("&"); if (expires instanceof Date) { cookieText += "; expires=" + expires.toGMTString(); } if (path) { cookieText += "; path=" + path; } if (domain) { cookieText += "; domain=" + domain; } if (secure) { cookieText += "; secure"; } } else { cookieText += "; expires=" + (new Date(0)).toGMTString(); } document.cookie = cookieText; }, //更改一个子cookie,这个方法会先找到这个子cookie所在的cookie中所有的子cookie,将这个新子cookie放到存着所有子cookie的对象中 //再调用setAll方法保存 set: function (name, subName, value, expires, path, domain, secure) { var subcookies = this.getAll(name) || {}; subcookies[subName] = value; this.setAll(name,subcookies,expires, path, domain, secure); }, unset: function (name, subName, path, domain, secure){ var subcookies = this.getAll(name); if (subcookies){ delete subcookies[subName]; this.setAll(name, subcookies, null, path, domain, secure); } }, unsetAll: function(name, path, domain, secure){ this.setAll(name, null, new Date(0), path, domain, secure); } }; document.cookie="data=name=Nicholas&book=Professional%20JavaScript"; var data = SubCookieUtil.getAll("data"); alert(data); alert(data.name); //"Nicholas" alert(data.book); //"Professional JavaScript" alert(SubCookieUtil.get("data", "name")); //"Nicholas" alert(SubCookieUtil.get("data", "book")); alert(SubCookieUtil.get("data", "class")); SubCookieUtil.set("data", "class", "how to kill people"); alert(SubCookieUtil.get("data", "class"));
JS不能访问的cookie
为了保证cookie的安全,有的cookie会不允许JS获取
Web储存机制
Web Storage的目标是克服cookie的限制,当数据需要被严格控制在客户端上时,无需持续的将数据发回到服务器。其两个主要目标是:提供一种在cookie之外储存会话数据的途径
提供一种储存大量可以夸会话存在的数据的机制
Storage类型
可以以名值对的方式储存字符串值,有如下方法:
clear()
getItem(name)
key(index)
removeItem(name)
setItem(name, value)
sessionStorage对象
这个对象储存特定于某个会话的数据,这也就意味着这里的数据只保存到浏览器关闭。不过刷新页面时这里的数据是可以存住的。存在这里的数据只能由最初储存数据的页面访问。sessionStorage 其实是Storage的一个实例,所以上面的方法同样可用。
sessionStorage.setItem("name", "Nicholas"); sessionStorage.book = "Professional JavaScript"; for (var i=0, len = sessionStorage.length; i < len; i++){ var key = sessionStorage.key(i); var value = sessionStorage.getItem(key); alert(key + "=" + value); } sessionStorage.removeItem("book");
globalStorage对象
这个对象的目的是跨会话存储数据,但是有域的限制,在储存数据时,首先要指定的就是域:
globalStorage["wrox.com"].name = "Nicholas"; var name = globalStorage["wrox.com"].name;
globalStorage不是Storage的实例globalStorage[“wrox.com”]才是哦。
这个域名下的所有子域可以访问这里的数据。
对每个空间访问的限制是根据域名,协议,端口来限制的,同一个域名。使用HTTP访问就访问不到HTTPS时存的数据。端口号不同也是一样。
globalStorage[location.host ].name = "Nicholas"; globalStorage[location.host ].book = "Professional JavaScript"; globalStorage[location.host ].removeItem("name"); var book = globalStorage[location.host ].getItem("book");
localStorage对象
这个对象是为了取代globalStorage而存在的。这个也是跨会话的,不需要指定域名,只有完全相同的域名才能访问,子域名都不行。
localStorage.setItem("name", "Nicholas"); localStorage.book = "Professional JavaScript"; var name = localStorage.getItem("name"); var book = localStorage.book; alert(name);
Storage事件
对storage对象进行的任何修改都会触发storage事件,这个事件的event有如下属性:
domain
key
newValue
oldValue
各浏览器对这个事件的支持并不全面
大小限制
各浏览器对Storage大小的限制并不相同,不过都是根据域名来区分的。
IndexedDB
Indexed Database API,是用来在浏览器中保存结构化数据的一种数据库。它的思想是创建一套API,方便保存和读取JavaScript对象,同时还支持查询及搜索。IndexedDB设计的操作完全是异步进行的。每次对数据库的操作都会返回一个相应的IDBRequest对象的实例来代表这次请求。在这个实例上可以设置事件,等待成功或失败事件被触发,在里面做相应的操作。IndexedDB是全局对象。API不稳定,有的浏览器为其加了前缀。
var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB;
数据库
打开数据库,把数据库名传入,存在会打开,不存在会创建并打开。打开的数据库的请求是一个IDBRequest对象,通过事件来观察请求是否成功。成功就会返回一个IDBDatabase对象。
可以给database设置一个版本号,同样是返回一个IDBRequest,同样的操作模式。
var request, database; request = indexedDB.open("admin"); request.onerror = function(event){ alert("Something bad happened while trying to open: " + event.target.errorCode); }; request.onsuccess = function(event){ database = event.target.result; setVersion(); }; function setVersion() { if (database.version != "1.0"){ request = database.setVersion("1.0"); request.onerror = function(event){ alert("Something bad happened while trying to set version: " + event.target.errorCode); }; request.onsuccess = function(event){ alert("Database initialization complete. Database name: " + database.name + ", Version: " + database.version); }; } else { alert("Database already initialized. Database name: " + database.name + ", Version: " + database.version); } }
对象储存空间
建立了与数据库的连接后,就可以使用对象储存空间(相当于表,其中的对象相当于表中的纪录)。
创建对象储存空间,需要两个信息,这个空间的名字,以及Key Path和Key Generator,这两个值确定了这个空间中储存的每个记录以什么来标识。
No No:This object store can hold any kind of value, even primitive values like numbers and strings. You must supply a separate key argument whenever you want to add a new value.
Yes No:This object store can only hold JavaScript objects. The objects must have a property with the same name as the key path.
No Yes:This object store can hold any kind of value. The key is generated for you automatically, or you can supply a separate key argument if you want to use a specific key.
Yes Yes:This object store can only hold JavaScript objects. Usually a key is generated and the value of the generated key is stored in the object in a property with the same name as the key path. However, if such a property already exists, the value of that property is used as key rather than generating a new key.
创建空间需要在打开数据库时返回的IDBRequest上的onupgradeneeded事件中进行,否则会报错的。这个事件会在新创建数据库或更新数据库版本号时(open()时传入更高的版本号,数据库的版本就会自己更新)被触发。
request.onupgradeneeded = function (event) { var db = event.target.result; // Create an objectStore to hold information about our customers. We're // going to use "ssn" as our key path because it's guaranteed to be // unique - or at least that's what I was told during the kickoff meeting. var objectStore = db.createObjectStore("customers", { keyPath: "ssn" }); // Create an index to search customers by name. We may have duplicates // so we can't use a unique index. objectStore.createIndex("name", "name", { unique: false }); // Create an index to search customers by email. We want to ensure that // no two customers have the same email, so use a unique index. objectStore.createIndex("email", "email", { unique: true }); // Use transaction oncomplete to make sure the objectStore creation is // finished before adding data into it. objectStore.transaction.oncomplete = function(event) { // Store values in the newly created objectStore. var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers"); for (var i in customerData) { customerObjectStore.add(customerData[i]); } }; };
在创建了空间后,就可以用add()或put()来向其中添加要保存的对象。对于添加唯一标识已经存在的对象,add会报错,put则会更新原有值。
事务
在创建好空间后,就相当于数据库的结构已经确定了,在这之后对数据的操作就要通过事务了。
创建事务需要指定针对哪个储存空间以及读写模式,可以一下打开多个储存空间。
var transaction = db.transaction(["customers"], "readwrite");
取得事务索引后使用objectStore()访问储存空间,然后就可以使用add()、put()、get()、delete()、clear()。这五个方法都会返回一个请求对象,通过事件来操作
var request = db.transaction("users").objectStore("users").get("007"); request.onerror = function(event){ alert("Did not get the object!"); }; request.onsuccess = function(event){ var result = event.target.result; alert(result.firstName); //"James" };
相关文章推荐
- HTML5中在客户端验证文件上传的大小
- html5 web数据存储
- 页面元素查找之Selectors API
- 使用ajax实现用户登录验证(升级版)
- Canvas 在高清屏下绘制图片变模糊的解决方法
- 关于前端的思考与感悟
- 新时代编辑神器:Atom
- rem : web app适配的秘密武器
- jquery高级应用之Deferred对象
- 原生js结合html5制作小飞龙的简易跳球
- HTML5游戏引擎LTweenLite实现的超帅动画效果(附demo源码下载)
- 三个不常见的 HTML5 实用新特性简介
- 低版本IE正常运行HTML5+CSS3网站的3种解决方案
- js+HTML5实现canvas多种颜色渐变效果的方法
- javascript+HTML5的Canvas实现Lab单车动画效果
- javascript+html5实现绘制圆环的方法
- javascript html5实现表单验证
- HTML5实现微信拍摄上传照片功能
- JavaScript+html5 canvas制作的百花齐放效果完整实例