整合营销服务商

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

免费咨询热线:

SVG优雅显示Email防止机器人窃取

现实中,我们时常需要在网页中展示你的联系方式,其中Email邮件地址通常需要提供在页面上。但是在网络机器人泛滥的互联中,如果直接显示你邮件,则很可能被他们识别并拷贝,然后对你的邮件地址实施邮件轰炸。为了避免这个问题,需要利用技术手段来保护你的地址,使其只能被人眼看到,并且支持直接链接发送邮件,但是不能被网络机器人识别到,一般常用的方法是通过JS,Html,CSS对地址隐藏,但是编写代码有点繁琐,可能还要引入额外的JS库才能实现,而且还有一个缺点就是对一些限制级别的设备上,浏览器可能会禁用掉JS功能,这样会导致页面不能正常工作。此处给大家介绍一种基于SVG方法的邮件地址保护技术,可以极大程度的保护你免受机器人骚扰以及保证在浏览器禁用JS情况下仍然可以正常工作。

优点

在JavaScript禁用的情况下工作

主要优点 这种基于SVG的电子邮件保护方法没有用的任何的JavaScript代码。

因此,即使访问者浏览器禁用了JavaScript,页面上显示的电子邮件地址仍然可用、可访问和受到保护,同时保持安全并免受垃圾邮件机器人的攻击。

允许标准mailto:链接

与其他不需要JavaScript的方法(例如,通过插入不可见的HTML注释或插入可见元素并随后通过CSS隐藏它们来混淆电子邮件地址)不同,这基于SVG的方法 允许标准 mailto:链接。主要区别是:mailto:链接存在于外部 SVG文档内部,而不是 内部引用的HTML文档。

像图像一样隐藏内容,像文本一样可复制

第三个优点是嵌入式SVG类似于图像,但不是图像。作为嵌入超文本文档中的替换元素,SVG可以像图像一样有效地隐藏垃圾邮件地址的电子邮件地址。

但严格来说,SVG是图形文档,而非实际图像。

因此,与图像不同,人类访问者仍然可以通过右键单击电子邮件地址来复制电子邮件地址 <text>嵌入SVG中的元素。这对于传统图像方法来说,无法多做到手动复制地址(但是可以使用图像文本识别OCR技术来实现)。

基本方法

我们以一个最简单的Emil链接地址共享为例。示例中由两个两个文件组成:其中SVG图形文档通过<object>标签方式嵌入到主HTML页面中,基本语法如下:

<object data="svg-email-protection.svg" type="image/svg+xml" /></object>。

注意,同一个SVG图形文档支持在多个地方,进行嵌入。主页面HTML(main.htm)源代码如下,一个很简单的页面:

<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="utf-8">
<title>SVG Email Protection</title>
<style>
.cc {
width: 180px;
height: 24px;
vertical-align: middle;
}
</style>
</head>
<body>
<p>请邮件联系我: <object class="cc" data="svg-email-protection.svg" type="image/svg+xml"></object></p>
</body>
</html>

SVG文档(svgprot-chongchong)代码:

<svg xmlns="http://www.w3.org/2000/svg"
lang="en-GB"
aria-labelledby="title"
viewBox="0 0 200 24">
<title id="title"> SVG Email Protection</title>
<defs>
<style type="text/css"><![CDATA[
rect {
width: 200px;
height: 24px;
fill: rgb(255, 255, 255);
}
a:focus rect,
rect:hover {
rx: 4px;
ry: 4px;
fill: rgb(0, 0, 255);
}
text {
font-size: 16px;
fill: rgb(0, 0, 255);
pointer-events: none;
}
a:focus text,
rect:hover + text {
fill: rgb(255, 255, 255);
font-weight: 900;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2);
text-decoration: underline 1px solid rgb(255, 255, 255);
text-underline-offset: 5px;
}
]]></style>
</defs>
<a href="mailto:chongchong[at]ijz.me" aria-label="点击发邮件">
<rect />
<text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle"> chongchong[at]ijz.me</text>
</a>
</svg>

将以上两个文件放到同意目录,然后用浏览器打开主页面main.hm就可以看到效果了

总结

本文给大家介绍了一种基于SVG文档的优雅的邮件保护方法,可以极大的免受网络机器人窃取你的邮件地址进行骚扰攻击,同时支持emailto链接,支持无JS浏览器下正常工作,支持手动邮件复制等优点,当然该方法也是只能抵挡一般性规模化工作的Web机器人攻击,如果遇到高级机器人,比如可以模仿真人访问行为的,可以分析语法找到SVG文件进行获取地址的高级机器人则无防御能力。

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

MouseEvent鼠标事件:

DOM2级事件中定义了7个,DOM3级事件增加了2个鼠标事件:

  • click:单击或者回车(一般是左按钮,可以通过键盘和鼠标进行);
  • dbclick:双击(从技术上说,这个事件不是DOM事件规范中规定);
  • mousedown:按下任意鼠标键;
  • mouseup:释放鼠标按钮时触发;
  • mousemove:在元素内部移动时重复地触发;
  • mouseover:当鼠标进入元素时触发;
  • mouseout:在鼠标光标位于一个元素上方,再将其移入另一个元素时触发;又移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素;
  • mouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发;类似于mouseover,但该事件不冒泡,而且在光标移动到后代元素上不会触发,该事件由IE引入,在DOM3级事件中被纳入规范;
  • mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发,类似于mouseout,但该事件不冒泡,而且在光移动到后代元素上不会触发,该事件由IE引入,在DOM3级事件中被纳入规范;
  • contextmenu:鼠标右击出现上下文菜单时触发,这个事件是在HTML5中定义的,其可以取消;

鼠标事件中还有一类滚轮事件,只包括一个mousewheel事件,但此事件已归WheelEvent类了;

document.addEventListener("click", function(event){
    console.log(event);  // MouseEvent
},false);
document.addEventListener("mousewheel", function(event){
    console.log(event);  // WheelEvent
},false);

可以检测浏览器是否支持所有事件,如:

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

页面上的所有元素都支持鼠标事件;除了mouseenter和mouseleave,其他所有鼠标事件都会冒泡,也可以取消,而取消鼠标事件将会影响浏览器的默认行为,也会影响其他事件;

click事件:

