整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

HTML5历史状态管理

历史状态管理是现在web应用开发的一个难点,用户每一次操作不一定会打开一个全新的页面,因此后退和前进按钮也就失去了作用,解决这个问题首选hashchange事件,通过该事件可以知道URL参数什么时候发生了变化用法如下:

window.onhashchange=function(){

alert(1)

}

而通过状态管理API,能够在不加载新页面的情况下改变浏览器的URL,为此需要使用history.pushState()方法,3个参数状态对象,新状态标题,和可选的相对URL。

history.pushState({name:"test"},"test","a.html");

注意只是pushState是不能改变网页标题的哦。

执行该方法后,新的状态信息就会被加入历史状态栈,但是浏览器并不会真的向服务器发送请求,第二个参数目前没有浏览器实现,因此也可以传入空字符串。

因为pushState()会创建新的历史状态,所以按下后退按钮会触发window对象的popstate事件。popstate事件对象有一个state属性,这个属性就包含第一个参数pushState()状态对象

window.onpopstate=function(e){

console.log(e.state)

}

注意浏览器加载的第一个页面没有状态,因此单击后退按钮返回浏览器加载的第一个页面时,之为null

要更新当前状态,可以调用replaceState();传入的参数与pushState()的前两个参数相同。调用这个方法不会再历史状态栈中创建新状态。只会重写当前状态。

history.replaceState({name:"re'd"},"red");

内容是《Web前端开发之Javascript视频》的课件,请配合大师哥《Javascript》视频课程学习。

事件有很多类型,不同的事件类型具有不同的信息;

DOM2级事件规定了下列5种事件:

  • UIEvent(user Interface,用户界面)事件,在用户与页面上的元素交互时触发;
  • MouseEvent鼠标事件,当用户通过鼠标在页面上执行操作时触发;
  • KeyboardEvent键盘事件:通过键盘在页面上执行操作时触发;
  • HTML事件:当浏览器窗口发生变化或发生特定的客户端/服务器交互时触发;
  • MutationEvent变动事件:当底层DOM结构发生变化时触发;

DOM3级在DOM2的基础上重新定义了这些事件,并增加了一些事件:

  • FocusEvent焦点事件:当元素获得或失去焦点时触发;
  • WheelEvent滚轮事件:当使用鼠标滚轮(或类似设备)时触发;
  • 文本事件:当在文本中输入文本时触发;
  • 合成事件:当为IME(Input Method Editor,输入法编辑器)输入字符时触发;

HTML5规范也定义了大量的事件:历史管理、拖放、跨文本通信,以及多媒体等事件;

除了这些事件,有些浏览器会在DOM和BOM中也实现其他专有事件;

从另外一个角度来分,事件大致可以分成几大类:

依赖于设备的输入型事件:这些事件依赖于特定的输入设备,比如鼠标和键盘,也包括触摸事件;例如,mousedown、mousemove之类的,只能通过鼠标设备,而keydown或keypress只能通过键盘设备,touchmove或gesturechange也只能通过触摸设备实现;

独立于设备的输入型事件:不依赖于特定的输入设备,比如click事件,既可以通过鼠标单击实现,也可以通过键盘或触摸设备实现;再如textinput事件,即可以通过键盘按键实现,也可以在剪切和粘贴实现,甚至通过手写设备来实现;

用户界面事件:通常指的是一些注册在表单控件上的事件,例如文本框获取焦点的focus事件,控件改变值的change事件或提交表单的submit事件;

状态变化事件:表示某些生命周期或相关状态的变化的事件,比如,文档加载的load事件或DOMContentLoaded事件或文档状态readystatechange事件;再如HTML5的历史管理的popstate事件,online或offline事件;

特定API事件:HTML5及相关的规范中定义的事件,例如:拖放事件,多媒体的相关事件;

UIEvent事件:

UIEvent事件表示简单的用户界面事件,但包含一些不一定与用户操作有关的事件,主要与元素的焦点有关;

该事件类继承自Event类;由其也派生出其他子类,如MouseEvent、TouchEvent、FocusEvent、KeyboradEvent、WheelEvent、InputEvent和CompositionEvent;

其自身定义了一些属性,如:detail、layerX、layerY、pageX、pageY、sourceCapabilities、view和which;

UIEvent相关的事件:

  • DOMActive:表示元素已经被用户操作(通过鼠标或键盘)激活时发生;
  • load:当页面完全加载后在window上触发,当所有框架都加载完毕时在框架集上触发,当图像加载完毕时在<img>元素上触发,或者当嵌入的内容加载完成时在<object>元素上触发;
  • unload:当页面完全卸载后在window上触发,当所有框架都卸载后在框架集上触发,或者当嵌入的内容卸载完毕后在<object>元素上面触发;
  • abort:当用户停止下载过程时,如果嵌入的内容没有加载完,则在<object>元素上触发;
  • error:当发生Javascript错误时在window上触发,当无法加载图像时在<img>元素上面触发,当无法加载嵌入内容时在<object>元素上触发,或者当一或多个框架无法加载时在框架集上触发;
  • select:当用户选择文本框(<input>或<textarea>)中的一或多个字符时触发;
  • resize:当窗口或框架的大小变化时在window或框架上触发;
  • scroll:当用户滚动带滚动条的元素中的内容时,在该元素上触发;

以上这些事件,在DOM2级事件中,除了DOMActive之外,其他事件都属于HTML事件,所以,在确定浏览器是否支持DOM2级事件,最后检测一下,如:

var isSupported = document.implementation.hasFeature("HTMLEvents","2.0");

确定浏览器是否支持DOM3级事件,如:

var isSupported = document.implementation.hasFeature("UIEvent","3.0");

另外,这些事件,多数与window对象或表单控件相关,所以有些地方把这些事件也称为window事件,就是因为这些事件的发生多数与浏览器窗口有关;

load加载事件:当页面完全加载后(包括所有图像、Javascript文件和CSS文件等外部资源),就会触发window对象的load事件,这也是Javascript中最常使用的事件;

