仰望星空的天台

0%

HTML5学习历程

写在前页面

关于H5 C3 JS API 的浏览器版本支持度 可以上caniuse 网站查询

HTML5初始化代码


<DOCTYPE html>
<html>
<head>
<meta charset="utf-8">	
<title>html5</title>

</head>
<body>
	<header>
		页面头部
	<hgroup>
		定义一组标题组合
		<h1>Owen的博客</h1>
		<h2>仰望星空的天台</h2>
	</hgroup>
	</header>
	<article>
	用来语意化一篇文章主体
		<aside>侧边栏</aside>
		<section>内容区</section>
	</article>
	<footer>页面底部</footer>
</body>
</html>

比起之前的版本 简化了很多

主要是用于语意化规范 那么语义化规范有什么用呢?

语意化标签的作用

1.便于SEO爬虫爬取你的页面(SEO优化)

首先,在语意化标签出来之前 我们是通过CSS给标签添加样式之后 (或者id和class稍微的语意化) 才知道每个布局是干什么的

但是搜索引擎(Search Engine) 是看不到我们整个页面的布局的 所以就很难爬取到有用的信息 这样检索信息的时候,你的页面就很难被找到 没被找到基本上你的网站就很难和大众接轨了

所以html标签的语意化有利于SEO找到你页面的关键信息

世界上还举办了这么个节日 CSS Naked Day CSS裸奔节

用以推动Web标准、提倡简洁为美、使用正确的(x)html语义标记、良好的层次结构。

2.便于网站维护人员维护页面

其次,如果当公司让你去维护一个不是你自己写的页面 这时候标签是清一色的 div 或者是 td 标签 你这时候就会觉得眼花缭乱

有可能把作者叫过来看,他自己也忘了啥是啥了 但是如果我们给加的标签自身添加了语义化的功能

我们维护的时候,一看标签就知道是干什么的了 这样工作也会轻松很多

3.有利于一些特殊终端的阅读

再来,我们介绍一些计算机的特殊终端:打印机、扫描器、手机等等 如果需要这些特殊的终端设备识别你的页面 标签语义化也是必不可少

其他的一些语义化标签

figure


用于对元素组合
说白了一般就是用来装<img><video>
figure 元素的内容应该与主内容相关,但如果被删除,则不应对文档流产生影响。
figcation的子元素 用来解释

time

用来标记时间

<p>
	庆祝抗日战争胜利70周年,阅兵日<time> 2015-09-03 </time>
</p>

<p>
	我的<time datetime = "1996-03-05">生日</time>
</p>

datalist 和 option

选项列表 当你在表单输入时 自动弹出可能值列表


<input type="text" list="valList" />
<datalist id="valList">
 	<option value="javascript">javascript</option>
   <option value="html">html</option>
  <option value="css">css</option>
</datalist>

这里又引出了一个新的属性”list” 用来关联可能值列表

summary 和 details

用于列出文档列表

<details>
<summary>点我,就送屠龙宝刀</summary>
乖乖努力打怪升级,自然就有
</details>

可以加一个属性Open 使列表默认是展开状态

但是,这个标签暂时还只有Chrome 和 Safari 支持

dialog

这对标签一般用来语意化一段对话


<dialog>
  <dt>老师</dt>
  <dd>2+2 等于?</dd>
  <dt>学生</dt>
  <dd>4</dd>
  <dt>老师</dt>
  <dd>答对了!</dd>
</dialog>

address

用来语意化页面作者和他们的详细信息

mark

高亮某一段需要高亮的字词或是段落 可以在CSS中使用background修改高亮颜色

keygen

可以使用keygen为发送的表单添加一个公共密钥

<form action="http://numerhero.github.io" method="get">
用户: <input type="text" name="usr_name" />
公钥: <keygen name="security" />
<input type="submit" />
</form>

progess

可以定义一个简陋的进度条

<progress max="100" value="77">
     <!-- 这里用以兼容 不支持该标签的浏览器 -->
     <span>77</span>%
</progress>

如何向下兼容IE8以下版本

因为浏览器允许我们自定义标签
比如

<Owen> 每天都萌萌哒 </Owen>

但是自定义的标签没有任何默认样式
需要自己去添加

虽然旧的浏览器版本没有HTML5这些标签
但是我们可以通过JS脚本创建,并给他们添加CSS属性

这里Google开发了一个兼容脚本:

点击下载html5shiv

只需外部引用一下就能实现兼容了

新增的表单属性

email


<form>
	<input type="email">
	<input type="submit">
</form>

相当于给输入框加了一个正则表达式
用来粗略的区分你输入的是否是一个正确的邮箱地址
(要真正判断 还得配合AJAX)

对于手机端,还会有一个输入法的转换
当你要输入有邮箱的时候,点击email表单
会从其他语言的输入法 转换为英文输入法

tel


<form>
	<input type="tel">
	<input type="submit">
</form>

对于PC端,没有什么特殊变化

对于手机端,还会有一个输入法的转换
当你要输入有电话的时候,点击tel表单
会从其他语言的输入法 数字键盘

一些可选属性:
maxlength : 限制最大值
readonly : 默认只读不可修改
size : 输入数字大小
placeholder : 默认提示
dirname : 

url


<form>
	<input type="url">
	<input type="submit">
</form>

和email一样
相当于给输入框加了一个正则表达式
用来粗略的区分你输入的是否是一个网址

对于手机端,还会有一个输入法的转换
当你要输入有网址的时候,点击url表单
会从其他语言的输入法 转换为英文输入法


<form>
	<input type="search">
	<input type="submit">
</form>

搜索框属性
当你输入的时候,
会出现清空内容的按钮
可以配合其他属性使用

range


<form>
	<input type="range">
	<input type="submit">
</form>

数值选择器属性
可以用来给我们选择数值
它底下有这么些属性
step : 数值跨度,即每次数值增加量 默认是1
min  : 最小数值
max  : 最大数值
value: 缺省数值值

其中Opera 浏览器 还特意为range 做了数值刻度

number


<form>
	<input type="number">
	<input type="submit">
</form>



另外一个数值选择器属性
也可以用来给我们选择数值
它底下也有这么些属性
step : 数值跨度,即每次数值增加量 默认是1
min  : 最小数值
max  : 最大数值
value: 缺省数值值

color


可以供我们使用的一个调色板插件
<form>
	<input type="color" id="incolor" >
</form>

可以添加的值
disabled : 是否可用
autocomplete :弹出自动候选色(和datalist用的时候使用)
autofocus : 是否获取焦点
list : 关联datalist列表 可以给用户产生候选颜色
value : 缺省色

=== Javascript 简单调用 ===

window.onload = function()
{
	var InputColor = document.getElementById('incolor');
	//使用oninput对象调用
	InputColor.oninput = function()
	{
		document.body.style.backgroundColor = this.value;
	}
}

date


<form>
	<input type="date" min="1996-03-05" max="2004-09-22">
</form>

一个插入日期的控件

一些可选属性
min : 最小日期
max : 最大日期

datatime-local


用法和date差不多
用于选取本地时间
<form>
	<input type="datetime-local" >
</form>

但是貌似没有min 和 max 属性

time、week、month

data可以向下细分为

time : 只显示时间 week : 只显示星期 month: 只显示月

一些表单的属性

placeholder : 提示信息

autocomplete : 历史输入信息 (on / off) autofocus : 打开页面自动获取表单焦点 直接加(例子如下) require : 强制用户填写拥有该属性的表单 直接加(注意:这里直接加的话写的是required 多个d)

pattern : 简单的表单正则表达式(列如:pattern=”\d{1,5}” 限制表单中只能填入1-5个数字) (注意:由于require 和 pattern 是写在行间的属性,所以使用浏览器的修改功能 可以被用户轻易的破除)

formaction : 用来添加一个新的提交路径 formnovalidate : 关闭验证,直接提交


