Hi,丸子

一个挺不正经的前端小混混

rainbow

叽歪html

菜鸟的叽叽歪歪

接触web开发一年多了,从完全不知道html跟css是干嘛的,到现在对他们有所理解并热爱着它们,这一切要感谢鬼哥(cssforest博主)在我入门的时候让我加入css森林群这个“变态”的地方,在这里我受益匪浅。这里充斥着对html跟css的激烈的讨论,其中不少关于语义化html的理解,语义化的html是html设计的本义,只是很多年后才被正确的认识。

我想说:HTML不是用来布局的

随着web2.0这个概念的大潮,网络上铺天盖地的宣传和传播着各种所谓“div+css”布局教程。嗯,国人喜欢跟随各种潮流,我也是因为这种潮流才接触的web开发。但是,我想说:HTML不是用来布局的。在微博上发了这句话后,马上就有人密我说:“那你用什么布局?除了div跟table,你还用什么?”。html是各种信息的载体,所以它表示的是各种信息的层次跟内容,布局就是让内容按照设想的样貌呈现,那是css的事。

语义化之路

html阐述着页面拥有者所要表达的各种信息,这些信息即使在无样式的条件下也能清楚地让浏览者知道页面上的内容和层次,但是显然这100多个标签所能表示的信息是有限的。在森林里经常有人会发一个设计图的某部分来问html结构要怎么写,因此可能会引起一堆的讨论,关于语义化的讨论。语义这东西本身就是有争议的,每个人的理解都会有些不同,最终问题可能无疾而终,一般提问者会选择大多数人赞成的方案,这个无可厚非。但是,我认为代码者写代码写的是自己的态度,自己的理解,自己所要传达给浏览者的信息。所以,为什么要拿设计图来讨论html结构,为什么不去掉样式看看你的代码所展示的真正含义,既然你这么纠结于语义化,那么就还原他本来的样貌,而不是在一堆修饰下看待结构。

那么如何来理解各种信息的语义,然后再通过传递类似信息的html标签来书写呢?我也没有答案。但是,你确定了解了各个标签及基本含义了吗?我想这是语义化的前提,估计很多人都只知道60%甚至更少,用到的标签更是少之又少。我们要做的是理解最基本的东西,再去思考更深一层的含义,才能将有限的语义扩展并承载无限的信息。

看完自己写的东西很装逼,但也是我的一点理解和思考,所谓语义,不就是你确切想告诉别人你想表达的东西么。

HTML5本地存储

同样是html5的规范,同样是存储,HTML5本地存储和离线存储不是一回事,感觉有必要提及,因为在互联网铺天盖地宣传html5的特性的时候,经常出现这样那样的字眼,对于还没接触的或初学者来说,很容易混淆这两个概念。离线存储(Offline Storage)正如之前我的那篇介绍中你看到的样子,实际上它实现的是文件的离线存储,而本地存储(Local Storage)跟会话存储(Session Storage)一样同属于web的数据存储(Web Storage)。还有一种存储方式是Web SQL Database,它是一个可以用SQL操作的客户端数据库。这些存储方式都是用户客户端实现的,因此有人会把它们称为“本地存储”,实际上我觉得叫“客户端存储”更合适,这样不至于跟Web Storage中的Local Storage概念冲突和混淆。

什么是HTML5 Local Storage?

HTML5中介绍的Local Storage(有的地方把它叫做DOM Storage)是一种将web数据存储在用户本地的存储方式,这种形式的存储其实你早就接触过了,那就是cookie。那么为什么不直接用cookie好了,还要本地存储做什么?我们知道,html5带来更丰富的web表现形式,在web中实现大量富媒体应用的时候,对于数据存储的要求也相对提高,当然服务器的压力也相应变大,因此我们需要一种类似于cookie的存储方式,但它和cookie不大一样:

  1. 我们需要更大的存储空间,本地存储默认有5MB,而cookie只能存4k的数据
  2. 和cookie一样都存储在客户端的
  3. 数据不会因为页面刷新或关闭等操作而改变
  4. 不会向服务器发出请求,cookie是会包含在每个HTTP请求中的

好吧,假设你现在正需要这样的一种存储方式,那么你要知道浏览器的支持情况如何,支持本地存储的浏览器版本有:IE8+,FIREFOX3.5+,SAFARI4+,CHROME4+,OPERA10.5+,IPHONE2+,ANDROID2+以及其他我不知道的。

如何使用HTML5 Local Storage?

不是所有的浏览器都支持html5的本地存储,所以在使用它之前,你需要一点检测工作,来确保你的应用正常运作,你需要下面的代码:

?View Code JAVASCRIPT
1
if('localStorage' in window&&window['localStorage'] !== null){/* do storage stuff... */}

Local Storage实现了Storage接口,该接口的原型如下:

interface Storage {
readonly attribute unsigned long length;
getter DOMString key(in unsigned long index);
getter any getItem(in DOMString key);
setter creator void setItem(in DOMString key, in any data);
deleter void removeItem(in DOMString key);
void clear();
};

Local Storage是以键值对(key/value)的形式存储的,每个键值对称为一个项(item);length属性返回项的数量;key(n)返回列表中第n个key的名字;getItem和setItem分别是取值和赋值的方法,如果要取值的key不存在返回null,如果赋值的key存在就覆盖,不存在就新建一个项;removeItem可以清除给定的key所对应的项,如果key不存在则什么都不做;clear会清除所有的项,如果列表本来就是空的就什么都不做。请不要忽略这段话的解释,而且要注意“什么都不做”这几个字是有重要作用的,在你需要实现对存储过程的监控的时候尤为重要!你可能会因为做了某个操作却没有触发相应的事件监听器而感到疑惑,原因就在于“什么都不做”。

看完Storage接口后,我们就可以来做实验了,看看如下代码(查看完整Demo):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>HTML5 Local Storage</title>
</head>
 
<body>
<h1>你好,<span id="name">我好像不认识你</span></h1>
<p><button type="button" onclick="rmMem()">忘记我吧!</button></p>
<script type="text/javascript">
<!--
window.onload=function(){
	if(('localStorage' in window) && window['localStorage'] !== null){
		var name=localStorage.getItem('name');
		if(name==null){
			name=prompt("你的名字是?",'丸子');
			if(name!=null){
				localStorage.setItem('name',name);
				document.getElementById('name').innerHTML=name;
			}
		}else{
			document.getElementById('name').innerHTML=name;
		}
	}else{
		alert('天啊,你还在用这么土的浏览器!');
	}
}
function rmMem(){
	document.getElementById('name').innerHTML='我好像不认识你';
	localStorage.removeItem('name');
	alert('清除记忆成功!');
}
//-->
</script>
</body>
</html>

你可能还会看到这样的写法:

?View Code JAVASCRIPT
1
localStorage.setItem(1,'some values');

这样不代表该项就是第一项,数字1会被转换为string,因为key的类型就是字符串。

除了接口中那样通过getter跟setter的方式对本地存储的数据进行读写,我们还可以像对象属性访问一样读取与写入数据:

?View Code JAVASCRIPT
1
2
localStorage.name='wanz';
localStorage['name']='wanz';

本地存储事件监听

事件监听对于处理存储的异常还是必需的,先来看看StorageEvent 接口的定义:

interface StorageEvent : Event {
readonly attribute DOMString key;
readonly attribute any oldValue;
readonly attribute any newValue;
readonly attribute DOMString url;
readonly attribute Storage storageArea;
void initStorageEvent(in DOMString typeArg, in boolean canBubbleArg, in boolean cancelableArg, in DOMString keyArg, in any oldValueArg, in any newValueArg, in DOMString urlArg, in Storage storageAreaArg);
};

initStorageEvent可以不用管,它负责事件初始化;key返回的是被修改的key;oldValue和newValue分别返回key修改前和修改后的值;url返回修改该key的文档页面地址,有的浏览器可能会叫uri;storageArea返回受影响的Storage对象。

一个简单的存储事件监听的方式可以像下面的代码这样,有经验的同学都知道怎么扩展了,兼容IE不知道的自己google。

?View Code JAVASCRIPT
1
addEventListener('storage',ev_handler,false);

不管是setItem(), removeItem()还是 clear()操作,各个存储事件只有在存储内容真正发生变化时才会被触发,这也是之前我强调了“什么都不做”的原因。到目前为止,我用http://www.quirksmode.org/dom/tests/html5_storage.html这个页面测试了IE8,FIREFOX3.6.8,CHROME6.0.496和SAFARI5.0.1发现只有IE8跟FF对事件监听有反应,不过返回的StorageEvent的各个属性值基本都是undefined,而CHROME和SAFARI则没有反应,个人猜测是浏览器对存储事件监听的实现还不完善,不排除代码问题,有兴趣的同学自己做测试,我就不给出DEMO了。

另外,出于浏览器安全限制的考虑,大家在测试代码的时候,一定要在服务器环境下,切记!

CSS3学习:线性渐变(linear-gradient)

在切一块很大的渐变背景图时,突然想为啥不试试用css3来实现,撇开兼容问题开始学习这个有趣的css3属性。于是分别在MDCSRL找到了对应的文档,结果出乎预料的发现Gecko和webkit的实现方式竟然不一样,按以往的经验这两个的写法应该只是-moz和-webkit开头的区别,分别来看下这个属性的两种写法:

对Gecko内核浏览器:
-moz-linear-gradient([point || angle,]? stop, stop [, stop]*)

对webkit内核浏览器:
-webkit-gradient(type, start_point, end_point [, stop]*)

看上去有点复杂,让我们来逐个分解,先从Gecko的开始。

-moz-linear-gradient
point是用来指定渐变起始点的,它的值和background-position的值是一样,它可以是百分比,像素值或“left”、“right”、“top”与“bottom”等。
angle是用来指定渐变的角度。
stop必须指定起始和结束的渐变颜色,颜色值后可以设置渐变结束位置,两者以空格隔开。

-webkit-gradient
type指定渐变类型,目前有两个值:linear和radial,本文讨论的是线性渐变,使用linear。
start_point和end_point用来指定渐变起始点和终点坐标。
stop用来指定渐变颜色。

请在firefox3.6+,safari4+或chrome4+运行下面的实例,才能看到效果。如果考虑到兼容性问题,在不用图片的前提下,你可以开头设置一个纯色的背景代替,这样不会太影响整体设计的美观。当然虽然IE有滤镜filter: progid:DXImageTransform.Microsoft.Gradient()可以实现,但不推荐。

提示:你可以先修改部分代码再运行。

代码中,你需要注意的是:1,webkit的stop的写法有from跟to,这样很直观;2,webkit中渐变起始和终点坐标是没有单位的;3,与webkit相反,Gecko中颜色值后的渐变结束位置值是有单位的。

html5离线文件存储入门

所有的浏览器都有自己的缓存机制,但那些机制并不可靠而且难以控制,在你做web开发的时候可能经常因为浏览器缓存带来的问题而烦恼不已。html5通过ApplicationCache接口解决了一些问题,并且使离线存储成为可能,离线存储使得你的web应用可以在用户离线的状况下进行访问。这个技术显然至少有三个好处:

  1. 最直接的好处就是用户可以离线访问你的web应用
  2. 因为文件被缓存在本地使得web页面加载速度提升许多
  3. 离线应用只加载被修改过的资源,因此大大降低了用户请求对服务器造成的负载压力

如何实现离线文件存储?
你的服务器得先支持html5!是的,这句话看上去像是以前在css森林群里大家说的“我的服务器不支持div+css”一样的玩笑话。但我很严肃的告诉你,要实现离线存储的应用,你确实需要服务器的支持!容我细细道来,先来看看html5的离线文件存储应用对你的代码有什么要求,你需要在页面的html标签中通过manifest属性引用一个manifest文件来使得你的应用可缓存。简单地说,manifest文件是一个文本文件,它罗列了离线访问应用时所需缓存的文件清单(注意:引用该manifest文件的页面,不管你有没有罗列到清单中,都会被缓存),但不只是这样。代码像下面这样:

1
2
3
<html manifest="test.manifest">
  ...
</html>

当然,这个manifest的文件路径用绝对路径和相对路径都可以,甚至可以引用其他服务器上的manifest文件。该文件所对应的mime-type应该是text/cache-manifest的,所以你需要配置服务器来发送对应的MIME类型信息,服务器配置不在讨论范围内,请自行去了解吧。接下来要看的是manifest文件的结构,它的结构很简单,我们来看下面的例子:

?View Code MANIFEST
1
2
3
4
5
CACHE MANIFEST
index.html
style.css
images/logo.png
scripts/mootools.js

这是最简单的一个manifest文件的样子,正如前面所提到的,文件罗列了需要被缓存的文件清单,第一行中的CACHE MANIFEST 是必须的,每个站点有5MB的空间来存储这些数据,如果manifest文件或文件里所列的文件无法加载,整个缓存更新过程将无法进行,浏览器会使用最后一次成功的缓存。前面说过manifest文件不只是罗列要缓存的文件,那么它还有其他什么作用呢?让我们来看个稍微复杂点的例子:

?View Code MANIFEST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CACHE MANIFEST
# wanz app v1
 
# 指明缓存入口
CACHE:
index.html
style.css
images/logo.png
scripts/main.js
 
# 以下资源必须在线访问
NETWORK:
login.php
 
# 如果index.php无法访问则用404.html代替
FALLBACK:
/index.php /404.html

有经验的同学一定可以看出来#是用来注释一行的,但它还有一个小作用。web应用的缓存只有在manifest文件被修改的情况下才会被更新,所以如果你只是修改了被缓存的文件,那么用户本地的缓存还是不会被更新的,但是你可以通过修改manifest文件来告诉浏览器需要更新缓存了。利用这点,你可以像上面的例子中那样,写一句这样的注释一个文件版本:

# wanz app v1

这样写有三个好处:

  1. 你可以很明确的了解离线web应用的版本
  2. 通过简单的修改这个版本号就可以轻易的通知浏览器更新
  3. 你可以配合JavaScript程序来完成缓存更新

正如你看到的,manifest文件有三个节点,它们各自的含义如下:

CACHE:
这个是manifest文件的默认入口,在此入口之后罗列的文件 (或直接写在CACHE MANIFEST后的文件)在它们下载到本地后会被缓存起来
NETWORK:
可选的,在此节后面所罗列的文件是需要访问网络的,即使用户离线访问了也会直接跳过缓存而访问服务器
FALLBACK:
可选的,用来指定资源无法访问时的回调页面。每一行包括两个URI,第一个是资源文件URI,第二个是回调页面URI。

