优秀的编程知识分享平台

网站首页 > 技术文章 正文

第56节 事件类型(Event Type)-Web前端开发之Javascript-王唯

nanyue 2024-10-18 07:40:36 技术文章 85 ℃

本内容是《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;

最近发表
标签列表