<form action="http://www.baidu.com">
	<input type="text" placeholder="输入的内容"  pattern="\d{1,5}" autofocus required  autocomplete="off">
	<input type="submit" value="提交百度">
	<input type="submit" value="提交腾讯" formaction="http://www.qq.com">
</form>

Javascript表单验证

首先我们需要讨论一下为什么要需要用到JS表单验证

例如这么一个表单


<form action="www.baidu.com">
	<input type="text" pattern = "/d{1,5}" required autocomplete="on" autofocus="on">
	<input type="submit" value="发送">
</form>

shootpic

因为浏览器自带可修改代码的功能 (本来是给我们工作人员调试用的)

所以用户可以轻易的通过修改代码, 顽皮的跳过一些重要的数据采集

因此,我们需要在JS代码添加一些 简单的验证

其实用户还能改JS代码,让前端的JS部分失效 所以,前后端对表单的双重验证是必要的常识! (毕竟用户改不了服务器端的代码嘛)

但是,表单验证不单单验证信息这么简单

比如说你实现一些功能 例如,没有输入直接发送的时候 通过JS的DOM操作弄个非浏览器默认样式的 更加美观的提示框

那么再使用if语句的时候 就需要用到JS表单验证返回的布尔值

还有调试的时候,检验你的正则是否写错啊 表单类型和你输入的值格式是否匹配啊等等


表单一共有11种验证
首先,表单验证都是围绕一个叫validity(英译:有效的、健全的)对象进行的
以下11种验证方法都是validity对象底下的

valid : 独立于其他验证,用来检测其他验证是否通过,
(如果其他验证全部通过,那么valid返回true 否则返回false)

下列属性,如果表单内容不通过则返回true 否则返回false (valid相反嘛)
badInput : 
valueMissing  :  输入值为空时 返回true
typeMismatch :  控件值与预期类型不匹配时 返回true
patternMismatch  :  输入值不满足pattern正则时 返回true

=== html ===

<form>
	<input type="text" id ="text" pattern = "/d{1,5}" required autocomplete="on" autofocus="on">
	<input type="submit" value="发送">
</form>

=== Javascript === 

window.onload = function()
{
	var oText = document.getElementById("text");
	//这里使用事件监听
	oText.addEventListener("invaild" , fn , flase);
	//阻止下默认事件
	event.preventDefault();
	function fn()
	{
		console.log(oText.validity.valueMissing)
		当啥都没输的时候 弹出true

		console.log(oText.validity.patternMismatch)
		当输入的值 不满足你写的正则函数的时候 返回true

		console.log(oText.validity.typeMismatch)
		当输入的值 和你 表单需要填写的值 类型不同的时候返回true
		比如说input类型是email 但是用户输的值不是一个email
	}	

}


tooLong  :  超过maxLength最大限制时 返回true
rangeUnderflow : 验证的range最小值 
rangeOverflow:验证的range最大值 
stepMismatch: 验证range 的当前值 是否符合minmaxstep的规则

(感觉以上4项功能比较鸡肋,因为当你错误的时候浏览器会帮你设置缺省值,也就是说几乎不会有错误的时候
所以这里就不做demo)

customError 不符合自定义验证
setCustomValidity(); 自定义验证

=== Javascript === 

window.onload = function()
{
	var oText = document.getElementById("text");
	//这里使用事件监听
	oText.addEventListener("invaild" , fn , flase);
	//阻止下默认事件
	event.preventDefault();
	function fn()
	{
		if(this.value == "敏感词汇")
		{
			this.setCustomValidity("嘘,可不能乱填一些带颜色的词语哟");
			//这样可以限制用户的填写
		}
		else
		{
			this.setCustomValidity("");
			//注意 : 当用户没有输入敏感词的时候,一定要把自定义验证清空
			//不然不论用户怎么输入都会弹出自定义的提示信息,也会弹出自定义的验证信息
		}

		//console.log(oText.validity.customError);
		//当用户确实输入了一些敏感的词后 返回true
	}	

}


当有些特殊的时候,暂时不需要验证
比如当用户赶时间的时候,表单没有填写完,需要保存到草稿箱的时候
我们就需要给保存到草稿箱那个submit 按钮添加一个formnovalidate属性来关闭所有的表单验证

新的DOM选择器

querySelector(类jQuery DOM选择器):

学过JQuery的同学应该比较清楚 在JQ中 获取元素很简单