备注:以上描述的这些节是没有先后顺序的,而且在同一个manifest中可以多次出现

到目前为止,你应该了解如何来实现离线文件的存储了,只需要三个步骤:1,配置服务器manifest文件的MIME类型;2,编写manifest文件;3,在页面的html标签的manifest属性中引用写好的manifest文件。

如何更新离线存储?
你做到了,即使拔掉网线你也可以访问你制作的页面。于是你开始兴致勃勃的丰富你的页面,修改JavaScript代码和css来实现更炫的页面效果,然后将它们上传到服务器,结果你会发现:即使你刷新页面刷到爆,清除n次历史访问记录都无法看到你要的新的页面效果。那是因为你根本还没有更新html5的离线存储文件。现在的问题是如何更新html5离线缓存?下面的三种情况可以做到:

  1. 用户清除了离线存储的数据,这个不一定就是清理浏览器历史记录就可以做到的,因为不同浏览器管理离线存储的方式不同。比如Firefox的离线存储数据要到“选项”=>“高级”=>“网络”=>“脱机存储”里才可以清除。
  2. manifest文件被修改,上面说的,你修改了manifest文件里所罗列的文件也不会更新缓存,而是要替换manifest文件
  3. 使用JavaScript api编写更新程序

前面两种方式很简单,自己折腾吧。我们来看第三种,首先你需要对相关的api有所了解,详细的前往查看:http://www.whatwg.org/specs/web-apps/current-work/#applicationcache
先简单介绍下ApplicationCache和它的几个重点属性及方法:

cache = window . applicationCache
返回应用于当前window对象文档的ApplicationCache对象

cache = self . applicationCache
返回应用于当前shared worker的ApplicationCache对象 [shared worker]

cache . status
返回当前应用的缓存状态,status有五种无符号短整型值的状态:
UNCACHED = 0;
IDLE = 1;
CHECKING = 2;
DOWNLOADING = 3;
UPDATEREADY = 4;
OBSOLETE = 5;

cache . update()
调用当前应用资源下载过程

cache . swapCache()
更新到最新的缓存,这个不会使得之前加载的资源突然被重新加载。图片不会重新加载,样式和脚本也不会重新渲染或解析,唯一的变化是在此之后发出请求页面的资源是最新的

applicationCache对象和缓存宿主的关系是一一对应,window对象的applicationCache属性会返回关联window对象的活动文档的applicationCache对象。在获取status属性时,它返回当前applicationCache的状态,它的值有以下几种状态:

UNCACHED (数值 0)
此时,ApplicationCache对象的缓存宿主与应用缓存无关联

IDLE (数值 1)
应用缓存已经是最新的,并且没有标记为obsolete

CHECKING (数值 2)
ApplicationCache对象的缓存宿主已经和一个应用缓存关联,并且该缓存的更新状态是checking

DOWNLOADING (数值 3)
ApplicationCache对象的缓存宿主已经和一个应用缓存关联,并且该缓存的更新状态是downloading

UPDATEREADY (数值 4)
ApplicationCache对象的缓存宿主已经和一个应用缓存关联,并且该缓存的更新状态是idle,并且没有标记为obsolete,但是缓存不是最新的

OBSOLETE (数值 5)
ApplicationCache对象的缓存宿主已经和一个应用缓存关联,并且该缓存的更新状态是obsolete

如果update方法被调用了,浏览器user agent就必须在后台调用应用缓存下载过程;如果swapCache方法被调用了,浏览器user agent会执行以下步骤:

  1. 检查ApplicationCache的缓存宿主是否与应用缓存关联
  2. 让cache成为ApplicationCache对象的缓存宿主关联的应用缓存
  3. 如果cache的应用缓存组被标记为obsolete,那么就取消cache与ApplicationCache对象的缓存宿主的关联并取消这些步骤,此时所有资源都会从网络中下载而不是从缓存中
  4. 检查在同一个缓存组中是否存在完成标志为“完成”的应用缓存,并且版本比cache更新
  5. 让完成标志为“完成”的新cache成为最新的应用缓存
  6. 取消cache与ApplicationCache对象的缓存宿主的关联并用新cache代替关联

假设你已经写好的离线文件存储的Demo页面,通过下面的代码可以来检查,当前页面缓存的状态:

?View Code JAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var appCache = window.applicationCache;
 
switch (appCache.status) {
  case appCache.UNCACHED: // UNCACHED == 0
    alert( 'UNCACHED');
    break;
  case appCache.IDLE: // IDLE == 1
    alert( 'IDLE');
    break;
  case appCache.CHECKING: // CHECKING == 2
    alert ('CHECKING');
    break;
  case appCache.DOWNLOADING: // DOWNLOADING == 3
    alert( 'DOWNLOADING');
    break;
  case appCache.UPDATEREADY:  // UPDATEREADY == 5
    alert ('UPDATEREADY');
    break;
  case appCache.OBSOLETE: // OBSOLETE == 5
    alert ('OBSOLETE');
    break;
  default:
    alert ('UKNOWN CACHE STATUS');
    break;
};

一个更新的实现过程大概是这样的:首先调用applicationCache.update()使得浏览器开始尝试更新,前提是你的manifest文件是更新过的(比如前面所说的,修改了版本号);在applicationCache.status为UPDATEREADY 状态时就可以调用applicationCache.swapCache()来将旧的缓存更新为新的。

?View Code JAVASCRIPT
1
2
3
4
5
6
7
var appCache = window.applicationCache;
 
appCache.update(); // 开始更新
 
if (appCache.status == window.applicationCache.UPDATEREADY) {
  appCache.swapCache();  // 得到最新版本缓存列表,并且成功下载资源,更新缓存到最新
}

没错,更新过程也可以很简单,但是一个好的应用少不了容错处理,就如Ajax技术一样,你需要对更新过程进行监控,处理各种异常或提示等待状态来使你的应用更强壮,用户体验更好,因此你需要了解applicationCache的更新过程所触发的事件,它们是:onchecking,onerror,onnoupdate,ondownloading,onprogress,onupdateready,oncached和onobsolete。监听事件的代码你应该也都轻车熟路了,假设你要对更新错误进行处理,你可以这样写:

?View Code JAVASCRIPT
1
2
3
4
5
6
7
8
9
var appCache = window.applicationCache;
 
// 请求manifest文件时返回404或410,下载失败
// 或manifest文件在下载过程中源文件被修改会触发error事件
appCache.addEventListener('error', handleCacheError, false);
 
function handleCacheError(e) {
  alert('Error: Cache failed to update!');
};

不管是manifest文件还是它所罗列的资源文件下载失败,整个更新过程就终止了,浏览器会使用上一个最新的缓存。更多关于事件的内容请前往:http://www.whatwg.org/specs/web-apps/current-work/#event-handlers

经过一段时间的测试,我建议大家在chrome下做实验,因为用firefox的实现就像屎,有这个功能却很臭!,最后献上离线应用的demo一枚:丸子的友邻

为什么使用<!DOCTYPE HTML>