有两种定义onload事件处理程序的方式:

window.addEventListener("load", function(event){
    console.log("Loaded");
    console.log(event);  // Event
},false);
// 或在IE10以下
window.attachEvent("onload", function(event){
    console.log("Loaded");
    console.log(window.event);
});

为<body>元素添加onload特性;

<body onload="console.log(event);">

一般来说,在window上发生的任何事件都可以在<body>元素中通过相应的特性来指定,因为在HTML中无法访问window元素;

图像也可以触发load事件,无论是在DOM中的图像还是在HTML中的图像元素;

<img src="images/1.jpg" onload="alert('图片已加载')" />

或:

var img = document.querySelector("img");
img.addEventListener("load", function(event){
    console.log(event.target.src);
});
// 或
EventUtil.addHandler(img, "load", function(event){
    event = EventUtil.getEvent(event);
    console.log(EventUtil.getTarget(event).src);
});

在创建新的<img>元素时,可以为其指定一个事件处理程序,只需要指定src属性就可以下载,并不需要添加到DOM树中,如:

window.addEventListener("load", function(event){
    var img = document.createElement("img");
    img.addEventListener("load", function(e){
        console.log(e.target.src);
    });
    img.src = "images/1.jpg";
    document.body.appendChild(img);
});
// 或者
EventUtil.addHandler(window, "load", function(){
    var img = document.createElement("img");
    EventUtil.addHandler(img, "load", function(event){
        event = EventUtil.getEvent(event);
        console.log(EventUtil.getTarget(event).src);
    });
    img.src = "images/1.jpg";
    document.body.appendChild(img);
});

还可以使用DOM0级的Image对象实现,如:

    // 把
    var img = document.createElement("img");
    // 换成
    var img = new Image();

还有一些元素也以非标准的方式支持load事件;在标准浏览器中,<script>元素也会触发load事件,以确定动态加载的js文件是否加载完毕,在设置了<script>的scr属性并且添加到文档中,才会开始下载;

window.addEventListener("load", function(){
    var script = document.createElement("script");
    script.addEventListener("load", function(event){
        console.log(event.target.src);
    });
    script.src = "scripts/main.js";
    document.getElementsByTagName("head")[0].appendChild(script);
});
// 或
EventUtil.addHandler(window, "load", function(){
    var script = document.createElement("script");
    EventUtil.addHandler(script, "load", function(event){
        console.log(event);
        var target = EventUtil.getTarget(event);
        console.log(target.src);
    });
    script.src = "scripts/main.js";
    document.getElementsByTagName("head")[0].appendChild(script);
});

此时,event对象的target及currentTarget或srcElement引用都是<script>节点;

IE8及以下浏览器不支持<script>元素上的load事件,但以上代码不会抛出异常;

浏览器还支持<link>元素上的load事件,如:

window.addEventListener("load", function(){
    var link = document.createElement("link");
    link.rel = "stylesheet";
    link.addEventListener("load", function(event){
        console.log(event.target.href);
    });
    link.href = "styles/css.css";
    document.getElementsByTagName("head")[0].appendChild(link);
});

与<script>类似,指定其href属性并且添加到文档中才会下载;

unload事件:在页面被完全卸载后触发;或者从一个页面切换到另一个页面,或者刷新操作也会触发unload事件,如:

<body onunload="alert('unload');">

抛出异常:blocked alert during unload,也就是页面被阻塞了,而在unload事件处理中,是不允许阻塞的,诸如弹窗之类的,都会引起阻塞;正确的做法:

<body onunload="console.log('unload');">

或:

EventUtil.addHandler(window, "unload", function(event){
   console.log("unload");
});
  • unload事件特点:
  • unload事件的event对象只包含target(或srcElement)属性,且值为document;
  • unload事件发生时的状态应该是:
  • 所有资源仍存在 (图片, iframe 等.)
  • 对于终端用户所有资源均不可见;
  • 界面交互无效 (window.open, alert, confirm 等,会引起阻塞);
  • 错误不会停止卸载文档的过程;

unload事件执行时间很短,并且是在一切都被卸载之后才触发,所以不适合处理常规代码,一般是取消(或清除)该页面上的对象引用,以避免内存泄漏;

var obj = {name:"wangwei",age:18};
window.addEventListener("unload", function(event){
    var oDiv = document.getElementById("mydiv");
    console.log(oDiv);
    var img = document.getElementsByTagName("img")[0];
    console.log(img);
    console.log(obj);
    obj = null;
console.log(obj);
debugger;
});
var obj = {name:"wangwei",age:18};
window.addEventListener("unload", function(event){
    localStorage.setItem("unload","页面被unload了");
    localStorage.setItem("person", obj);
});

示例:统计一个页面停留的时长,如:

window.addEventListener("load", function(){
    var start = new Date();
    console.log(start);
    window.addEventListener("unload", function(){
        var end = new Date();
        var duration = (end.getTime() - start.getTime()) / 1000;
        var pageView = {
            pageStayTime: duration,
            pageUrl: location.href
        };
        console.log("保存到数据库中");
    });
});

另外,unload事件是不冒泡的,也是不可取消的;

同样,DOM2级事件规定应该在<body>元素而非window对象上触发unload事件,但所有浏览器都在window上实现了unload事件;

error事件:window.onerror属性看起来像事件处理程序,并且当Javascript出错时就会触发它,但是,它并不是真正的事件处理程序,因为它的参数不是event对象,而是5个参数,由这5个参数可以获取详细的错误信息;

  • message:错误信息,描述错误的一条消息;
  • URL:引发错误的Javascript所在的文档的URL;
  • line:文档中发生错误的行数;
  • column:发生错误的列数;
  • error:错误对象,这个error也称为全局错误对象;
window.onerror = function(sMessage, sUrl, sLine, sColumn, error){
console.log("Error:" + sMessage + " URL:" + sUrl + " Line:" + sLine + " Column:" + sColumn);
console.log(error);
    return true;
}

