之前参加360前端实习生 星计划时,遇到一个任务,就是利用H5 localStorage实现密码的存入和获取。当时还没有学canvas 绘图,感觉利用原生JS和CSS实现比较复杂;这次刚好看了JS高程的Canvas绘图,所以正好可以理论与实践结合一下,试着做了一下这个。当然,这只是一个初期的效果,后期还会再改善。
由于手势绘制密码,需要用到触摸事件的一些东西,所以我会在开头先讲一下JS的事件机制,希望大家能耐心看完!(* _ *)所以整篇博客的目录如下:
- JS事件机制
- Canvas绘图原理
- H5缓存机制
JS事件机制
事件流
JS主要用于用户和浏览器之间的交互,这种交互往往通过事件的方式来实现。而触发页面中的某个元素实质上也触发了包含他的元素,这就类似于有很多同心圆,我们指向最里面圆的圆心时,实际也指向了其他圆的圆心。而事件流就是指这些”同心圆”事件触发的顺序。
现在主要有两种事件流,一种是冒泡事件流(IE主打),另一种是捕获事件流(网景公司主打)。
为了直观一些,我用下面的图来表示各种事件流:
事件处理程序
学过计组的应该都知道,中断的概念,每个中断对应一个中断处理程序,这一点类似于我们的JS事件,每个事件也有一个事件处理程序,不过这是要我们去自定义的。在JS中,有四种事件处理程序。
一、 HTML事件处理程序
直接看代码吧
<button onclick = function(){ alert("HTML事件!"); }>点击之后触发事件</button>
不难理解,这段代码在button 元素上绑定了一个事件,点击鼠标就会触发那个事件处理程序。
但这种方式的缺点主要是JS和HTML耦合性比较高,不利于后面的维护。
二、DOM 0级事件处理程序
<button id="btn">点击之后触发事件</button>
<script>
document.getElmentById("btn").onclick = function(){
alert("DOM 0级事件!");
}
</script>
这种方式是将事件处理程序绑定在对应的DOM对象上,该方式解决了HTML代码与JS耦合的问题,但是需要注意,在页面未加载出来时,去访问id 为btn的元素是会出错的。不过没关系,如下代码就可以解决
<script>
window.onload = function(){
document.getElmentById("btn").onclick = function(){
alert("DOM 0级事件!");
}
}
</script>
三、 DOM 2级事件处理程序
这种处理程序是通过两个api注册事件和删除事件的:
addEventListener(eventName, handler, ifCapture); //注册事件
removeEventListener(eventName, handler, ifCapture); //删除事件
注意:删除事件的handler必须与注册事件中传入的参数一致
var handler = function(){
alert("DOM 2级事件处理程序");
}
document.getElmentById("btn").addEventListener('click', handler, false);
//false表示不进行事件捕获
//当不需要事件处理函数时,将其删除掉
//document.getElmentById("btn").removeEventListener('click', handler, false);
四、 IE事件处理程序
IE 事件处理程序是针对IE浏览器的,并且只支持冒泡,不支持事件捕获。
它也提供了两个api来注册事件处理程序和删除事件处理程序,与DOM 2级不同的是:事件名这里是on+事件名,比如:点击事件就是onclick。看下面代码就很明白了:
var handler = function(){
alert("IE事件处理程序");
}
document.getElmentById("btn").attachEvent('onclick', handler);
//当不需要事件处理函数时,将其删除掉
//document.getElmentById("btn").detachEventL('onclick', handler);
对于上面四种事件处理程序,有一个特别需要注意的一点:
只有IE事件处理程序的作用域是全局作用域,也就是说this 指的是window对象;
其他DOM 事件处理程序的作用域就是相应元素;
我们可以做一个浏览器能力检测从而使注册事件处理程序得到好的兼容性。
3. 事件对象
事件对象只在事件处理程序执行时才存在。
一、DOM 中的事件对象
兼容DOM事件的浏览器在事件处理程序执行时都会传入一个event对象。这个对象拥有一系列的属性和方法:
Property/Methods | Type | decription |
---|---|---|
PreventDefalut | Function | 取消事件的默認行為 |
taget | Element | 事件的目標元素 |
stopPropagation | Function | 取消冒泡 |
type | String | 事件类型 |
这里只列举了常用的几种属性和方法,若要了解详细的事件对象,可以在浏览器控制台将事件对象输出,可看到详细的事件对象。
二、 IE中的事件对象
在IE 中的事件对象,根据指定事件处理程序的方式,IE 中的事件对象也不同。
如果采用DOM 0级方式指定事件处理程序,那么event对象是window对象的一个属性。
<button id="btn">点击之后触发事件</button>
<script>
document.getElmentById("btn").onclick = function(){
vae e = window.event;
console.log(e);
alert("DOM 0级事件!");
}
</script>
IE 中的事件对象同样也有一系列属性。(只有属性)
Property | Type | decription |
---|---|---|
returnValue | Boolean | 取消事件的默認行為 |
srcElement | Element | 事件的目標元素 |
cancelBubble | Boolean | 取消冒泡 |
type | String | 事件类型 |
4. 事件类型
常用的事件类型有这么几种:
UI事件:(如load事件,unload事件, scroll事件等不一定与用户操作有关的事件)
焦点事件:(常常与表单元素等一类需获取焦点的元素结合使用。focus,blur事件,这两个不支持事件冒泡的事件;因此出现了focusin,focusout事件,DOMFocusIn, DOMFocusOut事件)
鼠标事件:
eventName | decription |
---|---|
click | 鼠标点击事件 |
dblclick | 鼠标双击事件 |
mousedown | 鼠标左键按下事件 |
mouseup | 鼠标左键抬起事件(与mousedown可构成一个click事件) |
还有很多鼠标事件,这里不再陈列,需要注意的一点是:只要涉及到鼠标操作,在鼠标事件对象中都有如下属性:
clientX, clientY (这是当前触发事件的鼠标位置在浏览器窗口(视口)中的坐标)
pageX, pageY(当前触发事件的鼠标位置在页面中的坐标,如果页面没有滚动的话,与clientX, clientY是一样的)
screenX, screenY(当前触发事件的鼠标位置在整个显示屏中的坐标,也就是相对于桌面左上角点的相对坐标)
键盘事件:
keydown: 按下任意键时触发
keypress: 按下字符键时触发
keyup: 松开键时触发
与键盘事件相关的就是按下的键的值,也就是说keyCode 或 charCode。这也是包含在键盘事件对象中的。
HTML5事件:
这里介绍与本次手势解锁相关的触摸事件:
根据触摸的种类,触摸事件也有以下这几个:
touchstart: 开始触摸时触发
touchend: 在触摸结束时触发
touchmove: 在触发移动过程中触发。注意:在移动设备上触摸移动时会默认触发滚动事件,因此利用perventDefalut()将其默认行为取消。
每个触发事件都对应的事件对象。对于touchstart和touchmove事件,事件对象中有一个touches数组,这里面包括触摸的clientX,clientY,target等属性信息。这些对于实现手势解锁是很重要的信息。
事件对于Web性能是有很大影响的,因为注册的事件处理程序保存在内存中;并且绑定事件处理函数一般都是要先访问DOM节点的。
那么在JS事件处理这块如何去优化性能呢?
两方面:
- 事件委托
何为事件委托?
如果一个元素内部很多个元素上都要绑定同一类事件处理函数,我们很容易想到每个元素都绑定一个事件处理函数就好了,但是这种情况在元素很多的时候对于性能是由很大影响的,因为你不仅要访问每个元素,还要形成很多个事件连接,因此,我们采用事件委托的方式来绑定事件处理函数,即只给最外层元素上绑定这类事件,然后利用事件冒泡机制,当事件对象e.target为我们实际要触发事件的元素对象时,再采取一系列操作。看下面的代码:
<ul id="saleType">
<li>水果</li>
<li>蔬菜</li>
<li>书</li>
</ul>
<script>
var ulEle = document.getElementById("saleType");
var handler = function(e){
if(e.target.innerText == "水果")
{
//卖水果的相应操作
}
else if(e.target.innerText == "蔬菜")
{
//卖蔬菜的相应处理
}
else if(e.target.innerText == "书")
{
//进行卖书的相应操作
}
}
ulEle.addEventListener('click', handler, false);
</script>
- 移除事件处理函数
每次绑定事件处理函数,就会有一个事件连接,,每有一个事件连接,意味着这个函数就有一个引用,但有些情况会造成事件连接还在,但是用不到这个事件连接了,导致函数永远被引用而不能得到垃圾回收。所以,在确定不会再用的情况下,可以手动来移除事件处理函数。
<div id="container">
<input id="btn" type="button" value="Button" />
</div>
<input id="btnChange" type="button" value="替换内容" />
<script>
window.onload = function(){
var btn = document.getElementById('btn');
btn.onclick = function(){
alert("按钮被点击!");
}
var btnC = document.getElementById('btnChange');
btnC.onclick = function(){
//在此之前,应该先将id为btn的按钮绑定的事件移除
btn.onclick = null;
document.getElementById("container").innerHTML = "<sapn>内容替换掉了</sapn>";
}
}
</script>
本次就先总结这么多吧,下一节我会介绍canvas的原理以及与手势解锁实践的关联。