不管是刚接触前端,还是你已经“精通”web前端开发的内容,你应该知道在你写html的时候需要定义文档类型;你知道如果没有它,浏览器在渲染页面的时候会使用怪异模式;你知道各个浏览器在怪异模式下对各个元素渲染是有差异的。所以你会写像这样的doctype:

1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

好在现在的各种web开发工具都足够强大,支持插入模板代码,因此你并不需要把这又长又臭的doctype一个个字母的敲出来。但是如果你受够了它,你也许可以尝试下面这个写法:

1
<!DOCTYPE html>

哇哦,很简洁哦!好处显而易见:一、你可以轻松的写下这个doctype,而不用担心会写错;二、你大概省下了105字节字符,对于一个每日PV达到千万级的站点,它可以省下相当客观流量;三、它是向后兼容的,是的,html5的doctype就是这样写的,并且现代浏览器都认识它。

如果你跟我一样一直以为:没有指定dtd将会开启浏览器的怪异模式,这种说法是错的!正确的说法应该是没有定义doctype才会开启怪异模式,也就是说你只需要定义<!doctype html>就可以让浏览器在严格模式(标准模式)下渲染页面,而不需要指定某个类型dtd。让我们来回顾一下,所有的浏览器都需要两种模式:怪异模式和严格模式(也有人叫标准模式)。IE 6 for Windows/mac, Mozilla, Safari和Opera 都实现了这两种模式,但是IE 6以下版本永远定在了怪异模式。关于两种模式,你需要知道以下几点:

  1. 在标准化之前写的页面是没有doctype的,因此没有doctype的页面是在怪异模式下渲染的。
  2. 反过来说,如果web开发人员加入的doctype,说明他知道他所要做的事情,大部分的doctype会开启严格模式(标准模式),页面也会按照标准来渲染。
  3. 任何新的或者未知的doctype都会开启严格模式(标准模式)。
  4. 每个浏览器都有自己的方式来激活怪异模式。你可以看看这个清单:http://hsivonen.iki.fi/doctype/

注意:你可以根本不需要根据你选择的doctype来验证你的页面,只要doctype标签存在就足以开启严格模式(标准模式)了。如果你对我说的这些还是感到怀疑,那么请前往http://www.quirksmode.org/css/quirksmode.html#link2了解你想知道的内容。我们只需要一小段JavaScript代码就可以得到答案,它就是:

?View Code JAVASCRIPT
1
mode=document.compatMode;

这个代码可以用来判断,当前浏览器是处于怪异模式还是标准模式,该属性的兼容性毋庸置疑,如果你表示怀疑,可以查看http://www.quirksmode.org/dom/w3c_html.html#t11。你可以在你想测试的浏览器里访问:http://wanz.im/demo/doctype-test.html,便可看到结果了,据我所知,这样并没有激活怪异模式,即使是ie6下,如果你有什么新发现,欢迎给我留言。

canvas JavaScript API学习(五)

写在前面
上篇文章我们了解了canvas用来处理图像的两个简单的方式:拉伸和裁切。这次我们来挑战像素级的图像处理,这话看起来挺唬人的,不过不用担心,它并没有那么可怕。

像素处理API
你可以通过ImageData对象在字节级的水平来处理图像数据,先看看与此相关的一些API属性及方法:

imagedata = context . createImageData(sw, sh)
根据给定的尺寸返回一个ImageData对象,该尺寸为CSS像素值(CSS Pixel),返回的对象中的像素点都是黑色透明的,即rgba(0,0,0,0)。

imagedata = context . createImageData(imagedata)
返回一个跟参数所指对象大小一致的ImageData对象,返回的对象中的像素点都是黑色透明的。

imagedata = context . getImageData(sx, sy, sw, sh)
返回包含canvas指定区域图像数据的ImageData对象

imagedata . width
imagedata . height
返回ImageData对象数据的实际尺寸,该值为设备像素值(Device Pixel)。

imagedata . data
返回一个一维数组,该数组包含按照RGBA顺序排列的范围从0到255的数据

context . putImageData(imagedata, dx, dy [, dirtyX, dirtyY, dirtyWidth, dirtyHeight ])
将指定的ImageData对象的数据绘制到canvas上。

备注:globalAlpha,globalCompositeOperation,还有shadow属性在调用该方法时会被忽略。

createImageData()方法会根据所给的参数创建一个以sw为宽,sh为高的空白ImageData对象,当该方法只有imagedata 参数时,它将返回一个与参数所指的ImageData对象一样大小的ImageData对象,并且返回的ImageData对象是被填充为透明黑色的(transparent black)。

getImageData(sx, sy, sw, sh)方法返回包含在canvas坐标系中由(sx, sy), (sx+sw, sy),(sx+sw, sy+sh), (sx, sy+sh)四个坐标在canvas上所围成区域的基本像素数据的ImageData对象,如果该区域超出了canvas范围,超出的部分被填充为透明黑色的(transparent black)。

如果createImageData()和getImageData()的参数值是无穷的或NaN,或者createImageData()只有一个参数值为null,方法会抛出NOT_SUPPORTED_ERR异常,如果值为0则抛出INDEX_SIZE_ERR异常。

ImageData对象必须被初始化,有宽度和高度,data属性值被初始化为一个CanvasPixelArray对象用来存放图像数据,至少要返回一个有效的一像素图像数据。

CanvasPixelArray对象包含了图像数据每个像素点有序的可索引的色值数据。这些数据从上到下,从左到右(即从左上角开始)描述了各个像素点的红,绿,蓝还有alpha值,它们的取值范围从0到255,用8个比特的数据描绘一个值。因此,该CanvasPixelArray对象包含了w×h× 4字节的数据,一个CanvasPixelArray对象的length属性必须返回这个数字。因为该对象可索引的范围就是[0,w×h× 4-1]。

注意:这里所说的宽(w)和高(h)有别于sw和sh,比如当canvas支持高分辨率的位图或sw与sh为负数时。

putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)方法把ImageData结构的数据描绘在canvas上,如果任一参数值是无穷或NaN的,它将抛出NOT_SUPPORTED_ERR异常;如果第一个参数不是ImageData对象或为null它将抛出TYPE_MISMATCH_ERR异常;如果最后四个参数被省略,那它们将分别被赋值为0,0,imagedata的width和imagedata的height值。

像素处理实例
看了上面那么大一段的描述,估计你也头晕了,我也是,我想来看几个例子比较有助于理解。先来看第一个Demo。查看Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html>
<head>
<meta  charset="utf-8" />
<title>Canvas javascript api demo</title>
<style type="text/css">
canvas {
	border:1px #000 solid;
}
</style>
 
</head>
<body>
<h1>this is a canvas api test page!</h1>
<canvas id="canvas" width="300" height="300">this browser does not support canvas...</canvas>
<a href="http://blog.wanz.im/2010/04/28/canvas_javascript_api_learning_five/">返回文章</a>
<script type="text/javascript">
var $=function(id){
	return document.getElementById(id);
}
window.onload=function(){
	var ctx=$('canvas').getContext('2d');
	var img=new Image();
	img.src='firefox.jpg';
	img.onload=function(){
		ctx.drawImage(img,0,0,240,240,30,10,120,120);
		//for security reason,you should turn on UniversalBrowserRead in Firefox.
		//netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
 
		imgData=ctx.getImageData(30,10,120,120);
		ctx.putImageData(imgData,30,160);
	}
}
</script>
</body>
</html>