但如果使用DOM2级事件处理程序,其中的参数就是event对象;

window.onload = function(){
    num1 + num2;
}
window.addEventListener("error", function(event){
console.log(event);  // ErrorEvent
    return true;
});

ErrorEvent类继承自Event类,其定义了如下属性:

  • message:只读,返回包含了所发生错误的描述信息;
  • filename:只读,包含了发生错误的脚本文本的URL;
  • lineno:只读,错误发生的行号;
  • colon:只读,错误发生的列号;
  • error:只读,发生错误时所抛出的Error对象;

这5个属性也对应着window.onerror属性的5个参数;

如果是图片的onerror事件,就是一个真正的事件,其中只有一个参数,就是一个event对象;

var img = document.getElementsByTagName("img")[0];
img.onerror = function(event){
    console.log(event);  // Event type为error
}

abort事件:当一个资源的加载已中止时,将触发该事件;

var video = document.querySelector("video");
videoSrc = "https://www.zeronetwork.cn/video.webm";
video.addEventListener("abort", function(event){
    console.log(event);
    console.log("下载中止:" + videoSrc);
});
var source = document.createElement("source");
    source.setAttribute("src", videoSrc);
    source.setAttribute("type", "video/webm");
    video.appendChild(source);
source.addEventListener("error", function(event){
    console.log(event);
    return true;
});

resize事件:当浏览器窗口的大小被调整,就触发resize事件,该事件在window上触发;

EventUtil.addHandler(window, "resize", function(event){
    console.log(event);
});

在标准浏览器中,event对象有target属性,且值为window对象,但IE未提供任何属性;

浏览器的大小改变1像素就会触发resize事件,然后再随着变化不断重复触发;

最大化或最小化窗口时,也会触发resize事件;但是部分浏览器会在最大化或最小化时触发两次或以上的该事件,此时可以使用setTimeout()解决,也就是延迟执行某些代码,如:

function callBack(){
    console.log("callBack");
}
window.addEventListener("resize", function(event){
    var target = this;
    if(target.resizeFlag)
        clearTimeout(target.resizeFlag);
    target.resizeFlag = setTimeout(function(){
        callBack();
        console.log("resize");
        target.resizeFlag = null;
    },200);
});

示例:随窗口大小的变化而变化,如:

var mydiv = document.getElementById("mydiv");
var w = mydiv.clientWidth,
    h = mydiv.clientHeight,
    dx = w / h,
    dw = document.documentElement.clientWidth,
    scale = w / dw;
window.addEventListener("resize", function(event){
    var dw = document.documentElement.clientWidth;
    mydiv.style.width = dw * scale + "px";
    var w = mydiv.clientWidth;
    mydiv.style.height = w * dx * scale + "px";
    console.log(mydiv.style.width);
    console.log(mydiv.style.height);
});

示例:

<style>
*{margin:0;padding:0;}
.leftDiv,.rightDiv{
    width: 40%; overflow-y: scroll; float: left; margin-right: 10px; background-color: purple;
}
</style>
<div class="leftDiv">leftDiv ...</div>
<div class="rightDiv">rightDiv ...</div>
<script>
var leftDiv, rightDiv, dHeight;
window.addEventListener("load", function(){
    leftDiv = document.querySelector(".leftDiv");
    rightDiv = document.querySelector(".rightDiv");
    dHeight = document.documentElement.clientHeight;
    leftDiv.style.height = dHeight + "px";
    rightDiv.style.height = dHeight + "px";
});
window.addEventListener("resize", function(event){
    dHeight = document.documentElement.clientHeight;
    leftDiv.style.height = dHeight + "px";
    rightDiv.style.height = dHeight + "px";
});
</script>

resize事件目前只能注册在window对象上,它不支持注册在DOM元素上,如果要监听DOM元素的resize事件,最好的方案就是使用自定义事件;

scroll事件:虽然是在window对象上发生,但它实际表示的是页面中相应元素的变化;即可以在滚动窗口或其他元素时,跟踪变化来确保某些内容一直在屏幕上可见;

window.addEventListener("scroll", function(event){
    console.log(event);  // Event
});

该事件对象并没有提供滚动相关的信息,只是一个普通的Event的对象;

在混杂模式下,可以通过<body>元素的scrollLeft和scrollTop来监控到这一变化;在标准模式下,会通过<html>元素来反映这一变化;

window.onscroll = function(){
    console.log("x:" + document.documentElement.scrollLeft + ", y:" + document.documentElement.scrollTop);
}
// 或
window.addEventListener("scroll", function(event){
    if(document.compatMode == "CSS1Compat")
        console.log(document.documentElement.scrollTop);
    else
        console.log(document.body.scrollTop);
});

与resize事件类似,scroll事件会在文档被滚动活动期间会被重复触发,因此在scroll事件处理程序中尽量保持简单的代码;

scroll事件示例:

<div id="mydiv" style="width:200px;height:200px;background-color: purple; position: absolute;top:50px;right:50px;"></div>
<div style="height: 2000px; background-color:lightgreen;"></div>
<script>
window.onscroll = function(){
    var oDiv = document.getElementById("mydiv");
    oDiv.style.top = document.documentElement.scrollTop + "px";
}
</script>

scroll事件也可以注册到Element元素上;如:

var oDiv = document.getElementById("mydiv");
console.log(oDiv.scrollHeight);
oDiv.addEventListener("scroll", function(event){
    // console.log(event);  // Event
    console.log(event.target.scrollTop);
});

事件防抖(debounce)和节流(throttle):

防止事件被频繁触发;相关的事件有:mousemove、keydown、keypress、keyup、resize、scroll等;

防抖:触发高频事件后n秒内函数只执行一次,如果n秒内高频事件再次触发,则重新计算时间;如:

var timer = null;  // 创建一个标记用来存放定时器的返回值
window.addEventListener("resize", function(){
    if(timer){
        clearTimeout(timer);  // 每当resize时把之前的一个setTimeout清除
        timer = null;
    }
    if(!timer){
        timer = setTimeout(function(){ // 再创建一个新的定时器
            console.log("防抖");
        },500);
    }
});

在事件防抖中,清除定时器的时机很关键,必须在新定时器生成之前,如果在之后,会将所有定时器都清除,目标函数一次都不执行;或:

function debounce(callback, delay){
    var timeout = 0;
    return function(){
        var arg = arguments;
        // 一直触发,就一直清除
        clearTimeout(timeout);
        // 直到事件不再触发,最后一个定时器没有清除,delay后就会执行定时器,
        // 如此,就确保只执行一次
        timeout = setTimeout(function(){
            callback.apply(this, arg);
        }, delay);
    };
}
function fn(e){
    console.log("fn" + e);
}
window.addEventListener("resize", debounce(fn, 500));

示例:输入框验证

function checkEmail(callback,delay){
    var t = null;
    return function(){
        var arg = arguments;
        clearTimeout(t);
        t = setTimeout(function(){
            callback.apply(this, arg);
        }, delay);
    }
}
function emailHander(e){
    var reg = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
    if(!reg.test(e.target.value)){
        e.target.style.backgroundColor = "red";
        setTimeout(function(){
            e.target.style.backgroundColor = "";
        },800);
        console.log("格式不正确");
    }else{
        e.target.style.backgroundColor = "green";
        console.log("正确");
    }
}
var email = document.getElementById("email");
email.addEventListener("keyup", checkEmail(emailHander,800), false);

节流:防抖是多次触发事件,目标函数只执行一次,不管触发这些事件用了多少时间;而节流是在一定时间内只会执行一次,稀释函数的执行频率,进而达到缓慢执行目标函数的目的;如:

使用setTimeout():

var timeoutId;
window.addEventListener("resize", function(event){
    if(!timeoutId){
        timeoutId = setTimeout(function(){
            console.log("延迟执行");  // 只执行一次
            clearTimeout(timeoutId); 
            // timeoutId = null;  // 换成这一句,500ms执行一次
        },500);
    }
});
// 或
function resizeHandler(event){
    // 先解除绑定
    window.removeEventListener("resize", resizeHandler);
    console.log(document.documentElement.clientWidth);
    // 重新绑定
    setTimeout(function(){
        window.addEventListener("resize", resizeHandler);
    },500);
}
window.addEventListener("resize", resizeHandler);

使用setInterval():

将事件处理程序放在setInterval()函数中,每隔一段时间,去监测一次是否发生了scroll事件,然后执行,而scroll事件只是改变scrolled的值,不会影响性能,如:

var scrolled = false;
window.addEventListener("scroll", function(){
    scrolled = true;
});
setInterval(function(){
    if(scrolled){
        // 代码处理
        console.log("scroll");
        scrolled = false;
    }
},500);

使用时间戳:

var startTime = new Date();
var flag = false;
var delay = 200;
window.addEventListener("resize", function(){
    startTime = new Date();
    if(flag === false){
        flag = true;
        setTimeout(resizeEnd, delay);
    }
});
function resizeEnd(){
    if(new Date() - startTime <= delay)
        setTimeout(resizeEnd, delay);
    else{
        flag = false;
        console.log(document.documentElement.clientWidth);
    }
}

或:

function thorttle(callback, delay){
    var timeout = 0;
    var now = new Date() - 0;
    return function(){
        var arg = arguments;
        var last = new Date() - 0;
        clearTimeout(timeout);
        if(last - now >= delay){
            now = last; // 将上次执行的时间赋值给now
        }else{
            // 目标函数在这里执行
            timeout = setTimeout(function(){
                callback.apply(this, arg);
            }, delay);
        }
    };
}
function fn(e){
    console.log("节流" + e);
}
window.addEventListener("resize", thorttle(fn, 500));

使用开关:

设置一个开关,一次只能有一个触发执行,并对执行设置计时一段时间再执行,执行完毕之后再解锁;如:滚动事件;

function loadMore(){
    var canRun = true;
    return function(){
        if(!canRun) return;
        canRun = false;
        setTimeout(function(){
            var docHeight = document.documentElement.offsetHeight; // 文档高度
            var winHeight = window.innerHeight;  // 窗口高度
            var scrollDistance = document.documentElement.scrollTop; // 滚动距离
            // 当滚动到底部时
            if(docHeight - (winHeight + scrollDistance) <= 100){
                console.log("Loading...");
            }
            canRun = true;
        },600);
    }
}
window.addEventListener("scroll", loadMore());

两者比较:

节流在某个时间段内,目标函数能执行一次,限制目标函数的执行频率,不管事件触发了多少次;

防抖是多次触发事件,目标函数只执行一次,不管触发了这些事件用了多少时间;

节流函数限制目标函数的执行频率,有连续变化的效果,适用于关注变化过程的操作,可以调整目标函数执行频率使得变化更加平滑,比如动画、改变窗口时执行某些操作等,常用事件resize、scroll、mouseWheel、touchmove、mouseover等;

防抖函数适用于更关注结果的操作,不太关注操作过程,常见的事件有 input、keyup等;

FocusEvent焦点事件:

焦点事件会在页面元素获得或失去焦点时触发,或某些对象调用了focus()和blur()方法也会触发该事件;利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪;

有以下6个焦点事件:

  • blur:在元素失去焦点时触发,该事件不会冒泡;
  • focus:在元素获得焦点时触发,该事件不会冒泡;
  • DOMFocusIn:在元素获得焦点时触发,该事件与HTML事件focus等价,但它冒泡;DOM3级事件废弃了它,应使用focusin;FF及低版本的IE不支持;
  • DOMFocusOut:在元素失去焦点时触发,该事件是HTML事件blur等价,但它冒泡;DOM3级事件废弃了它,应使用focusout;FF及低版本的IE不支持;
  • focusin:在元素获得焦点时触发,该事件与HTML事件focus等价,但它冒泡;
  • focusout:在元素失去焦点时触发,该事件是HTML事件blur等价,但它冒泡;