$(‘#box’) $(‘.box’) $(‘ol li’);

到了HTML5中,DOM 允许我们使用JQ的方法去获取元素


通过获取ID 来获取元素

var oDiv = document.querySelector('#box');


通过获取类名 来获取元素

var oDiv = document.querySelector('.box');

但是注意一点
当获取类元素的时候,是不支持两个获取的
也就是说如果有两个DOM结点 都有class="box"
那么querySelector 无法获取,会返回undefined


通过获取属性名称来获取元素

比如<div tilte="Owen"></div>
我们也可以通过querySelector获取

var oDiv = document.querySelector('[title=Owen]');

甚至是自定义属性<div abc="Owen"></div>

var oDiv = document.querySelector('[abc=Owen]');
(如果有多个同样具有 abc="Owen" 属性的标签,则获取到的仅是第一个)

获取继承标签组

虽然querySelector不能获取多个类 DOM元素
但是可以获取 继承标签组的多个DOM元素
<ul id="ul1">
	<li></li>
	<li></li>
	<li></li>
</ul>

var oLi = document.querySelector('ul li');
console.log(oLi.length);

或者

你也可以
var oUl1 = document.querySelector('#ul1');
var oLi = oUl1.querySelector('li');
console.log(oLi.length);

querySelectorAll


上面,我们说到不能通过querySelector获取多个同类元素
但是我们可以通过querySelectorAll方法来获取多个同类元素


var oDivs = document.querySelectorAll('.box');
console.log(oDivs.length);

同样的
我们也可以使用querySelectorAll()
来获取多个 甚至是自定义的属性

<ul>
	<li abc="Owen" ></li>
	<li></li>
	<li abc="Owen" ></li>
</ul>

var oLi = document.querySelectorAll('[abc=Owen]');
console.log(oLi);

同时也可以获取单个元素(id class)

    是否能识别id   是否能识别class   是否能识别多个class   是否能通过标签选择器识别单个元素
querySelector        
querySelectorAll        

续表:

    是否能识别多个标签组   是否能识别单个属性或自定义属性   是否能识别多个属性或自定义属性
querySelector      
querySelectorAll      

总结来说 使用querySelectorAll 更方便,但是注意querySelectorAll获取元素的时候,都是以数组为单位的 所以,当使用querySelectorAll 获取单个元素的时候 需要加上[0]; var box = document.querySelectorAll(“#div”)[0];

或者引用的时候再加[0]

var box = document.querySelectorAll(“#div”); console.log( box[0] );

还有一点需要注意的是,使用querySelectorAll / querySelector来获取HTML5新功能 自定义属性的时候 需要加上data-

<div data-Owen="abc" ></div>
var box = document.querySelectorAll("data-Owen=abc");
而不是 
var box = document.querySelectorAll("Owen=abc");

getElementsByClassName

类似getElementsByTagName

var oboxs = document.getElementsByClassName('box');
console.log(oboxs);

classList

我们可以通过classList来获取到一个元素的全部class

<div id = "box" class="a b c"></div>

var boxClass = document.getElementById('box').classList;
console.log(boxClass);// a b c

注意这里classList 是一个object 类数组 应该说很像json,我们不能同过classList.length++ 
或者 classList.push()的方法给 这个DOM结点添加类

但是,HTML5classLIst添加了一些方法来满足我们的需求

length : 属性数量(不能直接改变)
add() : 添加一个类

boxClass.add('d');
console.log(boxClass);

remove() : 删除一个类

boxClass.add('a');
console.log(boxClass);

toggle() : 切换一个类

假设我现在  a b c 三个类
如果我执行 boxClass.toggle('a'); 原来类表里面存在,就会删除a这个类
如果我执行 boxClass.toggle('d'); 原来类表里面不存在,就会添加d这个类

自定义属性

我们可以使用data来自定义属性

如果是HTML5之前
我们这样自定义属性
<div Owen = "abc"></div>
如果要用DOM控制的话就需要用到
getArribute来获取,但是这方法嘛 兼容性不太乐观了

而现在我们可以这样定义属性
<div  data-Owen="abc" data-Owen-Zyz = "cba" ></div>
(注意:这里一定是“ - ”号连接!)
(注意:浏览器在解析的时候,大小写不敏感,也就是说所有含有大写字母的,在解析中都会被变成小写字母)

但是在获取对象的时候,大小写又敏感了,可以获取到DOM

var box = document.querySelector('data-Owen=')
console.log( box.dataset.Owen ); // undefined
console.log( box.dataset.owen ); // abc
console.log( box.dataset.owen-Zyz ) // 报错
console.log( box.dataset.owenZyz ) // cba
console.log( box.dataset.owenzyz ) // undefined
console.log( box.dataset ) // { owen : abc , owenZyz : cba }

实验说明 浏览器在解析的时候采用驼峰命名法
并且使用自定义属性的时候一定要注意大小写的问题
或者说我们应该尽量在自定义属性的时候不使用大写

如果说一定要使用大写字母的时候,应先将dataset弹出来看一下具体的属性名
再进行DOM操作:

var obj = document.querySelectorAll('[data-Owen]');
console.log(obj.getAttribute('data-Owen'));//abc

通过getAttribute方法我们就可以利用html中传递过来的内容了

data这个属性广泛用于jQuery Mobile上 我们只需要添加jQuery Mobile 然后在标签里面配属性就基本可以完成一些手机端的开发 和一些简单的特效

<div data-theme="a" ></div> //把div设置成黑色

当然除jQuery Mobile 的库 还可以用Knockout 手机端库 Knockout

Css访问

Css也可以访问data属性

<article data-parent="cars"></article>
article::before {
  content: attr(data-parent);
}

但是这个功能对于大多数浏览器来说暂时还是Partial support

defer 和 async

这两个新属性是关于加载脚本的

比如我这里需要加载三个js


<script src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>

<body>
	...
</body>

如果,这几个js文件比较大,而碰巧我们可爱的用户网又比较慢 因为默认的加载方式是同步加载(关于同异步的请参考本站AJAX的有关内容) 这时候就会出现一段没有内容的空白期,这样就可能让一些没有耐心的用户停止等待了

defer=”defer”属性可以让加载的脚本延迟加载 也就是优先让页面先加载出来,然后再加载一些js功能

而async=”async”呢 就是让加载的脚本和页面异步加载,齐头并进 但是,这样就会存在一个问题.加设如果a脚本需要引用b脚本的内容 而a脚本 在 b脚本之前就已经加载完成了,那么在a脚本需要使用b脚本的地方 就会报错了

所以,当我们使用async属性的时候 , 就需要保证加载的几个脚本是互相独立的

历史管理

onhashchange

当我们需要在一个主页面内不断的切换内容(域名只是添加哈希值)也就是一站式页面的时候,应考虑用户需要历史管理的情况 当用户需要前后切换历史内容的时候,缺省的浏览器历史管理方法就无效了 这时候我们就需要调用HTML5中特定的onhashchange方法

demo-详细自行查看网页原码

原理是通过window.location.hash保存散列值,使每一次页面的url都不同, 使浏览器产生历史管理并且保存url和对应的随机数的映射关系,并且通过onhashchange,依据每次url的散列值来修改不同的数据。

history新对象

history是HTML5下提供在服务器端下用于保存不同内容的对象 (注意,history只能在服务器端下才能产生作用)

pushState( data , title , url[可选] );

history.pushState方法, 可以将页面的变化状态保存,并自动建立映射关系
参数: 第一个是保存的数据(必须填写) 第二个是保存的页面标题(也是必须填写) 第三个是url(选填)用于
标明页面变化。

window.onpopstate事件

用来读取对应的历史内容
需要调用事件对象event来调动window.onpopstate底下的state(也就是保存的页面内容)

demo:
window.onpopstate = function(ev)
{
	var oEvent = event || ev;
	而后调用oEvent.state
}


来看看再刚才的随机函数的例子:
(html相同,请调整到服务器端测试)

window.onload = function()
{
	var 
	oInput = document.querySelector("#Input"),
	oText = document.querySelector("#text");
	
	oInput.onclick = function()
	{
		
		var arr = random( 50 , 5 );
		history.pushState( arr,'' );    //这里从简,不设置标题和url
		oText.innerHTML = arr;
	}
	
	window.onpopstate = function(ev)
	{
		var oEvent = ev || event
		oText.innerHTML = oEvent.state;
	}
	function random(iAll , n)
	{
		一个数组池,从里面抽取n个数
		var arr = [];
		一个数组,用来存放抽取的随机值
		var newArr = [];
		
		压栈iAll个数
		for(var i=1 ; i<iAll ; i++)
		{
			arr.push(i);
		}


		for(var i=0; i<n ; i++)
		{
			从总池中抽取数,因为每次抽取都是出栈,所以将改变随机函数的取值范围,
			所以不会出现无值可取的情况	
			newArr.push( arr.splice(Math.floor(Math.random()*arr.length) , 1 ) );
		}
		return newArr;
	}
}

拖拽

HTML5支持拖拽 只需要在你需要拖拽的元素标签中添加draggable=”true”属性 那么这个元素就可以拖拽了

然后你可以在JS中添加拖拽过程中各个事件 事件的执行顺序:(当 事件drop 不触发,也就是不会在目标元素中添加元素的时候) dragstart > drag > dragenter > dragover > dragleave > dragend

当事件drop 触发的时候: dragstart > drag > dragenter > dragover > drop > dragend

让我们看看这些事件有什么用处

ondragstart

当你产生拖拽开始时触发(不是按下的时候)

ondragend

当你拖住结束的时候触发(鼠标放开的时候)

ondrag

在拖拽结束的时候触发(不管是否运动,只要你开始拖拽了,拉住的过程中一直都是ondrag)

ondragenter

当有其他元素拖拽进入的时候触发

ondragleave

当进入的元素出去的时候触发,(判断触发的标志是鼠标是否离开目标元素而不是被拖拽元素是否超出目标元素)

ondragover

当进入的元素 存在目标元素内(不放入 与drop相反)

drop

当进入的元素 存放在目标元素内时触发 ( 注意:要想触发drop 一定要在阻止dragover的默认事件ev.preventDefault()才能使drop触发 )

但是,火狐浏览器规定必须设置dataTransfer对象才可以拖拽除图片外的其他标签

dataTransfer

设置数据传输(一定是字符串) dataTransfer.setData(key , value) key(是数据下标) value(获取到对应的内容)

dataTransfer.getData(key) 从对象中根据key获取指定格式的元素数据

dataTransfer.clearData(key) 从dataTransfer对象中根据key值删除指定格式的数据,参数可选,若不给出, 则为删除对象中所有的数据。

addElement(element):添加自定义图标

effectAllowed : 当拖拽元素进入目标元素的时候鼠标的光标样式(none, copy, copyLink, copyMove, link, linkMove, move, all 和 uninitialized)

(demo:ev.dataTransfer.effectAllowed = ‘copy’)

setDragImage(element,x,y):设置拖放操作的自定义图标。其中element设置自定义图标,x设置图标与鼠标在水平方向上的距离,y设置图标与鼠标在垂直方向上的距离。

demo: 一个垃圾桶删除功能的demo 垃圾桶可以拖拽

拖拽demo

请在chrome下测试,不兼容火狐

file

HTML5允许用户从外部拖拽文件 ev.dataTransfer.files

拖拽进来的文件会被浏览器自动打开,这时候我们只需要阻止ondrop的默认事件就可以了

filesList 单次拖拽进来后会返回一个filesList(对象)列表 列表下 有这么几个属性

name : 文件名(包括后缀)
length : 单次拖拽进来文件的个数 
type : 拖拽进来文件的类型
size : 文件的大小
lastModifiedDate : 该文件的最后修改时间
webkitRelativePath : webkit相关路径

demo:

oDiv.ondrop = function(ev)
{
	var fs = ev.dataTransfer.files;
	console.log(fs[0].type);
	ev.preventDefault();
}

FileReader对象

当我们接受完文件之后,还需要能读取这些文件
所以,我们需要用到这么一个对象FileReader

FileReader下面有这么几个方法

readAsDataURL([format])读取fileList文件中的数据 format必填,fileList中的文件
FileReader().onload 加载该文件数据该方法中一个result的值,返回的是文件base64的内容

onloadend 当文件加载结束后执行
onloadstart 当文件读取的时执行
onprogress  当文件加载时执行


demo:
=== html ===
<div draggable="true" id="div1">将文件拖拽到此区域</div>
<ul id="ul1">

=== css ===
#div1{ width: 200px; height: 200px; background: red; margin: 100px; }
#ul1{ list-style: none;}

=== javascript ===
window.onload = function()
{

	var oDiv = document.getElementById('div1');
	var oUl  = document.getElementById('ul1');

	oDiv.ondragenter = function()
	{
		this.innerHTML = '可以释放鼠标';
		this.style.background = 'blue';
	}

	oDiv.ondragover = function(ev)
	{
		ev.preventDefault();
	}

	oDiv.ondragleave = function()
	{
		this.innerHTML = '将文件拖拽到此区域'
		this.style.background = 'red';
	}

	oDiv.ondrop = function(ev)
	{
		ev.preventDefault();
		var fs = ev.dataTransfer.files;
	
		for(var i = 0 ; i<fs.length ; i++)
		{
			if(fs[i].type.indexOf('image') != -1 )
			{
				var fd = new FileReader();

				//千万注意这里需要将fd放入循环当中
				//不然会报The object is already busy reading Blobs这样的错误
				
				fd.readAsDataURL( fs[i] );
				fd.onload = function()
				{
					var oLi = document.createElement('li');
					var oImg = document.createElement('img');
					//将数据赋值给图片元素的src 图片就能显示出来了
					oImg.src = this.result;

					oLi.appendChild(oImg);
					oUl.appendChild(oLi);
				}
			}else{
				alert('第' + i+1 + '个文件不是图片,请上传图片');	
				continue;
			}
		}
	}
}

一个购物车实例

废话不说,直接上demo

一个购物车实例

原码自己查看

同域窗口间通信的两种模式

什么叫同域呢 就是在同一个主机文件夹内的两个不同的页面html 如果我们需要对它们进行通信(信息交换) 这就是所谓的同域窗口间通信

注意两种模式都需要在服务器底下运作,所以我们需要在wamp服务器上搭建虚拟主机 具体方法百度上很多很详细

同域iframe窗口间通信

在一个页面内嵌套另外一个页面,在这个页面内部操作被包含的页面


=== html ===
<input type="button" id = 'btn' value="点我改变被嵌套页面的背景颜色">
<iframe id = "myframe" src="被嵌套的页面路径"></iframe>

=== 被包含的页面的html ===
<body>
  <h1>这是被包含的页面</h1>
</body>

=== Javascript ===
window.onload = function()
{
  var oBtn = document.getElementById('btn');
  var oMyIframe = document.getElementById('myframe');

  oBtn.onclick = function()
  {
    //如果我们要修改iframe底下的dom 就需要找到iframe引入的页面的window对象
    console.log(oMyIframe.contentWindow);
    oMyIframe.contentWindow.document.body.style.background = 'red';
  }
}

反过来,被嵌套的页面也可以来操纵包含他的页面

父级操作被包含的自己我们用的是contentWindow 但是子级操纵父级却有3个 window top parent

怎么理解呢? shootpic


=== html ===
<input type="button" id = 'btn' value="点我改变被嵌套页面的背景颜色">
<iframe id = "myframe" src="被嵌套的页面路径"></iframe>

=== 被包含的页面的html ===
<body>
  <h1>这是被包含的页面</h1>
  <input type="button" id="btn2" value="点我操纵包含我的页面">
</body>

=== Javascript ===
window.onload = function()
{
  var oBtn = document.getElementById('btn');
  var oMyIframe = document.getElementById('myframe');

  oBtn.onclick = function()
  {
    //如果我们要修改iframe底下的dom 就需要找到iframe引入的页面的window对象
    console.log(oMyIframe.contentWindow);
    oMyIframe.contentWindow.document.body.style.background = 'red';
  }
}

=== 被包含的页面的Javascript ===

window.onload = function()
{
  var oBtn2 = document.getElementById('btn2');
  
  oBtn2.onclick = function()
  {
    console.log(parent);
    parent.document.body.style.background = 'red';
  }
}

同域window.open窗口间通信

从一个页面内通过window.open打开另外一个页面 会返回一个对象,这个对象就是新打开页面的window对象


=== html ===
<input type="button" id = "btn" value="点我打开新窗口">
<input type="button" id = "btn2" value="点我改变新窗口的背景色">

=== 被打开的新窗口 === 
<body>
    <h1>我是被打开的新窗口</h1>
</body>

=== Javascript ===
window.onload = function()
{
  var oBtn = document.getElementById('btn');
  var oBtn2 = document.getElementById('btn2');
  var newWindow = null;
  oBtn.onclick = function()
  {
    //window.open会返回被打开窗口的window对象
    //但是这里有一点要注意,如果多次打开同新的页面
    //newWindow这个变量会被覆盖
    newWinodw = window.open('被打开的新窗口的src','_blank');
  }

  oBtn2.onclick = function()
  {
    newWinodw.document.body.style.background = 'red';
  }

}

如果过来需要被打开的子页面操作打开它的父级页面

我们可以使用window.opener这个方法


=== html ===
<input type="button" id = "btn" value="点我打开新窗口">
<input type="button" id = "btn2" value="点我改变新窗口的背景色">

=== 被打开的新窗口 === 
<body>
    <h1>我是被打开的新窗口</h1>
    <input type="button" id = "btn3" value="点我改变打开我的父级页面的背景色">
</body>

=== Javascript ===
window.onload = function()
{
  var oBtn = document.getElementById('btn');
  var oBtn2 = document.getElementById('btn2');
  var newWindow = null;
  oBtn.onclick = function()
  {
    //window.open会返回被打开窗口的window对象
    newWinodw = window.open('被打开的新窗口的src','_blank');
  }

  oBtn2.onclick = function()
  {
    newWinodw.document.body.style.background = 'red';
  }

}

=== 被打开页面的Javascript ===
window.onload = function()
{
  var oBtn3 = document.getElementById('btn3');

  oBtn3.onclick = function()
  {
    window.opener.document.body.style.background = 'red';
  }

}

再次提醒,以上操作都是在同域下进行的 最后再次注意,窗口间通信一定要在服务器环境 (上面所有操作都是在我绑定虚拟主机的www.a.com下进行的)

跨域窗口间通信

下面我们准备两个虚拟主机www.a.com 和 www.b.com来进行实验

跨域iframe窗口间通信

如果用刚才的方法进行通信,将iframe标签修改成跨域b下的文件 <iframe id = "myframe" src="http://www.b.com/被嵌套的页面路径"></iframe>

这样浏览器就会产生一个跨域安全权限不够的错误提示

解决的方法有挺多的,但是html5给了我们这样一种方法: 通过postMessage来发送请求,跨域页面获得请求 根据请求在对自身的dom进行操作

postMessage( msg , url )

msg是需要传给b域底下被嵌套的页面的信息(代码) url是b域下面的地址 (一定要带上http协议)

而且我们需要在被嵌套的页面下面调用postMessage(); 例: oMyIframe.contentWindow.postMessage( '需要传递信息' , 'http://www.b.com' );

然后www.b.com就可以接受到请求的信息 并且得到一个data属性和origin属性

data属性就是接受的信息 origin属性就是发送请求的域名

demo:


=== 发送请求的页面html ===
<input id="btn" type="button" value="点我发送跨域发送数据">
<iframe id="myIframe" src="http://www.b.com/iframeb.html"></iframe>

=== Javascript ===
window.onload = function()
{
  var omyIframe = document.querySelector("#myIframe");
  var oBtn = document.querySelector("#btn");

  oBtn.onclick = function()
  {
    //这里发送的信息可以用中文
    omyIframe.contentWindow.postMessage( '修改背景颜色' , 'http://www.b.com' );
  }
}

=== 接受请求的html ===
<input type="button" id="btn" value="点我改变父级的颜色">
<h1>这是一个跨域iframe</h1> 

=== 接受请求的Javascript ===
window.onload = function()
{
  var oBtn = document.querySelector("#btn");
  
  //通过监听message来获得请求
  window.addEventListener("message" , function(ev)
  {
    //判断如果请求符合,则进行相应的dom操作,当然判断条件根据需求可以省略,
    if( ev.data == '修改背景颜色' && ev.origin == 'http://www.a.com' )
    {
      document.body.style.background = 'red';
    }
  },false);
} 

跨域window.open窗口间通信

与上文iframe同理 通过window.open返回来的window对象进行postMessage请求就行了

离线存储

离线存储可以让我们在网络环境不太好的情况一下依然浏览到一些信息 原理是当有网络的时候,加载一些信息在cache中保存,当网络条件缺乏的时候就可以调用cache中的资源

搭建一个Html5的离线应用需要经历这些步骤

① 服务器设置头信息

AddType text/cache-manifest manifest

② html标签添加引用离线清单

<html manifest="path/to/NAME.manifest">

③ 编写离线加载清单

离线清单格式:


# Version 0.1

# 版本号强烈推荐填写
# 虽然只是一行注释但是改变文件可以重新缓存,这样写上版本号,想更新的时候修改版本号来重新缓存

CACHE MANIFEST
#需要缓存的文件
CACHE:
a.js
b.css
background.png

#不需要缓存的文件(白名单),离线的时候是不可用的
NETWORK:
login.asp

#无法访问页面(替代方案),及离线的时候可以用一个页面替代
#比如说我的页面文件全部放在html5,下面这种写法可以让我用404.html 替代/html5中的所有文件
#这种替代关系可以写很多个

FALLBACK:
/html5/ 404.html

存在的问题

1.自动缓存引用了 manifest 文件的页面即使在NETWORK中指定”*“(所有的页面都)使用网络,还是不能 解决它自动缓存当前页面。

这使得这个页面在 manifest 没有变更的情况下,会一直以”类静态”存在。比如,你后台更新了一篇文章,这个动态的首页,还是像第一次缓存的时候一样,没有变。这对于静态页面还好,但应用在一个动态系统就很麻烦了,因为你的内容是时时要更新的。

2.火狐底下会弹提示

注意事项

① 站点离线存储的容量限制是5M ② 如果manifest文件,或者内部列举的某一个文件不能正常下载,整个更新过程将视为失败,浏览器继续全部使用老的缓存 ③ 引用manifest的html必须与manifest文件同源,在同一个域下 ④ FALLBACK中的资源必须和manifest文件同源 ⑤ CACHE MANIFEST字符串应在第一行,且必不可少 ⑥ 当manifest文件发生改变时,资源请求本身也会触发更新 ⑦ 在manifest中使用的相对路径,相对参照物为manifest文件 ⑧ manifest文件中CACHE则与NETWORK,FALLBACK的位置顺序没有关系,如果是隐式声明需要在最前面

Workers

进程和线程

Html5底下有这么个Wokers对象可以允许我们在服务器端口多开一条线程 我们先解释一下线程是什么 在了解一下和它意思很容易混淆的进程

所谓线程是程序执行的最小单位,进程是程序具有一定独立功能的程序关于某个数据集合上的一次运行活动 所谓进程就是你点开一个程序,用它做了一些工作(基于某个数据集合上的一次运行获得)也就是开启了一次进程 而且线程是进程分出来的实体,一个进程可以有很多线程。如果说吃饭是一次进程的话,那么吃饭要先盛饭,夹菜,再咀嚼。这一系列动作就是线程。

① 所以一个线程只能属于一个进程,但是一个进程可以有多个线程 ② 资源分配给进程,同一进程的所有线程共享该进程的所有资源 ③ 处理机分给线程,即真正在处理机上运行的是线程 ④ 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步

进程有并发性,线程也有并发性。也就是说你可以开着QQ这个进程和老妈汇报,同时也可以开着微信和女朋友聊天 这就是所谓的进程的并发性

而QQ下面不单单可以敲字,同时还可以视频聊天两不误,这就是所谓的线程的并发性

虽然实际运作的是各个线程,但是统一向系统得到资源的还是进程, 每个进程再向下将资源分配给底下的线程 虽然统一得到资源的是进程,但是CPU统一调度的还是基本单位还是线程。说白了线程是工作的各个模块

但是如果一个进程崩溃了,虽然对其他进程没有什么影响,其底下的线程却会全部挂掉(因为线程的资源是依靠进程提供的嘛)

javascript多线程

传统上的线程可以解释为轻量级进程,它和进程一样拥有独立的执行控制,一般情况下由操作系统负责调度。而在 HTML5 中的多线程是这样一种机制,它允许在 Web 程序中并发执行多个 JavaScript 脚本,每个脚本执行流都称为一个线程,彼此间互相独立,并且有浏览器中的 JavaScript 引擎负责管理。

以前我们写一个js脚本,都是单线程的,也就是window.onload 走完全部的语句然后程序就停止了。 (各种事件监听只是挂在那里,但是主程已运行完了)

如果我们多开辟多个线程在服务器端口,把一些运算交个服务器去做,只需把运算好的结果返回给客户端。 这样用户上网浏览加载的速度就会快很多,客户体验也就上来了

javascript支持两种多线程模式

专用型dedicated web worker

首先强调一下,Webworker只能运作在服务端环境下,不然会有安全性错误

Uncaught SecurityError: Failed to construct 'SharedWorker': Script at 'file:///D:/Wampserver/html5test/b/WebWokers/worker.js' cannot be accessed from origin 'null'.

开启专用型多线程很简单

var w1 = new Worker('test.js');// 只需要将需要在进程里运行的js文件的文件名写上就行了
w1.postMessage('owen'); // 我们可以传递一个消息给服务器端的支线程

那么在我们这个主线程中就已经开辟了一个支线程了,那么怎么进行主支线程的通信呢? 在服务器端的支线程里,我们可以使用self来调用全局的 worker对象, 那么我们在test.js这个脚本里面可以这样写道

self.onmessage = function(ev)
{ 
   self.postMessage(ev.data + "you are welcome!"); 
   // 我们可以使用postMessage再将新的信息传回客户端去
}

相比较共享型而言,专用型worker通信开启了一个隐式的端口通信,当专用线程被创建的时候,MessagePort 的端口消息队列便被主动启用。因此,这也和工作线程接口中定义的 start 方法作用一致。

而共享型worker则使用一个port()对象来开启显式的端口通信(下文会提到)

这里需要我们注意和明白的一点是我们开启的支线程序的运行环境是在服务器端的 所以,在服务端的js脚本不支持浏览器提供的任何API操作(DOM alert() console 等等都不能用)

能用的有什么呢?

Ecmascript 所有语法和对象 Navigator 浏览器信息对象 location 底下的所有属性都能读取,但是不能修改(只读) self 指向全局的worker对象 XMLHttpResquest 构造器 setTimeout 和 setInterval close() 方法停止多线程 importScript() 引入其他线程的JS脚本 JSON以及配套的方法

然后客户端口再接受服务端传递回来的消息

var w1 = new Worker('test.js');// 只需要将需要在进程里运行的js文件的文件名写上就行了
w1.postMessage('owen '); // 我们可以传递一个消息给服务器端的支线程

w1.onmessage = function(ev)
{
  console.log(ev.data);
}

数据量多的时候,也可以传递JSON

var w1 = new Worker('test.js');
w1.postMessage({ "name" : owen , "sex" : boy , "age" : 19 }); 

如果我们需要传递一个二进制数据的时候,还需要在第二个参数中指定

var w1 = new Worker('test.js');
w1.postMessage({ operation: 'list_all_users', //操作
  input: buffer,
  threshold: 0.8, //阈值
 }, [buffer]); //需要在后面指定二进制对象

当我们在服务器端的任务运行完成后,还可以使用close()方法自行进行关闭

self.onmessage = function(ev)
{ 
   self.postMessage(ev.data + "you are welcome!"); 
}
close();

或者在主线程中,使用terminate方法进行关闭

w1.terminate();

我们甚至可以通过importScripts() 在服务器端引入外部的JS文件但是要注意引入的而文件也不能有DOM的语法,而且不能跨域

importScripts("test2.js" , "test3.js");
self.onmessage = function(ev)
{ 
   self.postMessage(ev.data + "you are welcome!"); 
}
close();

而且我们还能使用importScripts来预加载资源,这样就可以为客户端节省资源,提高用户体验了。

当我们开启的支线程出现了错误的时候我们需要及时的进行回调函数,然后返回给主线程,以便进行相应的处理

var w1 = new Worker('test.js');// 只需要将需要在进程里运行的js文件的文件名写上就行了

w1.onerror = function(error){
  console.log( "It 's error need doSomthing " + error.message );
  console.log( error.filename );
  console.log( error.lineno );
}

甚至我们还可以在支线程下开辟新的支线程,各种操作是一样的

还要注意一点的是Dedicated web worker会随着当前页面的关闭而结束

共享型shared web worker

共享型Worker我们主要用来解决多并发连接问题,在这一点上和专用型WorkerAPI有区别,而在其他方面也和专用型Worker一样不能访问DOM也不支持跨域通信

Shared web worker 和 Dedicated web worker 的主要区别在于, 共享型线程是对多个页面进行使用的 如果多个页面都需要加载或者计算同样的数据和资源这时候我们就需要使用Shared web worker 当然,Shared web worker也需要等全部页面关闭了才会结束

var w1 = new SharedWorker('shareworker.js','sharedworker1'); //如果指定了第二个参数,将被用做定义该共享型线程的名称

由于共享型多线程需要对多个页面进行广播,所以就不能使用对单一对象使用的postMessage方法了 这里HTML5中卫共享型多线程提供了一个port()方法, 我们就可以通过port方法 + postMessage方法来对多个页面进行广播了

var w1 = new SharedWorker('shareworker.js','Owen's Sharedworker');
w1.port.start();                             // 显示的开启端口
w1.port.postMessage("hi");                   // 向端口发送请求

w1.port.postMessage = function(ev)
{
  console.log(ev.data);                      //得到线程发送的abc 然后console出来
}

模拟多个页面只要将上面的代码复制粘贴多份

然后我们来看看引入的线程代码

(function(){
  self.onconnect = function(ev){
      var port = ev.ports[0];         // 获得端口
      port.postMessage('abc');        // 当连接成功后,向页面发送abc
    } 
})()

具体的关于共享型多线程的内容还很多,本人能力不足。实验的时候也出现了许多问题,还是尽可能的将对的东西分享出来。而且html5还不算太完善.

等日后学习到了关于共享型多线程其他知识再继续补充

这里火狐的官方文档对多线程有很详细的讲解

HTML5的一些其他小功能

内容编辑

如果细心的同学会发现Qzone上发表说说的功能是通过div实现的而不是textarea

其中的魔法就是contentEditable HTML5允许其他非<textarea></textarea><input type="text"> 的元素进行内容编辑 只需在该元素标签内加上

contenteditable="true" 

或者在js中加上

document.getElementById('div1').contentEditable = true;

就可以使大多数标签能够进行内容的修改

这个属性兼容的效果特别好 支持IE6 及其以上版本并且全部的标准浏览器都支持

语音输入

webkit强大的一个功能,允许用户通过语音提交表单 原本出chrome允许我们这样使用语言输入功能

<input type="text"  x-webkit-speech >

但是不知道什么原因这个功能已经被遗弃了,但是我们还是可以用其他的方式去实现语言输入

Daniel-Hug’s repositorys about speech-input

这样我们只需要在需要这样在项目中调用就行了

<div class="si-wrapper">
    <input type="text" class="si-input" placeholder="What's up?">
    <button class="si-btn">
        speech input
        <span class="si-mic"></span>
        <span class="si-holder"></span>
    </button>
</div>

其中的CSS和fontsome根据自己项目需求来改变

该功能只支持webkit浏览器!

桌面提醒功能

原先旧的API已经全部被新的浏览器所抛弃了,也就是说原先使用window.webkitNotifications 来建立消息提醒的对象已经被废除了

而且这是一个正在试验中的功能,未来还有其他变数也说不定。 这个功能最明显的用例之一是一个网页版电子邮件应用程序,每当用户收到了一封新的电子邮件都需要通知用户

要使用桌面提醒功能必须得到用户同意(火狐直接默认用户同意) 所以首先我们先查看一下浏览器默认的权限情况

console.log(Notification.permission)// 火狐默认为granted  webkit 为default

default

用户还未被询问是否授权,所以通知不会被显示。参看 Getting permission 以了解如何请求显示通知的权限。

granted 表示之前已经询问过用户,并且用户已经授予了显示通知的权限。

denied 用户已经明确的拒绝了显示通知的权限。

由此看来用户在使用火狐的时候是默认可以的 但是在使用谷歌及其webkit浏览器的时候,就需要询问用户更改权限到granted才可以使用 查看完权限以后让我们来看下如何创建一个实例对象 首先我们先通过浏览器向用户发送一个请求,如果用户同意则进行下一步,如果不同意则改为alert发送

// 向用户询问,并修改权限
window.addEventListener('load', function () {
  Notification.requestPermission(function (status) {
    // 这将使我们能在 Chrome/Safari 中使用 Notification.permission
    
    if (Notification.permission !== status) {
      Notification.permission = status; 
      // 如果原本的权限和请求后得到的结果状态不符合则修改
      // 注意Notification.permission在其他状态下都是只读
      // 只有当我们通过Notification.requestPermission这个方法向用户获取权限后
      // 才能将获得的结果修改Notification.permission(译: 消息提醒.权限 );
    }
  });
});

然后我们可以试着创建一个Notification的实例对象

var n = new Notification("Hi!",{icon:"./avatar.png",body:"欢迎来到我的博客",tag:"777",dir:"rtl");
console.log(n.tag); // 输出777 

Notification( title , option ) 其中

title : 提醒文章的标题 [必填] icon : 传递一个图片url 提醒对象的头像 body : 提醒的内容 dir : 定义标题内容和头像的布局 auto自动默认 和 ltr 都是从左到右布局 rtl是从右到左布局 lang: 指定提醒中所使用的语言。这个字符串必须在 BCP 47 language tag 文档中是有效的。 tag: 赋予提醒一个ID,以便在必要的时候对提醒进行刷新、替换或移除。

生成的实例还支持4个事件 onclick 当用户点击提醒消息的时候查看(点击关闭按钮不会触发) onshow 当提醒消息弹出的时候触发 onerror 当出现错误的时候触发 onclose 当用户点击关闭消息的时候触发

var n = new Notification("Hi!",{icon:"./avatar.png",body:"欢迎来到我的博客"});

n.onshow = function(){
	console.log("欢迎欢迎 热烈欢迎");
}
n.onclick = function(){
	console.log("欢迎你处理该消息提醒");
}
n.close = function(){
	console.log("谢谢你使用消息提醒");
}

Demo 注意:如果使用webkit只能弹出alert 说明你的设置了永不弹出提醒消息,请使用火狐测试

建议大家看一下详细的MDN归档

MDN notification使用文档

此功能中文译者的使用归档

地理定位

我们可以在服务器环境下,通过浏览器来获取当前访问我们页面的用户的地理位置信息

单次定位请求

navigator.geolocation.getCurrentPosition( success , error , options );
// 调用这么对象然后向用户申请是否允许获取地理位置

该API返回三个参数

success : 用户允许并且位置获取成功后返回的回调函数,该函数返回一个对象参数

例:

navigator.geolocation.getCurrentPosition(function(position){
	oT.value += '经度' + position.coords.longitude + '\n';
	oT.value += '纬度' + position.coords.latitude + '\n';
	oT.value += '精确度' + position.coords.accuracy + '\n';
	oT.value += '海拔' + position.coords.altitude + '\n';
	oT.value += '海拔准确度' + position.coords.altitudeAccuracy + '\n';
	oT.value += '行进方向' + position.coords.heading + '\n';
	oT.value += '地面速度' + position.coords.speed + '\n';
	oT.value += '时间戳' + new Date(position.timestamp) + '\n';
});
// 当成功的时候,返回一个function(position){ ... }, 然后进行相应的操作 

Demo

error : 用户允许但位置并没有获取成功返回的回调函数,该函数返回一个对象参数

navigator.geolocation.getCurrentPosition(function(position){ ... } , function(error){
	// 错误的函数一定要写在第二个参数,也就是说一定要写了成功的函数才能写错误的函数
		
	// error对象返回错误对应错误的错误码
	switch(error.code)
	{
		case 1: console.log("用户拒绝访问");break;
		case 2: console.log("由于某种原因,获取用户信息失败(例如网速太慢)");break;
		case 3: console.log("请求没有在设置的时限内完成");break;
		case 0: console.log("除以上三种错误外的其他未知错误");break;
	}
});

options : 其他功能选择(更好的数据收集) 这个选项是以JSON的形式设置的

// 因为是第三个参数,所以一定要先写成功的函数和失败的函数
navigator.geolocation.getCurrentPosition(function(position){ ... } , function(error){
 ... },{ enableHighAcuracy : true , timeout : 5000 , maximumAge : 5000});

enableHighAcuracy 是否获取更精确的地理信息(更加消耗网络资源) 缺省值为false

timeout : 请求的最高时限,超过这一时限停止请求。以毫秒为单位 (为保证网络资源不全部消耗在请求位置上)

maximumAge : 缓存单次位置的最长时间(超过这一时间,自动将此次位置信息从缓存中删除)

多次定位请求

多次请求和单次请求的语法基本相同

var timer = navigator.geolocation.watchPosition( success , error , options );

和单次请求不同的是,多次请求会返回一个Id,便于我们去关闭

多次请求在PC端效果不明显(因为你不能运动,除非你是在交通工具上使用电脑) 一般在移动设备上使用的较多。 而多次请求,为了节省网络资源,如果你当前的终端没用移动的话,那么则不会进行请求 (如果你设置了timeout 则 error.code 会连续的返回 3)

如果我们需要关闭多次请求

var timer = navigator.geolocation.clearWatch( timer );

除此之外,多次请求还允许我们在第三个参数中设置更新频率 timeout : 5000 , frequency : 5000 // 每5秒发出一次请求,如果请求超过5秒则再等5秒再进行一次请求

本地存储

Storage 和 Cookie 很相似都是可以存储一些用户经常需要输入的,或者是没有输入完成的内容,存储下来。

Storage比Cookie的好处在于(或者说Cookie的缺点) ① Cookie有存储限制,每个域名(domain)下只能存储20条,而且每条Cookie长度不能过4kb ② 如果Cookie被人拦截下来存在安全性问题 ③ Cookie需要指定作用域,不可以跨域调用 ④ 使用Cookie还需要自己封装操作函数 ⑤ Storage有高达5M的存储空间,并且支持跨域存储

sessionStorage

当我们需要处理一些临时的信息的时候,就可以使用到sessionStorage来进行临时的存储,当然当页面被关闭的时候,这些临时信息就会被自动删除

sessionStorage有这么几个方法使用

① 设置sessionStorage

window.sessionStorage.setItem( Storage的key , 需要存储的内容[字符串] )

window.sessionStorage.setItem('name','Owen');

这样就能让我们设置一个临时的Storage来存储(注意:Storage不单单可以存储字符串,还能够存储对应的DOM结点)

② 获取sessionStorage

window.sessionStorage.getItem( Storage的key )

window.sessionStorage.getItem( 'name' );

这样就能根据我们提供的key值来获取之前已经存储了的Storage (如果key提供错误,则返回null)

③ 删除sessionStorage window.sessionStorage.removeItem(Storage的key)

window.sessionStorage.removeItem( 'name' );

这样我们就能根据提供的key值,在关闭页面之前就吧已经存储的Storage删除

④ 清空sessionStorage

window.sessionStorage.clear();

通过这个方法我们就可以,在关闭页面之前就将已经存储的Storage列表全部清空

localStorage

localSotrage和sessionStorage用法类似,不过localStorage可以永久存储在电脑中(上限是5M)

window.localStorage.setItem('name', 'owen');
window.localStorage.getItem( 'name' );
window.localStorage.removeItem( 'name' );
window.sessionStorage.clear();

再次注意: setItem 不单单可以存储字符串,还能存储DOM结点

本地存储实例

Demo

Storage事件

我们可以绑定Storage事件,来监控localStorage存储的缓存发生修改的时候,做一些相应的操作。

首先强调一下,通过localStorage存储的Storage事件被激发的条件是当另外一个同域的页面修改了Storage, 那么为了同步新的缓存数据,Storage事件才会被激发来处理一些操作。 最先创建缓存的页面,无论怎么修改缓存,都不会激发Storage事件 并且,如果后来修改的缓存内容与之前的相同,也不会激发Storage事件。

但是如果是sessionStorage的话,因为存储的内容随着当前页面关闭就会被清空,所以Storage事件可以是通过iframe 来触发, 其实道理也是相同的,就是当缓存被修改的时候。

还有一点就是webStorage事件只能够在服务器环境下面被触发,但是Storage存储(session 、 local 两个方法)都可以在本地环境和服务器环境下使用。

总的来说,Storage事件激发的条件是

① 缓存被修改了 ② 服务器环境

绑定Storage事件可以有两种方法

 window.addEventListener( 'storage' , function(ev){} , false );

 window.onstorage = function(ev){};

返回的回调函数接受一个Storage的回调参数,在参数底下有这么几个属性 ev.key : 缓存被修改的全部key的集合 如果是被删除的则返回null ev.newValue : 新设置的缓存值 如果是被删除的则返回null ev.oldValue : 之前被修改的值 ev.storageArea : 当前域名下保存的所有存储的缓存值(包括没有被修改的) ev.url : 修改缓存页面的url

一个简单缓存同步的Demo

测试时,请打开两个页面来模拟同步

Demo

一个简单的行走日记

Demo

注意:尽量使用火狐测试,原码自行右键,本人做了一些优化, 同步服务器有兴趣的可以下载下来原码自己调用ajax , 本人这里就没有写了

audio video

音频和视频标签,可以允许我们导入一段音频和视频

<audio src="音频地址"></audio>
<video src="视频地址"></video>

音频视频标签拥有以下属性,来控制不同的功能

controls: 显示或隐藏用户控制界面 autoplay: 媒体加载完后是否自动播放 loop: 媒体是否循环播放 currentTime: 开始到播放现在所用的时间 duration: 媒体播放需要的总时间( read-only ) volume: 0.0 - 1.0的音量相对值 muted: 是否静音

preload: 如果出现这个属性,那么在页面加载的时候同时加载视频,并准备播放,如果有autoplay属性,则这个属性自动忽略

paused: 媒体是否暂停( read-only ) ended: 是否播放结束( read-only ) error: 是否出现错误( read-only ) currentSrc: 播放源地址( read-only )

play() : 视频播放 pause() : 视频暂停 load() : 重新加载媒体 (如果我们source的地址修改了,需要重新加载一遍媒体才能更新视频内容)

只有视频才有的属性 width: 播放器的宽度 height: 播放器的高度 poster: 规定视频加载的时候或者用户点击播放前,播放器显示的图像 videoWidth: 视频的实际宽 videoHeight: 视频的实际高

但是一开始视音频导入后并不能播放,是因为默认并没有开始,如果需要开始播放,则需要我们显示用户控制界面 再点击开始播放

怎么显示用户控制界面呢?只要添加一个controls属性就行了

<audio controls src="音频地址"></audio>
<video controls src="视频地址"></video>

如果我们需要一开始就播放只要再加一个autoplay属性就OK了

<audio controls autoplay src="音频地址"></audio>
<video controls autoplay src="视频地址"></video>

如果我们需要获取到已经播放的时间,这时候就需要使用JS了


<audio controls autoplay src="音频地址"></audio>

window.onload = function()
{
	var oA = document.getElementsByTagName('audio');

	// 这个属性可以每一秒获取当前的时间进度
	// setInterval(function(){
	//	console.log( oA.currentTime );
	// },1000);

	// 当然我们可以通过js控制其时间进度
	oA.currentTime = 60; //从音频的一分钟后开始
	
	// 我们还可以通过duration 获取媒体将播放的总时间(秒)
	console.log( oA.duration );
	// 通过volume来获取媒体播放的相对音量
	console.log( oA.volume );
	// 是否静音? 若静音了返回true 没有静音返回false
	console.log( oA.muted );
	// 媒体是否暂停
	console.log( oA.paused );
	// 音频视频是否播放结束
	console.log( oA.ended );
	// 播放是否出现错误,如果错误则返回错误代码
	console.log( oA.error );
	// 返回媒体源地址
	console.log( oA.currentSrc );
}

视频也一样这里就不再赘余

编解码器的概念

因为视频或音频文件存储所需的空间通常是比较大的,所以各个浏览器厂商都会将浏览器中添加一个或多个编解码器程序,当读取较大的视频音频文件的时候, 会先通过编码将文件进行压缩。因为这样压缩会损失部分视音频的质量,所以将这样的编码压缩方式称之为为有损压缩, 当压缩完之后然后再传输过来,传输完之后再进行解码播放。 这样就算网络环境不稳定也能在较短时间内读取较大的音频视频文件

但是,因为编码程序有的是开源框架,有的是收费的框架,所以也就导致了不同的浏览器所支持的视频文件类型不同。

那么怎样兼容不同的浏览器,来播放视频文件呢?

source

上面我们提到如果需要引入一个视频或音频文件,可以通过src属性引入对应的文件源 但是由于编码程序的不同,我们需要做不同浏览器厂商视音频的兼容。

当然首先要兼容我们就需要准备几个同一个视音频,类型名不同的文件,

我们可以通过source标签引入不同类型名的视频源

<audio controls>
	<source src="视频地址">
</audio>
<video controls ></video>

使用getUserMedia

getUserMedia 是html5 规范新出来的一个API, 用处就是使用你电脑的媒体设备(摄像头,麦克风)

使用起来很简单,但是有一些需要注意的地方

navigator.getUserMedia({video: true} , function(stream) {
	/* 对摄像头拍摄的资源进行使用 code */	
},function(err) {
	console.log(err);
	alert("Request video Stream Fail | 加载摄像头资源失败");
});

如果是麦克风 就再添加一个成员 audio: true

这里获取的只是流数据,如果需要使用这些数据 还需要结合video标签 和 canvas

caveats

① 当前html5 还没定型,使用时需要添加兼容性处理(vendor prefixing 浏览器厂商前缀)

即:

// opera browser has't verdor prefixing | 欧鹏浏览器无需添加厂商前缀 
navigator.getUserMedia = navigator.getUserMedia || 
                 navigator.webkitGetUserMedia ||
                 navigator.mozGetUserMedia || 
                 navigator.msGetUserMedia;

② 各个浏览器厂家都对这项技术有安全性的限制,在非用户允许,或者是一些不可靠的网址(包括本地) 中就不会加载这个功能(如果需要测试可以考虑 Mamp 或者 Lamp)

③ 使用时会跳这么个提示

getUserMedia() is deprecated on insecure origins, and support will be removed in the future. You should consider switching your application to a secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details.

getUserMedia() 不能在不安全的站点中使用 你现在网址并不被浏览器认可为可靠站点,你需要将你考录把你的应用挂在一个安全的站点。例如 HTTPS,并且未来可能不会允许你的站点支持这项技术,

使用getUserMedia 实现简单的视频拍照

先看效果吧

snapshot

Steps: ① 调用getUserMedia 获取摄像机权限 ② 点击将获取摄像机拍摄到的流数据丢入video标签,进行播放 ③ 点击snapshot 时候调用canvas 中的drawImage 方法,将video中的图形绘制在canvas画布中 ④ 将画布中的图片编码数据路径,丢入img标签的src中

先看下布局:

<video autoplay></video>
<img src="">
<!-- canvas的作用就是生成图片,所以无需显现 -->
<canvas style="display: none;"></canvas>
<button id="capture">snapshot</button>

获取相机权限很简单,上面已经说的差不多了

// 兼容vendors
navigator.getUserMedia = navigator.getUserMedia || 
                 navigator.webkitGetUserMedia ||
                 navigator.mozGetUserMedia || 
                 navigator.msGetUserMedia;
// 获取权限
navigator.getUserMedia({ video:true } , function(stream) {
	video.src = window.URL.createObjectURL(stream);
	localMediaStream = stream;

	canvas.width = 640;
	canvas.height = 480;

	oImg.width = video.videoWidth;
	oImg.height = video.videoHeight;
},function(err) {	
	console.log(err);
	alert("浏览器不支持获取摄像头 get better browser")
});

可能大家对createObjectURL不太熟悉

createObjectURL 可以将传入的数据流创建一个指向该流的URL

传入的参数可以是 blob(二进制类型) 或者 file (文件流类型);

与之对应的还有revokeObjectURL

释放createObjectURL方法创建的URL,传入的参数是这个URL

到这一步为止我们就可以事先简单的摄像功能了

接下来就是拍照功能

var snapshot = function() {
// 如果摄像机工作了,产生了数据流,就可以进行拍照了
	if(localMediaStream) {
		ctx.drawImage( video , 0 , 0 );
		oImg.src = canvas.toDataURL("image/webp");
	}
}
var btnCapture = document.getElementById("capture");
btnCapture.addEventListener("click" , snapshot , false);

很简单,就是简单的吧video播放的图像直接印在canvas 中,然后通过toDataURL转成图片格式就行

完整源码,demo右键

caveats

① 摄像机功能只允许在服务器环境下才可以获取限权

② webp 是一种新的有损压缩图片格式,暂时只支持chrome ,兼容的话,可以这里改成png或jpg格式

感谢

本博文,有相当一部分内容COPY 和 引用了他人的知识,特此陈列,以表感谢

妙味课堂 IAN MCNALLY ‘s blog history对象 act262的博客 brainmao html5离线存储 html5 离线存储 煜子的博客关于contenteditable属性 Daniel-Hug 关于语音输入API使用 进程和线程的区别 web worker 多线程 Using_web_worker 借助web worker解决资源预加载的问题 关于桌面提醒,mozilla的官方文档 前端大全 web storage和cookie的区别

引用出自

《javascript高级程序设计》

Table of Contents