这段代码很简单,在上篇文章的Demo中加入了getImageData和putImageData两个函数实现了图像的复制。如果你将代码保存到本地测试了并且没有效果那就对了,在Firefox的错误控制台中你会看到:

错误: uncaught exception: [Exception... "Security error" code: "1000" nsresult: "0x805303e8 (NS_ERROR_DOM_SECURITY_ERR)" location: "file:///C:/Documents%20and%20Settings/Administrator/%E6%A1%8C%E9%9D%A2/html5_canvas.html Line: 30"]

在Chrome的JavaScript控制台你会看到:

Uncaught Error: SECURITY_ERR: DOM Exception 18

而在Safari中,该代码是可以正常运行的,原因在于:Firefox与Chrome对本地文件JavaScript访问权限控制比较严格。Demo中的注释去掉后,可以在Firefox中看到我们实际想得到的结果,但其实这是没必要的,因为在服务器环境下代码是可以正常运行的。

通过下面的例子,我们来看如何操作图像数据。查看Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html>
<head>
<meta  charset="utf-8" />
<title>Canvas javascript api demo</title>
<style type="text/css">
canvas {
	border:1px #000 solid;
}
</style>
 
</head>
<body>
<h1>this is a canvas api test page!</h1>
<canvas id="canvas" width="300" height="300">this browser does not support canvas...</canvas>
<a href="http://blog.wanz.im/2010/04/28/canvas_javascript_api_learning_five/">返回文章</a>
<script type="text/javascript">
var $=function(id){
	return document.getElementById(id);
}
window.onload=function(){
	var ctx=$('canvas').getContext('2d');
	var img=new Image();
	img.src='firefox.jpg';
	img.onload=function(){
		ctx.drawImage(img,0,0,240,240,30,10,120,120);
		imgData1=ctx.getImageData(30,10,120,120);
		imgData2=ctx.createImageData(50,50);
		for(var i=0;i<imgData2.data.length/4;i++){
			//set the rgba to 255,0,0,255
			imgData2.data[i*4]=0xfff;
			imgData2.data[i*4+1]=0;
			imgData2.data[i*4+2]=0;
			imgData2.data[i*4+3]=255;
		}
		ctx.putImageData(imgData1,30,160);
		ctx.putImageData(imgData2,50,200);
	}
}
</script>
</body>
</html>

我们通过给每个像素点的红,绿,蓝和alpha赋值为255,0,0,255,得到一块不透明的红色矩形,并把这块区域的数据放到指定的位置,效果如Demo所示,正如开始所说的那样,globalAlpha,globalCompositeOperation,还有shadow属性在调用putImageData时会被忽略,因此不管你给的alpha值是否是透明的,指定的区域的数据是被覆盖了,而不是层叠的,即使你设置了透明也无法看到覆盖前的图像。

写在后面
其实,这部分的函数使用起来并不会难,难的是对图像操作的效率问题的优化,这次的内容只是简单的介绍函数的用法,如果你有足够的图形学的理论知识,你可以用已学的这些知识对图像进行灰度变换、线性和非线性空间滤波、频率域滤波、图像恢复与配准、彩色图像处理、小波、图像数据压缩、形态学图像处理、图像分割、区域和边界表示与描述,以及目标识别等操纵。本人所学知识有限,在这里只起抛砖引玉的作用,希望本文对入门的朋友有帮助。
结合这篇笔记和上一篇,我做了一个还不完善的demo:简易的图像编辑器

canvas JavaScript API学习(四)

写在前面
canvas在绘图方面的能力正在日益增强,它支持简单的动画效果,并且被很多人所看好,从前一段时间“html5代替flash”的炒作中可见一斑,甚至一些大公司也开始使用canvas代替了原本使用flash实现的功能,当然这不是为了赶潮流,而是canvas确实有一些优势,这个以后再探讨。本文要介绍的是canvas在绘图以外的一点内容,因为光有绘图能力是不够的,下面我们就进入canvas图像处理相关的API学习。

drawImage函数
要将指定图像绘制到canvas上,你需要用到drawImage函数,该函数有三种函数原型:

drawImage(image, dx, dy)
drawImage(image, dx, dy, dw, dh)
drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)

第一个参数image可以用HTMLImageElement,HTMLCanvasElement或者HTMLVideoElement作为参数。dx和dy是image在canvas中定位的坐标值;dw和dh是image在canvas中即将绘制区域(相对dx和dy坐标的偏移量)的宽度和高度值;sx和sy是image所要绘制的起始位置,sw和sh是image所要绘制区域(相对image的sx和sy坐标的偏移量)的宽度和高度值。

drawImage参数图解

drawImage参数图解

如果上面对参数的描述和参数示意图都无法让你清楚的知道它们的含义,那么看完下面的demo,或许会让你清晰很多。查看demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html>
<head>
<meta  charset="utf-8" />
<title>Canvas javascript api demo</title>
<style type="text/css">
canvas {
	border:1px #000 solid;
}
</style>
 
</head>
<body>
<h1>this is a canvas api test page!</h1>
<canvas id="canvas" width="300" height="300">this browser does not support canvas...</canvas>
<a href="http://blog.wanz.im/2010/04/22/canvas_javascript_api_learning_four/">返回文章</a>
<script type="text/javascript">
var $=function(id){
	return document.getElementById(id);
}
window.onload=function(){
	var ctx=$('canvas').getContext('2d');
	var img=new Image();
	img.src='firefox.jpg';
	img.onload=function(){
		ctx.drawImage(img,-150,50);
		ctx.drawImage(img,125,50,150,50);
		ctx.drawImage(img,50,50,150,150,125,150,150,150);
	}
}
</script>
</body>
</html>

从这个demo我们还可以看出,当image指定区域与canvas上的绘制区域大小非等比例缩放的时候,图像会被拉伸来适应设置的大小。这里就实现的图像处理中的缩放或者拉伸,裁切部分。

那么image怎么来的?上面的demo中,我创建了DHTML中的Image对象来加载一个图像,你还可以使用document.images,document.getElementsByTagName或者document.getElementById来得到image所接受的对象;甚至你可以通过document.getElementsByTagName或者document.getElementById获取canvas中绘制的图像对象作为image参数的值,但你得保证canvas中绘制了图像。

另一种比较少用到的方法是dataURI,少用是因为就算是支持dataURI的浏览器之间,对它的支持情况也不尽相同,比如支持的格式,大小的区别。因此要用这个,你必须对他们之间的差异很熟悉,才能保证使用后不会出现兼容性问题。还有一些问题就是维护起来比较麻烦,无法缓存,一般图片的数据会很大,增加页面体积。优势在于它是内嵌在文件中的,所以可以减少http请求,可以内嵌到html,css或JavaScript中,可移植性强(主要是不用为路径问题烦恼)。使用方法也很简单,如下:

?View Code JAVASCRIPT
1
2
//直接将src属性赋值为图像的dataURI即可
img.src='data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==';

附上dataURI转换工具:datauri-0.2.1.jar使用说明

补充说明:如果传递给image参数的图像是一个动画图像,那么绘制出来的是动画的poster frame或者第一帧(poster frame不存在时);如果image参数的值是一个HTMLVideoElement对象,那么canvas上绘制出来的是该对象当前播放位置对应的帧。