要确定浏览器是否支持这些事件,可以检测:

var isSupported = document.implementation.hasFeature("FocusEvent", "3.0");

可以获得焦点事件的元素一般指的是window或表单控件或是超链接或是可编辑元素,称为focusable元素;但在IE中普通元素也可以获得焦点;

window.onblur = function(){ document.title = "你离开了";};
window.onfocus = function(){ document.title = "你来了";};
var oDiv = document.getElementById("mydiv");
oDiv.contentEditable = true;
// oDiv.tabIndex = 1;  // 或者添加tabIndex属性也可以
oDiv.focus();
console.log(document.activeElement);
oDiv.addEventListener("focus", function(event){
    console.log(event.target.id);
});

这一类事件中最主要的两个是focus和blur,它们都是Javascript早期就得到所有浏览器都支持的事件,这两个事件的最大问题是它们不冒泡,因此,才出现了IE的focusin和focusout与Opera的DOMFocusIn和DOMFocusOut这两对事件,后来IE的方式被DOM3级事件纳为标准方式;

var btn = document.getElementById("btn");
btn.addEventListener("focus", function(event){
    console.log(event);  // FocusEvent
});

FocusEvent类:

表示和焦点相关的事件类,其继承自UIEvent类;

其自身只添加了一个属性relatedTarget,代表此次事件的相关目标,但在实用中,例如切换浏览器tab标签时,为了安全起见,所有浏览器都会返回null;

当焦点从页面中的一个元素移动到另一个元素,会依次触发下列事件:

blur在失去焦点的元素上触发、focusout在失去焦点的元素上触发、DOMFocusOut在失去焦点的元素上触发、focus在获得焦点的元素上触发、focusin在获得焦点的元素上触发、DOMFocusIn在获得焦点的元素上触发;但IE不遵循此顺序;

function handler(event){
    console.log(event.target.id + ":" + event.type);
}
var btn = document.getElementById("btn");
btn.addEventListener("DOMFocusIn", handler, false);
btn.addEventListener("DOMFocusOut", handler, false);
btn.addEventListener("focus", handler, false);
btn.addEventListener("blur", handler, false);
btn.addEventListener("focusin", handler, false);
btn.addEventListener("focusout", handler, false);
btn.focus();
var txt = document.getElementById("txt");
txt.addEventListener("focus", handler, false);
txt.addEventListener("blur", handler, false);
txt.addEventListener("DOMFocusIn", handler, false);
txt.addEventListener("DOMFocusOut", handler, false);
txt.addEventListener("focusin", handler, false);
txt.addEventListener("focusout", handler, false);

其中,blur、DOMFocusOut和focusout的事件目标是失去焦点的元素,而focus、DOMFocusIn和focusin的事件目标是获得焦点的元素;

另外,如果同时注册了focusin和DOMFocusIn或focusout和DOMFocusOut,在IE中只会触发focusin和focusout;

另外,除了focus和blur事件,其他事件只能通过DOM2级事件添加,如:

var btn = document.getElementById("btn");
btn.onfocusin = function(event){
    console.log(event);  // 不会触发
};
btn.onDOMFocusIn = function(event){
    console.log(event);  // 不会触发,IE会触发
};

onfocusin和onfocusout事件,在HTML事件处理中,除FF,其它都支持;DOMFocusIn和DOMFocusOut,在HTML事件处理中,都不支持;

小应用:改变文本框样式,如:

<style>
    .focusInput{border:1px solid purple;outline: none; background-color: rgba(0, 0, 0, .6);}
</style>
<script>
function focusInput(focusClass){
    var elements = document.getElementsByTagName("input");
    for(var i=0,len=elements.length; i<len; i++){
        var elt = elements[i];
        if(elt.type == "text"){
            elt.onfocus = function(){
                this.className = focusClass;
            };
            elt.onblur = function(){
                this.className = "";
            }
        }
    }
}
window.onload = function(){
    focusInput('focusInput');
}
</script>

focusin和focusout事件是冒泡的,因此可以使用事件代理,如:

<form id="myform" action="demo.jsp">
<input type="text" id="firstname" value="firstname" />    
<input type="text" id="lastname" value="lastname" />    
</form>
<script>
var myform = document.getElementById("myform");
myform.addEventListener("focusin", function(event){
    event.target.className = "focused";
});
myform.addEventListener("focusout", function(event){
    event.target.className = "";
});
</script>

示例:验证数据,如:

<style>
    .invalid{border-color:red;}
    #error{color:red}
</style>
<p>邮箱:<input type="email" id="email" /></p>
<div id="error"></div>
<script>
var email = document.getElementById("email");
var error = document.getElementById("error");
email.addEventListener("blur", function(event){
    if(!event.target.value.includes("@")){
        event.target.classList.add("invalid");
        error.innerHTML = "请输入正确的邮箱";
    }
});
email.addEventListener("focus", function(event){
    if(this.classList.contains("invalid")){
        this.classList.remove("invalid");
        error.innerHTML = "";
    }
});
</script>

或者使用focus()和blur()方法,如:

<style>
    .error{background-color: red !important;}
</style>
<p>邮箱:<input type="email" id="email" /></p>
<script>
var email = document.getElementById("email");
email.addEventListener("blur", function(event){
    if(!event.target.value.includes("@")){
        event.target.classList.add("error");
        event.target.focus();
    }else
        event.target.classList.remove("error");
});
</script>

示例:一个HTML编辑器;

<style>
.container,.editor{width:400px; height: 150px; display: block;}
.container{padding: 2px; border:1px solid;}
.editor{padding: 0; border:2px solid blue;}
.editor:focus{outline: none;}
</style>
<div id="container" class="container">
    <h1>Web前端开发</h1>