在一个元素上被按下和放开时,click事件就会被触发,包括鼠标单击(通常是左按钮)或键盘回车,或者在脚本中为一个对象执行了click()方法也会触发该事件;

var btn = document.getElementById("btn");
btn.click();

在一个focusable元素上单击时,该元素就获得了焦点,就会触发focus事件和click事件;

function handler(event){
console.log(event.type);
}
var txt = document.getElementById("txt");
txt.addEventListener("click", handler,false);
txt.addEventListener("focus", handler,false);

其触发的顺序为:focus、click;

只有在同一个元素上相继触发mousedown和mouseup事件,才会触发click事件;如果mousedown或mouseup中一个被取消,就不会触发click事件,类似只有触发两次click事件才会触发一次dbclick事件;

这4个鼠标事件触发顺序:
mousedown –> mouseup –> click –> mousedown –> mouseup –> click –> dbclick;

mouseover和mouseout事件:
当鼠标进入或移出元素时触发这两个事件;

示例:鼠标悬停改变表格行的背景色,如:

<style>
#mytable{width: 400px; border-collapse: collapse;}
#mytable td{ height: 20px; border: 1px solid #000;}
</style>
<table id="mytable">
<tr>
<td></td> <td></td> <td></td>
</tr>
<!-- 多行 -->
</table>
<script>
// 表格名称、奇数行背景、偶数行背景、鼠标经过背景、点击后背景
function changeBg(table, oddColor, evenColor,overColor, clickColor){
var rows = document.getElementById(table).rows;
for(var i=0; i < rows.length; i++){
var tr = rows[i];
tr.style.backgroundColor = (tr.sectionRowIndex % 2 == 0) ? oddColor : evenColor;
tr.original = true;
tr.addEventListener("click", function(event){
if(this.original){
this.original = false;
this.style.backgroundColor = clickColor;
}else{
this.original = true;
this.style.backgroundColor = (this.sectionRowIndex % 2 == 0) ? oddColor : evenColor;
}
});
tr.addEventListener("mouseover", function(){
if(this.original)
this.style.backgroundColor = overColor;
});
tr.addEventListener("mouseout", function(){
if(this.original)
this.style.backgroundColor = (this.sectionRowIndex % 2 == 0) ? oddColor : evenColor;
});
}
}
changeBg("mytable", "#FFF", "#ccc", "#cfc", "#f00");
</script>

mouseover和mouseout事件会冒泡,因此,当触发mouseout事件时,有可能鼠标真的离开了目标元素,但也有可能是从这个元素移动到它的子元素上,或者从一个子元素移动到另一个子元素,所以在这种情况下,需要判断鼠标的确切位置;

<div id="mydiv">
<div id="div1">div1</div>
<div id="div2">div2</div>
</div>
<script>
var oDiv = document.getElementById("mydiv");
oDiv.addEventListener("mouseover", function(event){
console.log("mouseover:" + event.target.id);
},false);
oDiv.addEventListener("mouseout", function(event){
console.log("mouseout:" + event.target.id);
},false);
</script>

DOM3提供了两个不冒泡的对应版本mouseenter和mouseleave,如:

oDiv.addEventListener("mouseenter", function(event){
console.log("mouseenter:" + event.target.id);
},false);
oDiv.addEventListener("mouseleave", function(event){
console.log("mouseleave:" + event.target.id);
},false);

示例:图片遮罩,如:

<style>
*{margin:0; padding: 0;}
ul,li{list-style: none;}
ul{display:flex; flex-wrap: wrap;}
li{width: 200px;}
li>a{display: block; width: 100%; position: relative;}
li img{width:200px;}
</style>
<ul id="mylist">
<li><a href="#" title="天下第一山"><img src="images/1.jpg"></a></li>
<li><a href="#" title="zeronetwork"><img src="images/2.jpg"></a></li>
<li><a href="#" title="Javascript"><img src="images/3.jpg"></a></li>
<li><a href="#" title="Web前端开发"><img src="images/4.jpg"></a></li>
</ul>
<script>
window.onload = function(){
var mylist = document.getElementById("mylist");
var aList = mylist.getElementsByTagName("a");
for(var i=0,len=aList.length; i<len; i++){
var a = aList[i];
var mask = null;
a.addEventListener("mouseenter", function(event){
mask = this.getElementsByClassName("mask")[0];
if(!mask){
mask = document.createElement("div");
mask.className = "mask";
}
mask.style.backgroundColor = "rgba(0,0,0,0.8)";
var computedStyle = document.defaultView.getComputedStyle(this, null);
mask.style.width = computedStyle.width;
mask.style.height = computedStyle.height;
mask.style.position = "absolute";
mask.style.color = "#FFF";
mask.style.textAlign = "center";
mask.style.lineHeight = computedStyle.height;
mask.innerHTML = this.title;
this.insertBefore(mask, this.firstChild);
},false);
a.addEventListener("mouseleave", function(event){
var mask = this.getElementsByClassName("mask")[0];
console.log(this);
if(mask){
this.removeChild(mask);
}
},false);
}
}
</script>

mouseleave和mouseenter事件的行为与CSS的:hover 伪类非常相似;

mousemove事件,会频繁触发,所以,在其事件处理程序中不能放置计算密集的任务,或者使用事件节流的方式;

鼠标事件对象:

鼠标事件属于MouseEvent类,该类指的是用户与指针设备( 如鼠标 )交互时发生的事件,其继承自UIEvent类;
MouseEvent类定义了一组专属于鼠标事件的属性,描述了当事件发生时鼠标的位置和按键的状态,也包含了是否有辅助键被按下等所有信息;

客户区坐标位置:
clientX与clientY属性:取得鼠标相对于浏览器视口的坐标位置;

var oDiv = document.getElementById("mydiv");
EventUtil.addHandler(oDiv, "click", function(event){
event = EventUtil.getEvent(event);
console.log(event);
console.log(event.clientX + "," + event.clientY);
});

注,这个位置不包括页面滚动的距离,如果加上窗口的滚动偏移量,就会把鼠标位置转换成文档坐标;
所有浏览器也都实现了x和y属性,其是clientX和clientY的别名;

示例:计算鼠标拖动的直线距离,如:

var obj = {};
function downHandler(event){
obj.x = event.x;
obj.y = event.y;
}
function upHandler(event){
obj.mx = event.x - obj.x;
obj.my = event.y - obj.y;
obj.d = Math.sqrt((Math.pow(obj.mx,2) + Math.pow(obj.my,2)));
console.log(obj.mx, obj.my, obj.d);
}
document.addEventListener("mousedown", downHandler, false);
document.addEventListener("mouseup", upHandler, false);

示例:自定义鼠标样式,如:

document.documentElement.style.cursor = "none";
var cursor = document.createElement("span");
cursor.style.width = "20px";
cursor.style.height = "20px";
cursor.style.position = "absolute";
cursor.style.backgroundColor = "#000";
document.body.appendChild(cursor);
document.addEventListener("mousemove", function(event){
cursor.style.left = event.clientX - cursor.offsetWidth / 2 + "px";
cursor.style.top = event.clientY - cursor.offsetHeight / 2 + "px";
},false);

文档坐标位置:
pageX和pageY属性:取得鼠标光标在文档中的位置;

console.log(event.pageX + "," + event.pageY);

这个位置是包括滚动距离的,在文档没有滚动的情况下,pageX、pageY与clientX、clientY值相等;
IE8及以下不支持文档坐标,不过使用客户区坐标和滚动信息可以计算出来,也就是需要用到scrollLeft和scrollTop属性,如:

var oDiv = document.getElementById("mydiv");
EventUtil.addHandler(oDiv, "click", function(event){
event = EventUtil.getEvent(event);
var pageX = event.pageX,
pageY = event.pageY;
if(pageX == undefined)
pageX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft);
if(pageY == undefined)
pageY = event.clientY + (document.body.scrollTop || document.documentElement.scrollTop);
console.log(pageX + ":" + pageY);
});

屏幕坐标位置:screenX与screenY属性:取得相对于屏幕的位置;

console.log(event.screenX + ":" + event.screenY);

元素坐标位置:
offsetX和offsetY属性,返回与目标元素(target)的内填充边(padding edge)在X和Y轴方向上的偏移量;坐标原点为padding区左上角,因此,如果目标元素有边框,鼠标的位置位于边框上,该属性值为负值;

var oDiv = document.getElementById("mydiv");
oDiv.style.position = "relative";
oDiv.style.left = "100px";
oDiv.style.top = "50px";
document.addEventListener("click", function(event){
console.log("offsetX:" + event.offsetX + ",offsetY:" + event.offsetY);
},false);

如果元素滚动了,也包括offsetLeft和offsetTop值;

示例:绘图:

<style>
canvas{border: 1px solid;}
</style>
<canvas id="mydraw" width="560" height="360"></canvas>
<script>
var isDrawing = false;
var x=0, y=0;
var mydraw = document.getElementById("mydraw");
var context = mydraw.getContext("2d");
mydraw.addEventListener("mousedown", function(event){
x = event.offsetX, y = event.offsetY;
isDrawing = true;
});
mydraw.addEventListener("mousemove", function(event){
if(isDrawing === true){
drawLine(context, x, y, event.offsetX, event.offsetY);
x = event.offsetX, y = event.offsetY;
}
});
window.addEventListener("mouseup", function(event){
if(isDrawing === true){
drawLine(context, x, y, event.offsetX, event.offsetY);
x = 0, y = 0;
isDrawing = false;
}
});
function drawLine(content, x1, y1, x2, y2){
context.beginPath();
context.strokeStyle = "black";
context.lineWidth = 1;
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
context.closePath();
}
</script>

movementX和movementY属性:

返回当前事件和上一个mousemove事件之间鼠标在水平或垂直方向上的移动值;

即:currentEvent.movementX = currentEvent.screenX - previousEvent.screenX;

currentEvent.movementY = currentEvent.screenY - previousEvent.screenY;
document.addEventListener("mousemove", function(event){
console.log(event.movementX);
},false);

但IE不支持,并且此属性只有在mousemove事件中才能返回正确的值;

辅助键:

DOM规定了4个属性:shiftkey、ctrlKey、altkey和metaKey,表示当事件发生时shift、ctrl、alt和meta4个键的按下状态,均为布尔值,如果按下为true,反之为false;

var btn = document.getElementById("btn");
EventUtil.addHandler(btn, "click", handler);
function handler(event){
event = EventUtil.getEvent(event);
var keys = new Array();
if(event.shiftKey)
keys.push("shift");
if(event.altKey)
keys.push("alt");
if(event.ctrlKey)
keys.push("ctrl");
if(event.metaKey)
keys.push("meta");
console.log("keys:" + keys.join(","));
}

标准浏览器支持,但IE不支持metaKey属性;

getModifierState(key)方法:返回指定修饰键的当前状态;
参数key可以为Control、Alt、Shift和Meta,注意,大小写是敏感的;

btn.addEventListener("click", function(event){
console.log(event.getModifierState("Control"));
},false);

relatedTarget相关元素:

在发生mouseover和mouseout事件时,会涉及到多个元素;对mouseover而言,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素(这个相关元素也可以把它叫做次目标元素);对mouseout事件而言,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素;
DOM通过event对象的relatedTarget属性提供了相关元素的信息,该属性只针于mouseover、mouseout、mouseenter、mouseleave、focusin、focusout、dragenter(拖动元素进入)事件时才有值;对于其他事件,该属性为null;

var oDiv = document.getElementById("mydiv");
oDiv.addEventListener("mouseover", function(event){
console.log(event);
},false);
oDiv.addEventListener("mouseout", function(event){
console.log(event);
},false);

IE8及以下不支持relatedTarget属性,但提供了保存着同样信息不同的属性,在mouseover事件触发时,fromElement属性中保存了相关元素,toElement属性为事件目标;在mouseout事件触发时,toElement属性保存了相关元素,fromElement属性为事件目标;

document.addEventListener("mouseover", function(event){
console.log(event.fromElement);
console.log(event.toElement);
},false);

跨浏览器取得相关元素,添加到eventutil文件中;

getRelatedTarget: function(event){
if(event.relatedTaret)
return event.relatedTaret;
else if(event.toElement)
return event.toElement;
else if(event.fromElement)
return event.fromElement;
else
return null;
}

应用:

var oDiv = document.getElementById("mydiv");
EventUtil.addHandler(oDiv, "mouseout", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var relatedTarget = EventUtil.getRelatedTarget(event);
console.log(relatedTarget);
console.log(relatedTarget.tagName);
})

鼠标按钮:
对于mousedown和mouseup事件来说,在其event对象存在一个button属性,表示按下或释放的哪个鼠标按钮;可能有3个值:0主按钮;1中间按钮鼠标滚轮);2次按钮;

btn.addEventListener("mouseup", function(event){
console.log(event.button);
},false);

IE8及以下也提供了button,但其值与DOM的button属性有很大差异:0 没有按下按钮;1按下主按钮;2次按钮;3同时按下主次按钮;4中间按钮;5同时按下主和中间按钮;6次和中间按钮;7同时按下三个;

跨浏览器取得button属性,在eventutil文件中添加:

getButton: function(event){
if(document.implementation.hasFeature("MouseEvents","2.0"))
return event.button;
else{
switch(event.button){
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
case 2:
case 6:
return 2;
case 4:
return 1;
}
}
}

buttons属性:
当鼠标事件触发的时,如果多个鼠标按钮被按下,将会返回一个或者多个代表鼠标按钮的位掩码:

  • 0:没有按键或者是没有初始化;
  • 1:鼠标左键;
  • 2:鼠标右键;
  • 4:鼠标滚轮或者是中键;
  • 8:第四按键 (通常是“浏览器后退”按键);
  • 16:第五按键 (通常是“浏览器前进”);

buttons的值为各键对应值做按位与(+)计算后的值,例如,如果右键(2)和滚轮键(4)被同时按下,buttons的值为 2 + 4 = 6,如:

btn.addEventListener("mousedown", function(event){
console.log(event.button);
console.log(event.buttons);
},false);

属性button和buttons 是不同的,buttons可指示任意鼠标事件中鼠标的按键情况,而 button只能保证在由按下和释放一个或多个按键时触发的事件中获得正确的值;

which属性:
当鼠标事件触发时,表示被按下的按钮,其返回特定按键的数字,0为无、1为左键、2为中间滚轮、3为右键;其是非标准属性,但所有浏览器都支持;

btn.addEventListener("mousedown", function(event){
console.log(event.which);
console.log(event.button);
},false);

注意,此时应该注册mousedown或mouseup事件而不是click事件,因为右击或按下中间滚动不会触发click事件;

detail属性:
DOM2在event对象中提供了detail属性,用于给出有关事件的更多信息;对于鼠标click、mousedown和mouseup事件来说,detail中包含了一个数值,表示目标元素被单击了多少次,其它事件返回0;detail从1开始计数,每次单击都会递增;

console.log(event.detail);

用此属性就可以判断用户是单击、双击还是三击;如果鼠标在mousedown和mouseup之间移动了位置,则detail被重置为0;

IE也通过下列属性为鼠标事件提供了更多信息:

  • altLeft :布尔值,是否按下了Alt,如果为true,则altKey的值也为true;
  • ctrlLeft:布尔值,是否按下了ctrl,如果为true,则ctrlKey的值也为true;
  • shiftLeft:布尔值,是否按下了shift,如果为true,则shiftKey的值也为true;

这些属性只有IE支持;

示例:拖动文档元素,当鼠标按下或释放时,会触发mousedown和mouseup事件,因此,通过这两个事件,可以探测和响应鼠标的拖动;如:

function drag(elementToDrag, event){
    var scroll = {x:0, y:0};
    var startX = event.clientX + scroll.x;
    var startY = event.clientY + scroll.y;

    var origX = elementToDrag.offsetLeft;
    var origY = elementToDrag.offsetTop;

    var deltaX = startX - origX;
    var deltaY = startY - origY;

    if(document.addEventListener){
    document.addEventListener("mousemove", moveHandler, true);
    document.addEventListener("mouseup", upHandler, true);
    }else if(document.attachEvent){

    elementToDrag.setCapture();
    elementToDrag.attachEvent("onmousemove", moveHandler);
    elementToDrag.attachEvent("onmouseup", upHandler);
    // 作为mouseup事件看待鼠标捕获的丢失
    elementToDrag.attachEvent("onlosecapture", upHandler);
    }
    // 处理了这个事件,不让任何其他元素看到它
    if(event.stopPropagation)
    event.stopPropagation();
    else
    event.cancelBubble = true;
    // 现在阻止任何默认操作
    if(event.preventDefault)
    event.preventDefault();
    else
    event.returnValue = false;

    // 当元素正在被拖动时,这就是捕获mousemove事件的处理程序
    // 它用于移动这个元素
    function moveHandler(e){
    if(!e) e = window.event;
    // 移动这个元素到当前鼠标位置
    // 通过滚动条的位置和初始单击的偏移量来调整
    // var scroll = getScrollOffsets();
    var scroll = {x:0,y:0};
    elementToDrag.style.left = (e.clientX + scroll.x - deltaX) + "px";
    elementToDrag.style.top = (e.clientY + scroll.y - deltaY) + "px";
    // 同时不让任何其他元素看到这个事件
    if(e.stopPropagation)
    e.stopPropagation();
    else
    e.cancelBubble = true;
    }
    // 这是捕获在拖动结束时发生的最终mouseup事件的处理程序
    function upHandler(e){
    if(!e) e = window.event;
    // 注销捕获事件处理程序
    if(document.removeEventListener){
    document.removeEventListener("mouseup", upHandler, true);
    document.removeEventListener("mousemove", moveHandler, true);
    }else if(document.detachEvent){
    elementToDrag.detachEvent("onlosecapture", upHandler);
    elementToDrag.detachEvent("onmouseup", upHandler);
    elementToDrag.detachEvent("onmousemove", moveHandler);
    elementToDrag.releaseCapture();
    }
    // 并且不让事件进一步传播
    if(e.stopPropagation)
    e.stopPropagation();
    else
    e.cancelBubble = true;
    }
}

应用:

<div style="position: absolute;left:100px;top:100px; width:150px;background-color: purple;">
<div style="background-color: gray;" onmousedown="drag(this.parentNode, event);">标题栏-拖动我</div>
<p>Lorem ...</p>
</div>

CSS的pointer-events属性:

指定在什么情况下 (如果有) 某个特定的元素可以成为鼠标事件的target;主要用于 SVG元素;
可能的值为:

  • auto:默认效果,对于SVG内容,该值与visiblePainted效果相同;
  • none:元素永远不会成为鼠标事件的target;但是,当其后代元素的pointer-events属性指定其他值时,鼠标事件可以指向后代元素,在这种情况下,鼠标事件将在捕获或冒泡阶段触发父元素的事件侦听器;
  • visiblePainted、visibleFill、visibleStroke、visible、painted、fill、stroke、all;

该属性可以:

  • 阻止用户的点击动作产生任何效果;
  • 阻止缺省鼠标指针的显示;
  • 阻止CSS里的hover和active状态的变化触发事件;
  • 阻止JavaScript点击动作触发的事件;
<style>
/* 链接不会跳转 */
a[href="https://www.zeronetwork.cn/"]{
pointer-events: none;
}
</style>
<a href="https://www.zeronetwork.cn/">零点网络</a>
<script>
var link = document.querySelector("a");
function handler(event){
console.log(event);
}
// 以下均无效
link.addEventListener("click",handler,false);
link.addEventListener("mouseover",handler,false);
link.addEventListener("drag",handler,false);
</script>

此属性可以通过控制台改变,如在控制台输入:document.querySelector("a").style.pointerEvents = "auto";此时,超链接就可以触发了;

<style>
/* 使所有img对任何鼠标事件(如拖动、悬停、单击等)无反应 */
img{
pointer-events: none;
}
</style>
<img src="images/1.jpg" />
<script>
var img = document.querySelector("img");
function handler(event){
console.log(event);
}
// 以下均无效
img.addEventListener("click",handler,false);
img.addEventListener("mouseover",handler,false);
img.addEventListener("drag",handler,false);
</script>

除了指示该元素不是鼠标事件的目标之外,值none表示鼠标事件“穿透”该元素并且指定该元素“下面”的任何元素;如:

<style>
.container{position: relative; width: 200px; height: 150px;}
.mask{width: 100%; height: 100%; background-color: rgba(0, 0, 0, .5);
position: absolute; pointer-events: none; color:#FFF}
.container img{width: 100%; height: 100%;}
</style>
<div class="container">
<div class="mask"></div>
<a href="https://www.zeronetwork.cn"><img src="images/1.jpg" /></a>
</div>
<script>
var link = document.querySelector(".container>a");
link.addEventListener("mouseover", function(event){
var mask = event.currentTarget.parentNode.querySelector(".mask");
mask.innerHTML = event.currentTarget.title;
},false);
link.addEventListener("mouseout", function(event){
var mask = event.currentTarget.parentNode.querySelector(".mask");
mask.innerHTML = "";
},false);
</script>

示例:取得一个元素的相对鼠标坐标,如:

<style>
.parent{ width:400px; height:400px; padding: 50px; margin:100px; background:#f20; }
.child{ width:200px; height:200px; padding:50px; background:#ff0;}
.child-child{ width:50px; height:50px; background:#00d;}
</style>
<div class="parent" id="parent">
<div class="child">
<div class="child-child"></div>
</div>
</div>
<script>
var parent = document.getElementById("parent");
parent.addEventListener("click",function(event){
console.info(event.offsetX);
});
</script>

使用pointer-events属性后再获取,如为child和child-child类分别添加pointer-events属性,此时打印的值就是相对于parent元素的坐标了;

使用pointer-events来阻止元素成为鼠标事件目标不一定意味着元素上的事件侦听器永远不会触发,如果元素后代明确指定了pointer-events属性并允许其成为鼠标事件的目标,那么指向该元素的任何事件在事件传播过CSS添加pointer-events:none,再为其子元素添加pointer-events:all,此时在子元素上单击就可以触发注册在父元素上的事件处理程序;

当然,位于父元素但不在后代元素上的鼠标活动都不会被父元素和后代元素捕获(鼠标活动将会穿过父元素而指向位于其下面的元素);

var subchild = document.querySelector(".child-child");
subchild.addEventListener("click",function(event){
console.log("child-child");
parent.style.pointerEvents = "auto";
});

该属性也可用来提高滚动时的帧频;例如,当页面滚动时,如果恰巧鼠标悬停在某些元素上,则会触发其上的hover效果或触发onmouseover事件,有可能会造成滚动出现问题,此时,如果对body元素应用pointer-events:none,则会禁用了包括hover在内的鼠标事件,从而提高滚动性能;

<style>
#mydiv:hover{
background-color: lightgreen !important;
}
</style>
<div id="mydiv" style="height: 300px;background-color: purple;"></div>
<div style="height: 1000px;"></div>
<script>
var mydiv = document.getElementById("mydiv");
mydiv.addEventListener("mouseover", function(event){
console.log(event);
},false);
</script>

滚动页面时触发了mouseover事件及hover效果,可以在scroll事件中进行相应的处理,如:

var timeoutId = null;
window.addEventListener("scroll", function(event){
document.body.style.pointerEvents = "none";
if(timeoutId){
clearTimeout(timeoutId);
timeoutId = null;
}else{
timeoutId = setTimeout(function(){
console.log("解禁了");
document.body.style.pointerEvents = "auto";
},500);
}
},false);

部分浏览器不支持该属性,可以判断其支持情况,如:

var supportsPointerEvents = (function(){
var dummy = document.createElement("_");
if(!('pointerEvents' in dummy.style))
return false;
dummy.style.pointerEvents = 'auto';
// 如果是真的属性,则赋值不成功
dummy.style.pointerEvents = 'x';
document.body.appendChild(dummy);
var result = getComputedStyle(dummy).pointerEvents === 'auto';
document.body.removeChild(dummy);
return result;
})();
console.log(supportsPointerEvents);

WheelEvent滚轮事件:

当用户通过鼠标滚轮与页面交互,在垂直方向上滚动页面时(无论向上还是向下),就会触发mousewheel事件;该事件可以在任何元素上触发,最终会冒泡到document或window对象,也可以阻止其默认行为;

document.addEventListener("mousewheel", function(event){
    console.log(event);  // WheelEvent
},false);

WheelEvent类:

表示用户滚动鼠标滚轮或类似输入设备时触发的事件,用以替代MouseWheelEvent和MouseScrollEvent,mousewheel实际上是属于MouseWheelEvent类,而后面要讲的Firefox中的DOMMouseScroll属于MouseScrollEvent类,它们两者都不属于标准,兼容性也不好,为了统一两者,就出现了标准的WheelEvent类;

WheelEvent类继承自MouseEvent类(MouseEvent类继承自UIEvent类),所有也可以把它看作是鼠标事件,因此,对于WheelEvent事件对象来说,其中也保存着大量与MouseEvent事件同样的属性,例如,四对有关获取坐标的属性、which(值为0)、relatedTarget(为null)等等;还包括辅助键的属性;

mousewheel事件中的event对象,除了保存鼠标事件的所有标准信息外,还包含一个特殊的wheelDelta属性,其指定用户滚动滚轮有多远,当用户向前滚动鼠标滚轮时,该属性值是120的倍数,当向后滚动时,该值是-120的倍数;

EventUtil.addHandler(document, "mousewheel", function(event){
    event = EventUtil.getEvent(event);
    console.log(event);
    console.log(event.wheelDelta);
})

如果要判断用户滚动的方向,只要检测wheelDelta属性的正负号即可;在Opera9.5之前的版本中,wheelDelta的值的正负号是颠倒的;

除了wheelDelta属性外,事件对象还有wheelDeltaX和wheelDeltaY属性,并且wheelDelta和wheelDeltaY的值一直相同;

console.log(event.wheelDelta);
console.log(event.wheelDeltaY);
console.log(event.wheelDeltaX);

IE不支持这两个属性;
Firefox不支持mousewheel事件,但支持一个名为DOMMouseScroll的类似事件,也是在鼠标滚轮滚动时触发,它也被视为鼠标事件,也包含与鼠标事件有关的所有鼠标;而有关鼠标滚轮的信息则保存在detail属性中,该属性与wheelDelta作用相同;当向前滚动鼠标滚轮时,该属性返回-3的位数,反之返回3的位数;

可以把该事件添加到页面中的任何元素,而且该事件会冒泡到window对象;

EventUtil.addHandler(document, "DOMMouseScroll", function(event){
    event = EventUtil.getEvent(event);
    console.log(event);
    console.log(event.detail);  // 向前为-3,向后是3
})

detail属性值与wheelDelta的值的关系是:wheelDelta等于detail乘以-40;

可以跨浏览器取得鼠标滚轮增值(delta),并添加到eventutil.js中,如:

    getWheelDelta: function(event){
        if(event.wheelDelta){
            return event.wheelDelta;
        }else
            return -event.detail * 40;
    }

应用时,需要同时注册mousewheel和DOMMouseScroll事件,如:

function handlerMouseWheel(event){
    event = EventUtil.getEvent(event);
    var delta = EventUtil.getWheelDelta(event);
    console.log(delta);
}
EventUtil.addHandler(document, "mousewheel", handlerMouseWheel);
EventUtil.addHandler(document, "DOMMouseScroll", handlerMouseWheel);

另外,DOMMouseEvent事件对象中还有一个axis属性,其返回一个long型常量值,表明鼠标滚轮滚动的方向,当返回常量HORIZONTAL_AXIS,值为1时,表示由鼠标滚轮的横向滚动触发的,当返回VERTICAL_AXIS,值为2时,表示是由鼠标滚轮的纵向滚动触发的;

wheel事件:
DOM3事件定义了一个名为wheel事件,用于替代mousewheel和DOMMouseScroll事件;事件对象中保存着deltaX、deltaY及deltaZ属性:用来获取三个不同的鼠标滚轴;大多数鼠标滚轮是一维或二维的,所以并不能使用deltaZ属性;

EventUtil.addHandler(document, "wheel", function(event){
    event = EventUtil.getEvent(event);
    console.log(event);  // WheelEvent
    console.log(event.wheelDelta);  // -120
    console.log(event.wheelDeltaY);  // -120
    console.log(event.deltaX);  // -0
    console.log(event.deltaY);  // chrome返回100,Firefox返回63
    console.log(event.deltaZ);  // 0
});

这些值必须乘以-1.2,才和mousewheel事件的wheelDelta值和正负号相匹配;

wheel事件对象还有一个deltaMode属性,只读,其返回long常量值,表示各delta*的值的单位,其值及所表示的单位如下:

常量值描述

  • DOM_DELTA_PIXEL0x00滚动量单位为像素
  • DOM_DELTA_LINE0x01滚动量单位为行
  • DOM_DELTA_PAGE0x02滚动量单位为页
console.log(event.deltaMode);

示例:在Enclose.js文件中定义enclose()函数,可以把一个图片装载到一个容器中,并且能移动这个容器,也能改变容器的大小,如:

function enclose(content, framewidth, frameheight, contentX, contentY){
    // 这些参数不仅仅是初始值,它们保存当前状态,能被mousewheel处理程序使用和修改
    framewidth = Math.max(framewidth, 50);
    frameheight = Math.max(frameheight, 50);
    contentX = Math.min(contentX, 0) || 0;
    contentY = Math.min(contentY, 0) || 0;
    // 创建frame元素,且设置CSS类和样式
    var frame = document.createElement("div");
    frame.className = "enclose";
    frame.style.width = framewidth + "px";
    frame.style.height = frameheight + "px";
    frame.style.overflow = "hidden"; // 没有滚动条,不能溢出
    frame.style.boxSizing = "border-box"; // 能简化调整frame大小的计算
    content.parentNode.insertBefore(frame, content);
    frame.appendChild(content);
    // 确定元素相对于frame的位置
    content.style.position = "relative";
    content.style.left = contentX + "px";
    content.style.top = contentY + "px";
    var isFirefox = (navigator.userAgent.indexOf('Gecko') != -1);
    // 注册mousewheel事件处理程序
    frame.onwheel = wheelHandler;
    frame.onmousewheel = wheelHandler;
    if(isFirefox)
        frame.addEventListener("DOMMouseScroll", wheelHandler, false);
    function wheelHandler(event){
        var e = event || window.event;
        var deltaX = e.deltaX / 3.33 || // wheel事件
                   e.wheelDeltaX / 4 || // mousewheel事件
                                   0    // 属性未定义
        var deltaY = e.deltaY / 3.33 || 
                   e.wheelDeltaY / 4 ||
    (e.wheelDeltaY === undefined &&     // 如果没有2D属性
                   e.wheelDelta / 4) || // 就使用1D的滚轮属性
                     e.detail * -1.2 || // DOMMouseScroll事件
                                   0;   // 属性未定义
        if(isFirefox && e.type !== "DOMMouseScroll")
            frame.removeEventListener("DOMMouseScroll", wheelHandler, false);
        // 获取内容元素的当前尺寸
        var contentbox = content.getBoundingClientRect();
        var contentwidth = contentbox.right - contentbox.left;
        var contentheight = contentbox.bottom - contentbox.top;
        // 如果按下Alt键,就可以调整frame大小
        if(e.altKey){
            if(deltaX){
                framewidth -= deltaX; // 新宽度,但不能比内容大
                framewidth = Math.min(framewidth, contentwidth);
                framewidth = Math.max(framewidth, 50); // 不能小于50
                frame.style.width = framewidth + "px";
            }
            if(deltaY){
                frameheight -= deltaY; 
                frameheight = Math.min(frameheight, contentheight);
                frameheight = Math.max(frameheight - deltaY, 50);
                frame.style.height = frameheight + "px";
            }
        }else{ // 没有按Alt键,就可以平移frame中的内容
            if(deltaX){
                var minoffset = Math.min(framewidth - contentwidth, 0);
                // 把deltaX添加到contentX中,但不能小于minoffset
                contentX = Math.max(contentX + deltaX, minoffset);
                contentX = Math.min(contentX, 0);
                content.style.left = contentX + "px";
            }
            if(deltaY){
                var minoffset = Math.min(frameheight - contentheight, 0);
                contentY = Math.max(contentY + deltaY, minoffset);
                contentY = Math.min(contentY, 0);
                content.style.top = contentY + "px";
            }
        }
        if(e.preventDefault)
            e.preventDefault();
        if(e.stopPropagation)
            e.stopPropagation();
        e.cancelBubble = true;
        e.returnValue = false;
        return false;
    }
}

应用:

<style>
div.enclose{border: 10px solid; margin:10px}
</style>
<img id="content" src="images/1.jpg" />
<script>
window.onload = function(){
    enclose(document.getElementById("content"),400,200,-200,-300);
}
</script>

不要混淆wheel事件和scroll事件:wheel事件的默认动作取决于浏览器实现,因此wheel事件不一定会触发scroll事件;即便滚轮事件引发了文档内容的滚动行为,也不表示wheel事件中的delta*值恰好反映文档内容的滚动方向;因此,不要依赖delta*属性获知文档内容的滚动方向,可在文档内容滚动事件中监视target的scrollLeft和scrollTop的变化以推断滚动方向;

VG 动画有很多种实现方法,也有很大SVG动画库,现在我们就来介绍 svg动画实现方法都有哪些?

一、SVG 的 animation

SVG animation 有五大元素,他们控制着各种不同类型的动画,分别为:

  • set
  • animate
  • animateColor
  • animateTransform
  • animateMotion

1.1、set

set 为动画元素设置延迟,此元素是SVG中最简单的动画元素,但是他并没有动画效果。

使用语法:

<set attributeName="" attributeType="" to="" begin="" />
  • attributeName :是要改变的元素属性名称。
  • attributeType :是表明attributeName属性值的列表,支持三个固定参数 CSS/XML/auto,如x,y以及transform属于XML,opacity属于CSS。auto是浏览器自动判别的意思,也是默认值,如果你不知道该选哪个就填auto,浏览器自己判别。
  • to :动画结束的属性值。
  • begin :动画延迟时间。

eg:绘制一个半径为200的圆,4秒之后,半径变为50。

<svg width="320" height="320">
 <circle cx="0" cy="0" r="200" style="stroke: none; fill: #0000ff;">
  <set attributeName="r" attributeType="XML" to="50" begin="4s" />
 </circle>
</svg>

1.2、animate

是基础的动画元素,实现单属性的过渡效果。

使用语法:

<animate 
 attributeName="r" 
 from="200" to="50" 
 begin="4s" dur="2s" 
 repeatCount="2"
></animate>
  • from :过渡效果的属性开始值。
  • to:过渡效果的属性结束值。
  • begin:动画开始时间。
  • dur:动画过渡时间,控制动画速度。
  • repeatCount:动画重复次数。

eg:绘制一个半径为200的圆,4秒之后半径在2秒内从200逐渐变为50。

<circle cx="0" cy="0" r="200" style="stroke: none; fill: #0000ff;">
 <animate attributeName="r" from="200" to="50" 
  begin="4s" dur="2s" repeatCount="2"></animate>
</circle>

1.3、animateColor

控制颜色动画,animate也可以实现这个效果,所以该属性目前已被废弃。

1.4、animateTransform

实现transform变换动画效果,与css3的transform变换类似。实现平移、旋转、缩放等效果。

使用语法:

<animateTransform attributeName="transform"  type="scale" 
 from="1.5" to="0" 
 begin="2s"  dur="3s" 
 repeatCount="indefinite"></animateTransform>
  • repeatCount:重复次数,设置为 indefinite 表示无限循环,一直执行。
  • type:添加 transform 变换类型。
  • eg:绘制一个半径为200的圆,4秒之后开始缩放,在2秒内从1.5缩小到0倍。
<svg width="320" height="320">
 <circle cx="0" cy="0" r="200" style="stroke: none; fill: #0000ff;">
  <animateTransform attributeName="transform" begin="4s"  
   dur="2s" type="scale" from="1.5" to="0" 
   repeatCount="indefinite"></animateTransform>
 </circle>
</svg>

1.5、animateMotion

可以定义动画路径,让SVG各个图形,沿着指定路径运动。

使用语法:

<animateMotion 
 path="M 0 0 L 320 320" 
begin="4s" dur="2s"></animateMotion>
  • path:定义路径,使用语法与《HTML5(八)——SVG 之 path 详解》path的d属性一致。
  • begin:延迟时间。
  • dur:动画执行时间。

eg:绘制一个半径为10的圆,延迟4秒从左上角运动的右下角。

<svg width="320" height="320">
 <circle cx="0" cy="0" r="10" style="stroke: none; fill: #0000ff;">
  <animateMotion 
   path="M 0 0 L 320 320" 
   begin="4s" dur="2s"
   ></animateMotion>
 </circle>
</svg>

实际制作动画的时候,动画太单一不酷,需要同时改变多个属性时,上边的四种元素可以互相组合,同类型的动画也能组合。以上这些元素虽然能够实现动画,但是无法动态地添加事件,所以接下来我们就看看 js 如何制作动画。

二、JavaScript 控制

上篇文章我们介绍js可以操作path,同样也可以操作SVG的内置形状元素,还可以给任意元素添加事件。

给SVG元素添加事件方法与普通元素一样,可以只用on+事件名 或者addEventListener添加。

eg:使用SVG绘制地一条线,点击线条地时候改变 x1 ,实现旋转效果。

<svg width="800" height="800" id="svg">
    <line id="line" x1="100" y1="100" 
    x2="400" y2="300" 
    stroke="black" stroke-width="5"></line>  
  </svg>
<script>
 window.onload = function(){
  var line = document.getElementById("line")
  line.onclick = function(){
   let start = parseInt(line.getAttribute("x1")),
       end=400,dis = start-end
   requestAnimationFrame(next)
   let count = 0;
   function next(){
    count++
    let a = count/200,cur = Math.abs(start+ dis*a)
    line.setAttribute('x1',cur)
    if(count<200)requestAnimationFrame(next)
   }
  }
 }
</script>

js制作的SVG动画,主要利用 requestAnimationFrame 来实现一帧一帧的改变。

我们上述制作的 SVG 图形、动画等,运行在低版本IE中,发现SVG只有IE9以上才支持,低版本的并不能支持,为了兼容低版本浏览器,可以使用 VML ,VML需要添加额外东西,每个元素需要添加 v:元素,样式中还需要添加 behavier ,经常用于绘制地图。由于使用太麻烦,所以我们借助 Raphael.js 库。

三、Raphaël.js (拉斐尔)

Raphael.js是通过SVG/VML+js实现跨浏览器的矢量图形,在IE浏览器中使用VML,非IE浏览器使用SVG,类似于jquery,本质还是一个javascript库,使用简单,容易上手。

使用之前需要先引入Raphael.js库文件。cdn的地址为:https://cdn.bootcdn.net/ajax/libs/raphael/2.3.0/raphael.js

3.1、创建画布

Rapheal有两种创建画布的方式:

第一种:浏览器窗口上创建画布

创建语法:

var paper = Raphael(x,y,width,height)

x,y是画布左上角的坐标,此时画布的位置是绝对定位,有可能会与其他html元素重叠。width、height是画布的宽高。

第二种:在一个元素中创建画布

创建语法:

var paper = Raphael(element, width, height);

element是元素节点本身或ID width、height是画布的宽度和高度。

3.2、绘制图形

画布创建好之后,该对象自带SVG内置图形有矩形、圆形、椭圆形。他们的方法分别为:

paper.circle(cx, cy, r); // (cx , cy)圆心坐标 r 半径
paper.rect(x, y, width, height, r); // (x,y)左上角坐标 width宽度 height高度 r圆角半径(可选)
paper. ellipse(cx, cy, rx, ry); // (cx , cy)圆心坐标 rx水平半径 ry垂直半径

eg:在div中绘制一个圆形,一个椭圆、一个矩形。

<div id="box"></div>
<script>
 var paper = Raphael("box",300,300)
 paper.circle(150,150,150)
 paper.rect(0,0,300,300)
 paper.ellipse(150,150,100,150)
</script>

运行结果如下:

除了简单图形之外,还可以绘制复杂图形,如三角形、心型,这时就使用path方法。

使用语法:paper.path(pathString)

pathString是由一个或多个命令组成,每个命令以字母开始,多个参数是由逗号分隔。

eg:绘制一个三角形。

let sj = paper.path("M 0,0 L100,100 L100,0 'Z'")

还可以绘制文字,如果需要换行,使用 \n 。

文字语法:paper.text(x,y,text)

(x,y)是文字坐标,text是要绘制的文字。

3.3、设置属性

图形绘制之后,我们通常会添加stroke、fill、stroke-width等让图形更美观,Raphael使用attr给图形设置属性。

使用语法:circle.attr({"属性名","属性值","属性名","属性值",...})

如果只有属性名没有属性值,则是获取属性,如果有属性值,则是设置属性。

注意:如果只设置一个属性时,可以省略‘{}’。如:rect.attr('fill','pink')

eg:给上边的矩形添加边框和背景色。

<div id="box"></div>
<script>
 var paper = Raphael("box",300,300)
 let rect = paper.rect(100,100,150,200)
 rect.attr({'fill':'red','stroke':'blue','stroke-width':'10'})
</script>

3.4、添加事件

RaphaelJS一般具有以下事件:
click、dblclick、drag、hide、hover、mousedown、mouseout、mouseup、mouseover等以及对应的解除事件,只要在前面加上“un”就可以了(unclick、undblclick)。

使用语法:

obj.click(function(){
 //需要操作的内容
})

3.5、添加动画

animate为指定图形添加动画并执行。

使用语法:

obj.animate({
 "属性名1":属性值1,
 "属性名2":属性值2,
  ...
},time,type)

属性名和属性值就根据你想要的动画类型加就ok。

time:动画所需时间。

type:指动画缓动类型。常用值有:

  • linear - 线性渐变
  • ease-in | easeIn | < - 由慢到快
  • ease-out | easeOut | > - 由快到慢
  • ease-in-out | easeInOut | <> - 由慢到快再到慢
  • back-in | backIn - 开始时回弹
  • back-out | backOut - 结束时回弹
  • elastic - 橡皮筋
  • bounce - 弹跳

eg:点击矩形,矩形缓缓变大。

<div id="box"></div>
<script>
 var paper = Raphael("box",800,500)
 let rect = paper.rect(100,100,150,100)
 rect.attr({'fill':'red','stroke':'blue','stroke-width':'10'})
 rect.attr('fill','pink')
 rect.click(function(){
  rect.animate({
   "width":300,
   "height":300
  },1000,"bounce")
 })
</script>

复制上边的代码,分别在各个浏览器和低版本IE浏览器运行,发现都可以正常运行。SVG的动画库挺多了,我们介绍了拉斐尔,有兴趣的小伙伴可以自行找找其他库。