写在后面
这次的内容较少,只涉及到图像的变形跟裁切,下一篇文章会讲到图像数据的处理,像素级的,比较高级些,所以留点篇幅哈。最后还是用所学的知识再献上一个demo。查看demo

canvas JavaScript API学习(三)

写在前面
介绍过一些基本的图形绘制api和比较复杂的贝塞尔曲线的绘制方法,接下来要了解的是如何在canvas里写入文本。为什么要学习这个?canvas跟其他标签不一样!一般的,我们在标签里这样写:

1
<div>这是一些文字</div>

上面的代码在页面上就可以直接看到我们写的文字,并且可以通过给这个标签添加样式来改变文字的显示效果,但canvas不行,在canvas标签里的文字只有在不支持canvas的浏览器中才会显现出来,一般用来提示该浏览器不支持canvas。这也是该标签独特的地方,决定这点的就是它的设计初衷:绘图。

canvas文字绘制api概述
虽说canvas的主要目的是用来绘图,而且标签里的文字也无法显示出来,但不代表它不支持在canvas绘制的图形中插入文本,只是方法和平常的不一样,文本也被当做图形来处理,因此要插入文本,也需要通过对应的api来绘制。

下面是一些函数原型及相关属性:

context . font [ = value ]
返回当前字体设置,该值是可写的,可用来设置字体。语法与css中的”font”属性一样,无法被解析为css font值的字体将被忽略。

context . textAlign [ = value ]
返回当前文本对齐方式,该值是可写的,可用来改变文本水平对齐方式。可用的值有:start, end, left,right和center,其他值无效, 默认为start。

context . textBaseline [ = value ]
返回当前底线的对齐方式,该值是可写的,可用来改变底线的对齐方式。可用的值有:top,hanging,middle,alphabetic,ideographic,bottom,其他值无效,默认为alphabetic。