</div>
<script>
var area = null;
var container = document.getElementById("container");
container.addEventListener("click", function(event){
    editStart();
},false);
function editStart(){
    area = document.createElement("textarea");
    area.className = "editor";
    area.value = container.innerHTML;
    area.addEventListener("keydown", function(event){
        if(event.keyCode == 13)
            this.blur();
    },false);
    area.addEventListener("blur", function(event){
        editEnd();
    });
    container.replaceWith(area);
    area.focus();
}
function editEnd(){
    container.innerHTML = area.value;
    area.replaceWith(container);
}
</script>

当用户按下Enter键或失去焦点时,<textarea>变回<div>,其内容在<div>中变为HTML;

.应用程序标签

  • DataList(数据列表)

  • Progress(进度条)

  • Meter(数值显示器) 示例:

<html lang="en"><head> <meta charset="UTF-8"> <title>应用程序标签</title> <style> .my-progress{ -webkit-appearance: none; } </style></head><body><!--数据列表,呈现需要载体--><input type="text" list="data-list"><datalist id="data-list"> <option value="张三"></option> <option value="李四"></option></datalist><!--进度条--><progress></progress><!--progress默认最大值是1,可以设定自己想要的值--><progress class="my-progress" value="30" max="100"></progress><meter min="0" max="100" low="40" high="90" optimum="100" value="91">A+</meter></body>

可以利用数据列表(data-list)做一个那种带下拉框的输入框,可以根据用户输入的内容匹配下拉框的内容(select2也可以实现这种需求,而且最低可以兼容到IE8,推荐用select2),后面的progress和meter样式不好控制,用的不太多。

2.自定义属性(data-*)

在html5中可以自定义数据,以data开头,利用这个自定义标签我们可以把属性暂时存储到页面中,在js中可以使用,例子如下:

<html lang="en"><head> <meta charset="UTF-8"> <title>data属性</title></head><body> <ul id="list"> </ul></body><script> // 键是ID 值是信息 var data = { 01: { name: "张三1", age: 18 }, 02: { name: "张三2", age: 19 }, 03: { name: "张三3", age: 20 } }; var list = document.getElementById("list"); for(var id in data){ var item = data[id]; var liem = document.createElement("li"); liem.innerHTML = item.name; //自定义data-*属性 liem.setAttribute("data-name",item.name); liem.setAttribute("data-age",item.age); list.appendChild(liem); //点击获取dataset属性 liem.addEventListener("click",function(){ //下面这两种方式均可以,都不带data- alert(this.dataset['name']); alert(this.dataset.name); }) }</script>

下面这篇文章总结的很好,可以参考:http://blog.csdn.net/qq_31851435/article/details/53100691

3.新的表单提交方式

传统的一共有9中表单提交方式,html5新增了几种,目前只用在移动端,pc端兼容性有问题,而且在输入的时候并没有进行验证,比如类型是email的,并没有校验邮箱格式,还是需要我们自己手动写正则表达式验证的,只是在手机端当唤起输入法的时候会自动切换到英文输入法。

<head> <meta charset="UTF-8"> <title>新的表单提交方式</title></head><body><h3>传统的表单提交方式(9种)</h3>用户名:<input type="text"> <br>密码:<input type="password"><br>性别:<input type="radio"><br>课程:<input type="checkbox"><br>隐藏:<input type="hidden">文件:<input type="file"><br>按钮:<input type="button" value="按钮"><br>表单:<input type="submit"><br>重置:<input type="reset"><br><hr><h3>新的表单提交方式(目前只用在移动端)</h3>颜色:<input type="color"><br>网址:<input type="url"><br>搜索:<input type="search" results="10" placeholder="Search..."><br>邮箱:<input type="email" pattern="^\*.com"><br>日期:<input type="date" min="2015-09-01" max="2018-09-01"><br>图片:<input type="image"><br>范围:<input type="range" min="0" max="50" value="10"><br>数字:<input type="number"></body>

4.新的选择器

html5提供新的选择器,querySelector选择单个的元素,返回满足条件的第一个元素,是一个dom元素,querySelectorAll选择全部的元素,返回满足条件的全部元素,是一个dom数组,里面可以是id,标签或class类。

<html lang="en"><head> <meta charset="UTF-8"> <title>新选择器</title></head><body><h3>新选择器</h3><ul> <li class="item">item1</li> <li class="item">item2</li> <li class="item">item3</li> <li class="item">item4</li> <li class="item">item5</li> <li class="item">item6</li> <li class="item">item7</li> <li class="item">item8</li> <li class="item">item9</li> <li class="item">item10</li></ul></body><script> //匿名函数,可以将作用域分隔开 (function(){ var liem = document.querySelector(".item"); //只选择第一个 console.log(liem.innerHTML); var lis = document.querySelectorAll("ul>li"); //选择全部 for(var i=0;i<lis.length;i++){ console.log(lis[i]); lis[i].addEventListener("click",function(){ debugger; //可以调试代码。类似于浏览器打断点 console.log(this.innerHTML); }) } })()</script>

5.元素类列表(Element.classList)

新H5中DOM对象多了一个classList属性,是一个数组:

  • add 添加一个新的类名

  • remove 删除一个类名

  • contains 判断是否包含一个指定的类名

  • toggle 切换一个类名。 element.toggle('class-name',[addorremove]),第一个参数是类名,第二个参数是布尔值,如果是true,则添加类名,如果是false,则去掉类名。示例:

<head> <meta charset="UTF-8"> <title>元素类列表</title> <link rel="stylesheet" href="css/bootstrap.css"></head><body><div class="container"> <div class="collapse navbar-collapse"> <nav class="navbar navbar-default"> <a class="navbar-brand" href="#">Brand</a> <ul class="nav navbar-nav"> <li><a href="#">Home</a></li> <li><a href="#">Link</a></li> <li><a href="#">Link</a></li> </ul> </nav> </div></div><script> //匿名函数 (function(){ /** 点击时添加和删除类名**/ var lis = document.querySelectorAll(".nav li"); for(var i=0;i<lis.length;i++){ lis[i].addEventListener("click",function(e){ //去掉原先所有的样式 for(var j=0;j<lis.length;j++){ lis[j].classList.remove("active"); } //添加点击样式 this.classList.add("active"); e.preventDefault(); //取消事件的默认操作 }) } /** 点击时存在类名则删除,不存在类名则添加**/ var elem = document.querySelector(".navbar-brand"); //点击时如果存在active这个属性则去掉,如果不存在这个属性则加上 elem.addEventListener("click",function(e){ //判断是否包含active这个类名 var isExist = elem.classList.contains("active"); this.classList.toggle("active",!isExist); e.preventDefault(); }) })()</script></body>

6.访问历史Api

在HTML5中可以通过window.history操作访问历史状态,让一个页面可以有多个历史状态

  • window.history.forward(); // 前进

  • window.history.back(); // 后退

  • window.history.go(); // 刷新

  • history.pushState(放入历史中的状态数据, 设置title(现在浏览器不支持), 改变历史状态),通过JS可以加入一个访问状态

<head> <meta charset="UTF-8"> <title>历史记录</title></head><body><input type="button" value="测试历史记录" onclick="addHistory()"></body><script> //添加历史记录 function addHistory(){ //判断浏览器是否支持历史记录,毕竟是h5的新特性,低版本浏览器不支持 if(window.history && history.pushState){ //支持 history.pushState(new Date().toLocaleDateString(),"设置历史记录的标签,但是目前浏览器还不支持","?demo="+new Date().toLocaleTimeString()); }else{ console.log("抱歉,浏览器不支持历史记录") } } //下面这个事件是点击浏览器中的前进或后退时触发 window.addEventListener("popstate",function(e){ console.log("点击了历史按钮"+e.state); })</script>

7.全屏Api

H5中可以用requestFullScreen()方法实现指定元素的全屏显示(类似于浏览器中按F11)。首先需要获取需要全屏显示的元素,然后判断浏览器是属于谷歌内核还是火狐内核或者其它浏览器,然后调用响应方法即可。(注意:目前这个特性浏览器兼容性还比较差,谷歌和火狐可以)

var elem = 需要全屏的元素;if (elem.webkitRequestFullScreen) { elem.webkitRequestFullScreen();} else if (elem.mozRequestFullScreen) { elem.mozRequestFullScreen();} else if (elem.requestFullScreen){ elem.requestFullScreen();}
<head> <meta charset="UTF-8"> <title>全屏显示</title></head><body><h3 style="cursor: pointer">点我全屏显示</h3><p>测试全屏显示测试全屏显示测试全屏显示测试全屏显示测试全屏显示测试全屏显示 测试全屏显示测试全屏显示测试<br> 全屏显示测试全屏显示测试全屏显示测试全屏显示测试全屏显示</p><script> //匿名函数 (function () { //获取事件源和要全屏显示的元素 var elem = document.querySelector("p"); document.querySelector("h3").addEventListener("click",function(e){ //判断浏览器是否支持全屏显示 if (elem.webkitRequestFullScreen) { elem.webkitRequestFullScreen(); } else if (elem.mozRequestFullScreen) { elem.mozRequestFullScreen(); } else if (elem.requestFullScreen){ elem.requestFullScreen(); }else{ console.log("浏览器不支持全屏显示") } }); })()</script></body>

8.Application Cache离线访问技术

在H5中可以利用Application Cache实现离线访问技术,测试时可以通过谷歌浏览器——NetWork——No throttling(Disable cache后面的那个下拉框)——下拉选择Offline(模拟未联网的情形)。示例如下:

html代码:<html lang="en" manifest="cache.manifest"><head> <meta charset="UTF-8"> <title>离线访问</title> <link rel="stylesheet" href="style.css"></head><body><p>这是html页面中的内容</p><script src="script.js"></script></body>style.css文件代码body::before{ content: "这是Css中引入的内容"; font-size: 40px;}script.js文件代码:document.write("这是js中引入的内容")cache.manifest文件代码:CACHE MANIFEST# version 1.0.1CACHE: 08离线访问.html script.js style.cssNETWORK: *

解释:在html代码中通过css的伪类和js的document.write函数分别输出两句话,模拟请求其它文件,然后在manifest(缓存清单)中定义了一个cache.manifest的缓存清单文件,文件里首先定义了当前软件的版本,下面的CACHE,代表断网情况下,从下面定义的文件中读取文件,NETWORK代表联网时候要读取的文件,星号代表全部。即断网时从我定义的文件中读取文件,联网时,读取全部文件,这样便可实现离线访问。

9.web网页存储

h5中提供sessionStorage和localStorage两种数据存储方式,前者存储的内容只在本次会话中存储,浏览器关闭数据消失,后者是可以永久存储在浏览器中,除非手动或通过程序删除。这两种存储方式比cookie存储的数据量大,而且可以存储对象数据(cookie只能存储字符串数据)。

<head> <meta charset="UTF-8"> <title>网页存储</title></head><body><textarea rows="5" cols="30" id="txt-data"></textarea><input type="button" value="设置数据" id="set-data"><input type="button" value="获取数据" id="get-data"><script> (function(){ var txtValue = document.querySelector("#txt-data"); console.log(txtValue.value); //设置数据 document.querySelector("#set-data").addEventListener("click",function (e) { //判断是否支持存储 if(window.sessionStorage){ sessionStorage.setItem("key",txtValue.value); }else{ console.log("还在用渣渣IE吗?升级吧。。。") } }) //获取数据 document.querySelector("#get-data").addEventListener("click",function (e) { //判断是否支持存储 if(window.sessionStorage){ txtValue.value = sessionStorage.getItem("key"); }else{ console.log("还在用渣渣IE吗?升级吧。。。") } }) })()</script></body>

10.操作文件Api

通过file表单选择文件,之后利用h5提供的文件api可以获取文件名,大小,类型,最后修改时间等文件信息。

<html lang="en"><head> <meta charset="UTF-8"> <title>操作文件</title> <link rel="stylesheet" href="css/bootstrap.css"></head><body> <div class="container"> <form> <input type="file" class="form-control hidden" id="btn_file" multiple> <input type="button" value="选择文件" class="btn btn-success" id="btn_select"> <div class="list-group" id="ul_list"> <!--待追加数据位置--> </div> </form> </div></body><script> (function(){ var btn_file = document.querySelector("#btn_file"); var btn_select = document.querySelector("#btn_select"); var ul_list = document.querySelector("#ul_list"); //利用“hidden”类将文件输入框隐藏,当点击“选择文件按钮时”,调用选择文件按钮,选择文件, // 因为input type="file"这个无法修改样式,把它隐藏,点击其它按钮时,在其它按钮中调用点击上传文件 btn_select.addEventListener("click",function(){ btn_file.click(); }) //选择文件完成后(即文件内容发生改变后) btn_file.addEventListener("change",function(e){ var files = btn_file.files; //获取所有文件,并遍历 for(var i=0;i<files.length;i++){ console.log(files[i]); var liem = document.createElement("li"); liem.setAttribute("class","list-group-item"); liem.innerHTML = ' <h4 class="list-group-item-heading">'+files[i].name+'</h4>'+ '<p class="list-group-item-text">'+files[i].lastModifiedDate.toLocaleTimeString()+' '+(files[i].size/1024).toFixed(2)+'kb</p>' ul_list.appendChild(liem); } }) })()</script>

这里是用到了bootstrap的样式,因为file表单比较丑,而且无法改变样式,所以我们可以把文件表单隐藏(用hidden隐藏样式),提供一个按钮,当点击这个按钮时去雕砌选文件的弹框,multiple属性支持一次选择多个文件。

11.拖拽文件

拖拽文件时需要依次注册以下事件:

  • dragenter 注册拖拽进入事件

  • dragleave 注册拖拽离开事件

  • dragover 注册滑动事件(在这里面阻止默认事件)

  • drop 注册松手落地事件。 在拖动松手落地事件中判断拖进来的是文件还是图片地址还是文字,分别进行处理。e.dataTransfer获取拖动进来的信息。

<head> <meta charset="UTF-8"> <title>拖拽文件</title> <link rel="stylesheet" href="css/bootstrap.css"> <style> #target{ height: 200px; border: 5px dashed #c0c0c0; color: #505050; padding: 20px; font-size: 40px; cursor: pointer; text-align: center; -webkit-user-select: none; } #target.actived{ border-color: #888; color: #080808; box-shadow: 0 0 80px #e0e0e0 inset; } </style></head><body><div class="container"> <div class="page-header"> <h3>拖拽文件</h3> </div> <div class="jumbotron"> <p>我们这里测试拖拽文件</p> <img src="test.png"> </div> <div id="target"> 拖拽文件进来 <ul id="result" class="list-group"></ul> </div></div></body><script> (function(){ var target = document.querySelector("#target"); var fileList = document.querySelector('#result'); //注册拖拽进入事件 target.addEventListener("dragenter",function(){ this.classList.add("actived"); //添加样式 }) //注册拖拽文件离开事件 target.addEventListener("dragleave",function(){ this.classList.remove("actived"); //添加样式 }) //注册落地事件之前必须要先阻止默认事件,这里在滑动里面阻止默认事件 target.addEventListener("dragover",function(e){ e.preventDefault(); //阻止默认事件 e.stopPropagation(); }) //注册松手落地事件 target.addEventListener("drop",function(e){ //判断拖入进来的是文件,图片还是文字,分别处理 if(e.dataTransfer.files.length){ //拖入的是文件 var files = e.dataTransfer.files; for (var i = 0; i < files.length; i++) { var li = document.createElement('li'); li.setAttribute('class', 'list-group-item'); // 创建信息的子节点 li.innerHTML = '<h5 class="list-group-item-heading">' + files[i].name + '</h5><p class="list-group-item-text">' + files[i].lastModifiedDate.toLocaleDateString() + ' ' + files[i].lastModifiedDate.toLocaleTimeString() + ' ' + (files[i].size / 1024).toFixed(2) + 'KB</p>'; fileList.appendChild(li); } }else{ //不是文件 var data = e.dataTransfer.getData('text/plain'); //判断拖入的是文本还是图片 if(data){ //拖入的是文本,直接置换 target.innerHTML = data; }else{ //拖入的是图片 var imgElem = document.createElement("img"); imgElem.src = data; target.appendChild(imgElem); } } }) })()</script>

12.访问设备信息

h5提供了许多可以访问设备的Api,但是目前基本都应用在手机端,pc端使用非常有限。

  • 获取网络状态。

// × 所有PC浏览器现在都不支持 只有手机端的Firefoxvar connectionInfo = navigator.connection;或者下面这个方法:if (navigator.onLine){ console.log('online');}else{ console.log('offline');}
  • 获取重力感应方向

window.addEventListener('deviceorientation', function(event) { var a = event.alpha; // Y轴 上下方向 var b = event.beta; // Z轴 东西方向 var g = event.gamma; // X轴 南北方向});
  • 加速度计

window.addEventListener('devicemotion', function(e) { // 获取加速计数据 {x,y,z} var acceleration = e.accelerationIncludingGravity;});
  • 地理坐标

navigator.geolocation.getCurrentPosition(function(e) { // e.coords.longitude : 经度, e.coords.latitude : 纬度 document.querySelector('#result').innerHTML = JSON.stringify(e.coords);}, function(e) { document.querySelector('#result').innerHTML = JSON.stringify(e);});navigator.geolocation.watchPosition(success, error);

H5还有其它的一些特性,包括语义化标签,ARIA无障碍互联网应用,多媒体(包括音频,视频)等等,真正使用的时候再详细研究。