context . fillText(text, x, y [, maxWidth ]
context . strokeText(text, x, y [, maxWidth ] )
在指定的位置(坐标值[x,y])分别用fill或stroke方式描绘所给的文本。如果给定maxwidth值,在文本宽度超过该值的时候,文本会被拉伸来适应这个值。

metrics = context . measureText(text)
返回当前字体下所给的文本的度量的一个TextMetrics对象

metrics . width
返回传递给measureText()函数的文本的宽度

canvas文字绘制api解析
在了解了相关的知识后,你可能对前面提到的内容还有些疑问,接下来我来一一演示各个函数与属性的用法。先来看一个最简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
<head>
<meta  charset="utf-8" />
<title>Canvas javascript api demo</title>
<style type="text/css">
canvas {
	border:1px #000 solid;
}
</style>
 
</head>
<body>
<h1>this is a canvas api test page!</h1>
<canvas id="canvas" width="300" height="300">this browser does not support canvas...</canvas>
<a href="http://blog.wanz.im/2010/04/19/canvas_javascript_api_learning_three/">返回文章</a>
<script type="text/javascript">
var $=function(id){
	return document.getElementById(id);
}
window.onload=function(){
	var ctx=$('canvas').getContext('2d');
	ctx.font="12px sans-serif";
	ctx.fillText("IE你叫我情何以堪呐!", 10, 50);  	
}
</script>
</body>
</html>

这个代码你可以自己测试,我就不给出demo了,虽然代码很简单,但是还是有一个问题需要提醒一下,就是你在设置font值的时候,尽量要把你想要使用的字体,样式,大小,间距等写全,因为这里的设置会覆盖之前的内容。所以,如果你只是想改变字体大小,但是你写的是ctx.font=”12px”,这样是不行的,在Firefox下,字体会变成浏览器默认的字体,大小为12px,而在WebKit浏览器中,这个设置将不会生效,font的值依然是默认值:10px sans-serif。

fillText函数很简单,各个参数的含义见上面的描述,文字的颜色使用的是当前环境的fillStyle值。字体属性由font确定,对齐方式由textAlign指定,底线则是通过textBaseline设置。strokeText与其对应,相应的文字的颜色的设置是strokeStyle,你可以自己对比他们的差别。BTW貌似名字带stroke的相关函数描边的宽度都是2px,如果你看过之前文章里的介绍并做了测试,你就会发现这点,这也是我一直很困惑的一点。

fillText和strokeText的y值也就是纵坐标还有一点更让我疑惑的是文字出现在垂直方向的位置比给定的值少了10px;

Texts at the coordinate(10,20)

Texts at the coordinate(10,20)


这里我给定的坐标是(10,20),即:ctx.fillText(texts,10,20); 但实际y方向的位置少了10px

对于textAlign的start跟end值你可能会比较陌生,因为这两个值是在css3里才出现的,对齐的结果你可以自己试试比我的文字说明会更清楚点。

textBaseline对应的六个值所呈现的底线样子我想通过一张图就可以清楚的表达了,如下图,点击图片可以查看大图:

baselines

baselines

measureText返回的是一个TextMetrics对象,该对象目前只有一个只读的属性width,通过该属性就可以得到文本的宽度,单位为px。来看一个demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html>
<head>
<meta  charset="utf-8" />
<title>Canvas javascript api demo</title>
<style type="text/css">
canvas {
	border:1px #000 solid;
}
</style>
 
</head>
<body>
<h1>this is a canvas api test page!</h1>
<canvas id="canvas" width="300" height="300">this browser does not support canvas...</canvas>
<a href="http://blog.wanz.im/2010/04/19/canvas_javascript_api_learning_three/">返回文章</a>
<script type="text/javascript">
var $=function(id){
	return document.getElementById(id);
}
window.onload=function(){
	var ctx=$('canvas').getContext('2d');
	ctx.font="12px sans-serif";
	m=ctx.measureText("IE你叫我情何以堪呐!")
	alert(m.width);  	
}
</script>
</body>
</html>

同样是很纯很天真的代码,但是我在Firefox3.6.3,Chrome5.0.356.2和Safari4.0(530.17)中得到结果分别为:120,120,119;然后我将font的大小改为10px,得到的结果分别为:100,120,100;前一步基础上又将字体改为Arial,得到的结果分别为:100,119,100,最后把大小改为12px,得到的结果为:119,119,119。实在有点汗颜,看来对这个方法的计算方式还有的研究。

measureText_in_browers

measureText_in_browers

最后一点遗言
对于canvas文本绘制方面的相关函数就介绍到这里,更多内容就靠自己多查找资料多实战了,对于今天学习的东西实在有点出乎我的预料,因为正如文章中提到的那样,还有很多疑惑尚未解决,希望有高手给我释疑啊。最后还是以一个demo来结尾吧!查看demo

canvas JavaScript API学习(二)

上一篇文章中,我简单介绍了使用JavaScript在canvas中绘图的过程。接下来的内容是我对canvas绘图进一步学习心得,这次会介绍更多API方法的使用,来提高我们绘图的能力,创建更多丰富的图形,如果你有一点点图形学基础,那将对你的学习有很大帮助。

写在前面
看过前面的那篇文章,你就大概了解使用JavaScript的canvas api来绘图是怎么一回事。你需要得到一个渲染环境(rendering context),然后在指定位置通过api画出你要的图形。这里涉及到canvas的“网格”或叫“坐标系”。

Canvas_default_grid

Canvas_default_grid


如图中所示的,canvas坐标系的原点就是canvas左上角坐标(0,0)的位置,所画图形的位置就是相对这个原点水平偏移x个像素,垂直偏移y个像素的位置。

canvas绘图:矩形
canvas只支持一种图形的绘图方法,那就是矩形,其他的图形都是通过路径(path)构造出来的,因此,在以后的绘图过程中,你可能更多的要使用它来构建复杂的图形,这就是你需要一点图形学的基础的原因。

与矩形绘图相关的函数有:

fillRect(x,y,width,height) : 填充的矩形
strokeRect(x,y,width,height) : 矩形框
clearRect(x,y,width,height) : 清除指定区域使之透明,该区域是矩形区域

上一篇文章中我们演示了fillRect的用法,也提到了clearRect,具体的内容请查看之前的文章或查看demo:html5_canvas_basic html5_canvas_rebound

strokeRect函数的用法与其他两个一样,直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
<head>
<meta  charset="utf-8" />
<title>Canvas javascript api demo</title>
<style type="text/css">
canvas {
	border:1px #000 solid;
}
</style>
 
</head>
<body>
<h1>this is a canvas api test page!</h1>
<canvas id="canvas" width="300" height="300">this browser does not support canvas...</canvas>
<a href="http://blog.wanz.im/2010/04/17/canvas_javascript_api_learning_two/">返回文章</a>
<script type="text/javascript">
var $=function(id){
	return document.getElementById(id);
}
window.onload=function(){
	var ctx=$('canvas').getContext('2d');
 	ctx.strokeStyle='rgb(255,0,0)';
	ctx.strokeRect (10, 10, 50, 50); 
}
</script>
</body>
</html>

代码很简单,strokeStyle用法同fillStyle,用来设置边框颜色,同样支持rgb或rgba值,十六进制值(如:#F00)和hsl或hsla值。但是你可能注意到了,我们设定的矩形框宽度和高度都是50px,但是实际绘图出来的是52px,边框宽度为2px,如下图的测量结果,这显然不是我们想看到的。不知道为什么要这样设计,希望有知道的朋友可以告诉我一下,先谢过了。

strokeRect_border

strokeRect_border_extra_width

canvas绘图:使用路径(path)绘图——线段
正如前面所说的,只有矩形是可以直接使用api函数绘出的,其他的图形都需要通过路径来构造。要使用路径来绘图,我们需要先了解下面几个函数:

beginPath()
closePath()
stroke()
fill()

beginPath函数是为画路径而准备的,是一个初始化的过程,在调用函数后就可以开始画路径了,所有的用来构成一个图形的路径将被保存起来,每调用一次这个函数,之前保存的路径就都被重置清空。

closePath函数用来得到将被画出的路径,并且可以用来闭合路径。

stroke函数是可选的,该方法可以配合closePath函数,它会尝试将路径闭合,在当前位置到起始位置用一条直线连接起来。如果路劲已经闭合或者当前“画布”上只有一个点,那么这个函数将不起任何作用。

fill函数使用fillStyle的颜色来填充图形,使用这个函数可以不需要调用closePath函数而直接闭合路径。

让我们看看下面的代码,查看demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html>
<head>
<meta  charset="utf-8" />
<title>Canvas javascript api demo</title>
<style type="text/css">
canvas {
	border:1px #000 solid;
}
</style>
 
</head>
<body>
<h1>this is a canvas api test page!</h1>
<canvas id="canvas" width="300" height="300">this browser does not support canvas...</canvas>
<a href="http://blog.wanz.im/2010/04/17/canvas_javascript_api_learning_two/">返回文章</a>
<script type="text/javascript">
var $=function(id){
	return document.getElementById(id);
}
window.onload=function(){
    var ctx=$('canvas').getContext('2d');
    ctx.beginPath();  
    ctx.moveTo(75,50);  
    ctx.lineTo(100,75);  
    ctx.lineTo(100,25);  
    ctx.closePath();//使用closePath配合stroke闭合路径
    ctx.stroke();  
 
    ctx.beginPath();  
    ctx.moveTo(140,30);  
    ctx.lineTo(140,75);  
    ctx.lineTo(160,25);  
    ctx.stroke();  
 
    ctx.beginPath();  
    ctx.moveTo(100,100);  
    ctx.lineTo(200,100);  
    ctx.lineTo(100,200);  
    ctx.fillStyle='#f00';
    ctx.fill();  
 
}
</script>
</body>
</html>

代码中除了前面介绍的四个函数外,还提到moveTo跟lineTo两个函数。这两个函数都只带两个参数,分别为canvas上某点的坐标值。moveTo什么都不做,如果把canvas解释成一块画布,那么它的作用就是将画笔移动到画布上的某个位置,在canvas初始化时或者beginPath被调用时,画笔的默认位置是canvas坐标系的原点。lineTo函数用来画一条线段,该函数的两个参数指定了线段的终点坐标,起始坐标为moveTo指定的位置或者上一个路径的终点。

canvas绘图:使用路径(path)绘图——圆弧
我们使用arc函数来画圆弧或一个完整的圆,这个函数有6个参数,函数原型如下:

arc(x, y, radius, startAngle, endAngle, anticlockwise)

参数x和y是圆心坐标值,radius为圆半径;startAngle和endAngle为圆弧起始位置弧度和和终点位置弧度,该值为弧度而不是角度值,角度转弧度的公式为:var radians = (Math.PI/180)*degrees;anticlockwise是一个布尔值,当它为true时,圆弧绘制方向为逆时针方向,false是为顺时针方向。

圆或圆弧的绘制相对前面提到的那些图形稍微复杂了点。查看demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<!DOCTYPE html>
<html>
<head>
<meta  charset="utf-8" />
<title>Canvas javascript api demo</title>
<style type="text/css">
canvas {
	border:1px #000 solid;
}
</style>
 
</head>
<body>
<h1>this is a canvas api test page!</h1>
<canvas id="canvas" width="300" height="300">this browser does not support canvas...</canvas>
<a href="http://blog.wanz.im/2010/04/17/canvas_javascript_api_learning_two/">返回文章</a>
<script type="text/javascript">
var $=function(id){
	return document.getElementById(id);
}
window.onload=function(){
	var ctx=$('canvas').getContext('2d');
	ctx.beginPath();  
	var x= 50;               // x coordinate  
	var y= 50;               // y coordinate  
	var radius= 20;                    // Arc radius  
	var startAngle= 0;                     // Starting point on circle  
	var endAngle=(Math.PI/180)*120;
	var anticlockwise= true; // anticlockwise       
	ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);  
	ctx.stroke();
 
	ctx.beginPath();  
	var x= 150;               // x coordinate  
	var y= 50;               // y coordinate  
	var radius= 20;                    // Arc radius  
	var startAngle= 0;                     // Starting point on circle  
	var endAngle=(Math.PI/180)*120;
	var anticlockwise= false; // clockwise    
	ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);  
	ctx.stroke();
 
	ctx.beginPath();  
	var x= 150;               // x coordinate  
	var y= 125;               // y coordinate  
	var radius= 50;                    // Arc radius  
	var startAngle= 0;                     // Starting point on circle  
	var endAngle=(Math.PI/180)*360;
	var anticlockwise= false; // clockwise or anticlockwise       
	ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);  
	ctx.fillStyle='rgba(255,0,0,0.5)';
	ctx.fill();
 
	ctx.beginPath();  
	var x= 125;               // x coordinate  
	var y= 150;               // y coordinate  
	var radius= 50;                    // Arc radius  
	var startAngle= 0;                     // Starting point on circle  
	var endAngle=(Math.PI/180)*360;
	var anticlockwise= false; // clockwise or anticlockwise       
	ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);  
	ctx.fillStyle='rgba(0,255,0,0.5)';
	ctx.fill();
 
	ctx.beginPath();  
	var x= 175;               // x coordinate  
	var y= 150;               // y coordinate  
	var radius= 50;                    // Arc radius  
	var startAngle= 0;                     // Starting point on circle  
	var endAngle=(Math.PI/180)*360;
	var anticlockwise= false; // clockwise or anticlockwise       
	ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);  
	ctx.fillStyle='rgba(0,0,255,0.5)';
	ctx.fill();
}
</script>
</body>
</html>

canvas绘图:使用路径(path)绘图——贝塞尔二次和三次曲线
通过使用这两种曲线绘制的api,你可以画出更复杂的图形。它们的区别在于二次曲线只有一个控制点,而三次曲线有两个。你可以在wiki上查看更多关于贝塞尔二次和三次曲线的资料。它们的函数原型如下:

quadraticCurveTo(cp1x, cp1y, x, y) //二次曲线,在Firefox 1.5下有bug
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)//三次曲线

Canvas_curves

二次曲线和三次曲线


红色为控制点,蓝色为起点跟终点

两个函数的x和y参数都是终点坐标值,cp1x和cp1y是第一个控制点的坐标值,cp2x和cp2y是第二个控制点的坐标值,起点坐标可以用moveTo来确定。

虽说这两个函数能构造出复杂的图形,但是使用二次和三次贝塞尔曲线是有很大难度的,因为你根本无法想象一个复杂的图形,它所对应的曲线参数是多少,无法直观的得到它们。要做到这点,你需要有足够的时间和耐心慢慢尝试,或者借助图形学工具来得到这些参数。接下来我来演示一个比较复杂的demo。查看demo

canvas_poker

canvas_poker

上面的这个demo比较复杂,原因在于前面说的,你必须很清楚的知道贝塞尔曲线的各个参数,这里我借助了我上大学时候图形学的课程设计的帮助得到这些必要的参数的,希望它也能帮到你。这个工具文件只有28k,请使用右键另存为下载:贝塞尔二次和三次曲线生成工具
先看看工具界面,各个节点都可以调整到你所希望的样子,并有对应的坐标值。

bezierCurvetool

bezierCurvetool

Firefox 1.5 quadraticCurveTo() bug
这个bug出现在Firefox1.5中,在调用quadraticCurveTo函数的时候,得到的却是bezierCurveTo函数一样的结果。这是核心中将二次的控制点重复使用一次导致的,正如前面所说的,二次只有一个控制点,这里却变成了两个。解决的办法是将原本使用二次曲线函数的方式用三次曲线函数来表示该曲线。

canvas JavaScript API学习(一)

<canvas>是html5规范中的标签,通过JavaScript脚本可以在canvas中绘画出图形或实现动画效果。更多canvas的资料请查看:The canvas element — HTML5 (including next generation additions still in development)

写在前面
对于canvas的历史我就不多赘述,Google比我知道的多!我们直接从实例开始我们的旅程。本文虽然是讲canvas,但更多的是JavaScript内容,因此要继续下去,你需要一点JavaScript基础。现在<canvas>并没有被所有浏览器支持,你需要一些支持html5的浏览器来测试文中的例子(如:Firefox1.5+,较新版本的Safari或Chrome以及opera9+等)。本人使用的浏览器是Firefox3.6.3并在此环境下做所有测试。如果你想让ie也支持可以使用explorercanvas,用vml来描述canvas的内容,使用起来很简单,只要加入下面代码即可。

1
<!--[if IE]><script src="excanvas.js"></script><![endif]-->

Hello,canvas!
首先,我们来看一段最简单的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
<head>
<meta  charset="utf-8" />
<title>Canvas demo</title>
<style type="text/css">
canvas {
	border:1px #000 solid;
	/*width:300px;
	height:300px;*/
}
</style>
</head>
<body>
<h1>this is a canvas api test page!</h1>
<canvas id="canvas" width="300" height="300">this browser does not support canvas...</canvas>
</body>
</html>

当你在支持html5 canvas的浏览器下查看页面的时候,你会看到一个大小为300px*300px(BTW:canvas默认大小为:300px*150px),边框为1px宽的方框,而在ie下,你只能看到一句话:this browser does not support canvas…。正如你所看到的那样,canvas跟其他标签一样,可以通过css来定义样式,也可以用它本身支持的html属性来定义宽和高。但是这里有一点需要注意:在css中为canvas定义宽高,实际是将canvas进行拉伸,因此,如果在这样的情况下在canvas中绘图,你得到的图形可能就是变形的。所以建议直接在canvas中用html属性来定义canvas的大小。

canvas with JavaScript
接下来,我们来演示如何通过JavaScript在canvas里绘图。查看demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html>
<head>
<meta  charset="utf-8" />
<title>Canvas javascript api demo</title>
<style type="text/css">
canvas {
	border:1px #000 solid;
	/*width:300px;
	height:300px;*/
}
</style>
</head>
<body>
<h1>this is a canvas api test page!</h1>
<canvas id="canvas" width="300" height="300">this browser does not support canvas...</canvas>
<script type="text/javascript">
var $=function(id){
	return document.getElementById(id);
}
window.onload=function(){
	var ctx=$('canvas').getContext('2d');
	ctx.fillStyle='rgba(255,0,0,1)';
	ctx.fillRect (10, 10, 50, 50); 
	ctx.fillStyle = "rgba(0, 255, 0, 0.5)";  
	ctx.fillRect (30, 30, 50, 50);  
	ctx.fillStyle = "rgba(0, 0, 255, 0.5)";  
	ctx.fillRect (50, 50, 50, 50); 
}
</script>
</body>
</html>

canvas提供了一个接口来实现一种或多种的渲染环境(rendering context),但目前还只实现了2D渲染环境。通过这个接口,我们就可以在canvas中绘图了。那么在绘图前,我们先取得这个环境:

?View Code JAVASCRIPT
1
var ctx=$('canvas').getContext('2d');//通过canvas的id取得canvas并调用getContext方法得到2d渲染环境,目前只有2d为有效值。

代码中getContext(’2d’)方法返回CanvasRenderingContext2D对象,该对象拥有的方法和属性可以参考文章一开始提到的url。这里我们简单介绍fillStyle属性跟fillRect方法,更多用法我会在后面的学习中演示。

fillStyle是为当前环境配置填充颜色,它支持rgb值或rgba值或十六进制值(如:#F00),也支持hsl或hsla值,默认为黑色;
fillRect定义了填充区域为矩形,并由四个参数来设置填充的位置及大小,第一二两个参数为相对canvas左上角的坐标值,三四两个参数为所填充的矩形区域的宽和高,填充颜色使用fillStyle值。与之相反的方法就是clearRect,用来擦除指定区域内容,参数及用法与fillRect类似。

最后,我们来用所学的知识写一个简单的动画实例:查看demo