目录
2、接收 XDM 消息会触发一个事件——message 事件
(2)、dataTransfer 对象的 dropEffect 属性
(3)、dataTransfer 对象的 effectAllowed 属性
(2)、dataTransfer 对象兼容 Firefox 和 IE
前言
本文会用到 EventUtil 事件处理程序,详情请戳:js 跨浏览器事件处理程序脚本_weixin79893765432...的博客-CSDN博客
一、跨文档消息传递
跨文档消息传递是 HTML5 中新增的功能,简称为 XDM,Web Messaging 指的也是 XDM。
XDM 方案可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递;
- 多窗口之间消息传递;
- 页面与嵌套的iframe消息传递;
- 上面三个场景的跨域数据传递。
1、XDM 的核心方法——postMessage() 方法
postMessage(data, origin) 方法接受两个参数:
- data:一条消息。有的浏览器允许传入任何数据,有的浏览器仅支持传入字符串,所以传参时最好先用 JSON.stringify() 方法,把数据转为字符串。
- origin:一个表示消息接收方所在域的字符串。如果是“*”表示可以传递给任意窗口(不安全不推荐);如果要指定和当前窗口同源的话可以设置为"/"。
发送方通过 postMessage() 方法发送 XDM 消息:
var data = {
name: 'Mali',
age: '18'
};
// 发送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), "http://127.0.0.1:80/");
2、接收 XDM 消息会触发一个事件——message 事件
接收方接收到 XDM 消息时会触发 window 对象的 message 事件,该事件是以异步的形式触发的,因此会 延迟 一段时间。
触发 message 事件后,传递给 onmessage 处理程序的事件对象包含以下三个重要信息:
- data:消息内容。
- origin:发送消息的文档(发送者)所在域。
- source:发送消息的文档(发送者)的 window 对象的代理。这个代理用于:在发送消息的窗口中调用 postMassage() 方法。如果发送消息的窗口来自同一个域,那这个对象就是 window。只通过这个代理调用 postMassage() 方法即可。
接收到消息后,验证发送窗口的来源是很重要的,在 onMessage 处理程序中检测消息的来源可以确保传入的消息来自已知页面,这样才更安全可靠。
检测模式如下:
<script src="./methods/EventUtil.js"></script>
<script>
EventUtil.addHandler(window, "message", function(){
var event = EventUtil.getEvent(event);
// 确保发送消息的域是已知域
if(event.origin == "http://test.com"){
// 处理接受到的数据
processMessage(event.data);
// (可选)向来源窗口发送回执
event.source.postMessage("Received", "http://p2p.test.com");
}
});
</script>
3、案例
tip:该测试案例,需要开启本地服务器(Wampserver)。
// test.html 中
<iframe name="richedit" id="myframe" style="width: 300px;height: 100px;" src="http://127.0.0.1:80/iframe/myIframe.html"></iframe>
<script>
var iframe = document.getElementById("myframe");
iframe.onload = function() {
var data = {
name: 'Mali',
age: '18'
};
// 发送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), "http://127.0.0.1:80/");
};
</script>
// myIframe.html 中
<p>hello</p>
<div id="box"></div>
<script src="./methods/EventUtil.js"></script>
<script>
EventUtil.addHandler(window, "message", function(){
var event = EventUtil.getEvent(event);
// 确保发送消息的域是已知域
if(event.origin == "http://127.0.0.1:80/" || event.origin == "http://localhost"){
// 处理接受到的数据
processMessage(event.data);
// (可选)向来源窗口发送回执
event.source.postMessage("Received", "http://127.0.0.1:80/iframe/test.html");
}
});
function processMessage(data){
data = JSON.parse(data);
if(data){
var box = document.getElementById("box");
box.innerHTML = `我是${data.name},今年${data.age}岁了`;
}
}
</script>
效果:
二、原生拖放
拖放就是抓取对象后拖到另一个位置。
1、拖动性——draggable 属性
在 HTML5 中,默认情况下,文本只有在被选中的情况下才能拖动,而图像和链接在任何时候都可以拖动。也就是说,不用编写代码,用户就可以拖动它们。这是为什么呢?
原来,HTML5 为所有 HTML 元素定义了一个 draggable 属性:接收一个布尔值,表示元素是否可以拖动。
- 对于图像和链接:draggable 属性的默认值为 true。
- 对于文本:当文本被选中时,draggable 属性的值就变为 true。
想让其他元素可以拖动,或者让图像、链接不能拖动,都可以通过设置 draggable 属性的值来实现。
<!-- 不允许该图像拖动 -->
<img src="./img/333.png" draggable="false" alt="atm给你小心心">
<!-- 允许该div拖动 -->
<div draggable="true">666666</div>
2、自定义放置目标
拖动元素时,经过某些无效放置目标时,会显示不能放置标志。
不过任何元素都可以自定义为有效的放置目标,方法是——禁止 dragover 事件的默认行为。
例如:假设有一个 id 为 “droptarget” 的 <div> 元素,可以用如下代码将它变成一个放置目标:
var droptarget = document.getElementById("droptarget");
EventUtil.addHandler(droptarget, "dragover", function(event){
EventUtil.preventDefault(event);
});
3、拖放事件
拖放事件,可分为 “拖动元素上触发的事件” 和 “可放置目标上触发的事件”。
(1)、在拖动元素上触发事件
- ondragstart - 当用户开始拖动元素时触发。
- ondrag - 当元素正在拖动时触发。
- ondragend - 当用户完成元素拖动后触发。
(2)、在可放置目标上触发的事件
- dragenter - 当拖动元素进入可放置目标时触发。
- dragover - 当拖动元素在可放置目标内移动时触发。
- drop - 当拖动元素最终被放置在了可放置目标时触发。
- dragleave - 当拖动元素离开可放置目标时触发。
(3)、探索不同情况下,事件触发
- 鼠标在拖动元素上按下,不触发拖放系列事件。
- 鼠标在拖动元素上按下,并开始移动该元素(未进入可放置目标),先后触发:dragstart、drag 事件。
- 鼠标拖动元素一直移动(未进入可放置目标),一直触发:drag 事件。
- 释放鼠标(未进入可放置目标),触发:dragend 事件。
- 拖动元素进入可放置目标,先后触发:dragenter、dragover 事件。
- 在放置目标内,拖动元素一直移动,一直先后触发:dragover、drag 事件。
- 将元素放置在放置目标内,依次触发:dragover、drop、dragend 事件。
- 拖动元素,从放置目标移出,依次触发:dragleave、drag、dragend 事件。
4、dataTransfer 对象
dataTransfer 对象,是拖放事件对象的属性,所以,它只能在拖放事件处理程序中被访问。
dataTransfer 对象,有 2 个功能:
- 用于从被拖动元素向放置目标传送字符串格式的数据——setData() + getData() 方法。
- 用于确定被拖动的元素和作为放置目标的元素能够接收什么操作——dropEffect 和 effectAllowed 属性。
(1)、dataTransfer 对象的 2 个方法
- setData() 方法:用来保存拖动元素的数据(浏览器自动调用,也可以在 dragstart 事件处理程序中手动保存这些数据)。
接收 2 个参数:一个表示数据类型的字符串 和 一个数据。第一个参数的可选值包括:"URL" 和 "text"。
当第一个参数为 "text" 类型时,第二个参数只能是文本数据,表示“设置和接收文本数据”;
当第一个参数为 "URL" 类型时,第二个参数只能是 URL 地址,表示“设置和接收 URL”。 - getData() 方法:获取由 setData() 方法保存的值(只能在 drop 事件处理程序中读取 dataTransfer 对象中的数据)。
接收 1 个参数:一个表示数据类型的字符串,可选的值包括:"URL" 和 "text"。
当参数为 "text" 类型时,表示“读取文本”;
当参数为 "URL" 类型时,表示“读取 URL”。
(2)、dataTransfer 对象的 dropEffect 属性
dropEffect 属性:用于获知被拖动的元素能够执行哪种放置行为。该属性有以下可选值:
- "none":不能把拖动的元素放在这里。这是除文本框之外的所有元素的默认值。
- "move":应该把拖动的元素移动到放置目标。
- "copy":应该把拖动元素复制到放置目标。
- "link":表示放置目标会打开拖动的元素(但拖动的元素必须是一个链接,有 URL)。
必须在可放置目标的 dragenter 事件处理程序中针对放置目标来设置 dropEffect 属性。
另外,dropEffect 属性只有和 effectAllowed 属性一起使用时才有效。
(3)、dataTransfer 对象的 effectAllowed 属性
effectAllowed 属性:用来表示允许拖动元素的哪种 dropEffect。该属性的可选值如下:
- "uninitialized":没有给被拖动元素设置任何放置行为。
- "none":被拖动的元素不能有任何行为。
- "copy":只允许值为 "copy" 的 dropEffect。
- "link":只允许值为 "link" 的 dropEffect。
- "move":只允许值为 "move" 的 dropEffect。
- "copyLink":允许值为 "copy" 和 "link" 的 dropEffect。
- "copyMove":允许值为 "copy" 和 "move" 的 dropEffect。
- "linkMove":允许值为 "link" 和 "move" 的 dropEffect。
- "all":允许任意的 dropEffect。
必须在被拖动元素的 dragstart 事件处理程序中设置 effectAllowed 属性。
(4)、dataTransfer 对象的其他成员
根据 HTML5 的规定,dataTransfer 对象还包括以下方法和属性:
- addElement(element):为拖动操作添加一个元素。该元素只影响数据,不改变页面外观。
- clearData(format):清除以特定格式保存的数据。
- setDragImage(element, x, y):指定一幅图像,当拖动发生时,显示在光标下方。接收 3 个参数:要显示的 HTML 元素、光标在该图像上的 x 坐标 和 光标在该图像上的 y 坐标。
- types:当前保存的数据类型。这是一个类数组的集合,以 "text" 这样的字符串形式保存着数据类型。
5、拖放兼容性
(1)、drop 事件兼容 Firefox
在 Firefox 3.5+ 中,drop 事件的默认行为是打开被放置目标上的 URL。这就导致了两种结果:
如果把图片放置到目标上,页面就会转向图像文件;
如果把文本放置在目标上,则会导致无效 URL 错误。
为了让 Firefox 支持正常的拖放,需要取消 drop 事件的默认行为,阻止它打开 URL。代码实现如下:
EventUtil.addHandler(droptarget, "drop", function(){
EventUtil.preventDefault(event);
})
(2)、dataTransfer 对象兼容 Firefox 和 IE
Firefox 5 之前的版本不能正确的将 "text" 和 "url" 映射为 "text/plain" 和 "text/uri-list"。但是却能够把 "Text" 映射为 "text/plain"。
为了更好地在跨浏览器的情况下从 dataTransfer 对象中取得数据,最好在取得 URL 数据时检测两个值,在取得文本数据时使用 "Text"。
EventUtil.addHandler(placeTarget, "drop", function(event){
var dataTransfer = event.dataTransfer;
// 读取 URL
var url = dataTransfer.getData("url") || dataTransfer.getData("text/uri-list");
// 读取文本
var text = dataTransfer.getData("Text");
console.log([url, text]);
EventUtil.preventDefault(event);
})
注意:IE 只定义了 "URL" 和 "text" 两种有效的类型,HTML5 则允许指定各种 MIME 类型,也兼容 "text" 和 "URL" 类型,不过这两种类型分别被映射为 "text/plain" 和 "text/uri-list"。一定要把短数据类型放在前面,因为 IE10 及之前的版本仍然不支持扩展的 MIME 类型名,而它们在遇到无法识别的数据类型时,会抛出错误。
三、媒体元素
HTML5 新增的媒体元素指的是:<audio> 和 <video>,分别是“音频”和“视频”。
1、audio 和 video 元素的属性
属性 | 数据类型 | 说明 |
---|---|---|
autoplay | 布尔值 | 取得或设置 autoplay 标志 |
buffered | 时间范围 | 表示已下载的缓冲的时间范围的对象 |
bufferedBytes | 字节范围 | 表示已下载的缓冲的字节范围的对象 |
bufferingRate | 整数 | 下载过程中每秒平均接收到的位数 |
bufferingThrottled | 布尔值 | 表示浏览器是否对缓冲进行了节流 |
controls | 布尔值 | 取得或设置 ontrols 属性,用于显示或隐藏浏览器内置的控件 |
currentLoop | 整数 | 媒体文件已经循环的次数 |
currentSrc | 字符串 | 当前播放的媒体文件的 URL |
currentTime | 浮点数 | 已经播放的秒数 |
defaultPlaybackRate | 浮点数 | 取得或设置默认的播放速度。默认值为 1.0 秒 |
duration | 浮点数 | 媒体的总播放时间(秒数) |
ended | 布尔值 | 表示媒体文件是否播放完成 |
loop | 布尔值 | 取得或设置媒体文件在播放完成后是否再重头开始播放 |
muted | 布尔值 | 取得或设置媒体文件是否静音 |
poster | 字符串 | 规定视频下载时显示的图像,或者在用户点击播放按钮前显示的图像。 |
networkState | 整数 | 表示当前媒体的网络连接状态:0表示空;1表示正在加载;2表示正在加载元数据; 3表示已经加载了第一帧;4表示加载完成。 |
paused | 布尔值 | 表示播放器是否暂停 |
playbackRate | 浮点数 | 取得或设置当前的播放速度。用户可以改变这个值,让媒体播放速度变快或者变慢, 这与只能由开发人员修改的 defaultPlaybackRate 不同。 |
played | 时间范围 | 到目前为止已经播放的时间范围 |
readyState | 整数 | 表示媒体是否已经就绪(可以播放了)。0表示不可用;1表示可以显示当前帧; 2表示可以开始播放了;3表示媒体可以从头到尾播放了。 |
seekable | 时间范围 | 可以搜索的时间范围 |
seeking | 布尔值 | 表示播放器是否正移动到媒体文件中的新位置 |
src | 字符串 | 媒体文件的来源 |
start | 浮点数 | 取得或设置媒体文件中开始播放的位置,以秒表示 |
totalBytes | 整数 | 当前资源所需的总字节数 |
videoHeight | 整数 | 返回视频(不一定是元素)的高度。只适用于 <video> |
videoWidth | 整数 | 返回视频(不一定是元素)的宽度。只适用于 <video> |
volume | 浮点数 | 取得或设置当前音量,值为 0.0 到 1.0 |
2、audio 和 video 元素的方法
事件 | 触发时机 |
---|---|
abort | 下载中断 |
canplay | 可以播放时;readyState 的值为 2 时 |
canplaythrough | 播放可继续,而且应该不会中断;readyState 的值为 3 时 |
canshowcurrentframe | 当前帧已经下载完成;readyState 的值为 1 时 |
dataunavailable | 因为没有数据而不能播放;readyState 的值为 0 时 |
durationchange | duration 属性的值改变 |
emptied | 网络连接关闭 |
empty | 发生错误,阻止了媒体下载 |
ended | 媒体已经播放到末尾了,播放停止 |
error | 下载期间发生网络错误 |
load | 所有媒体已经加载完成。这个事件可能会被废弃,建议使用 canplaythrough |
loadeddata | 媒体的第一帧已经加载完成 |
loadedmetadata | 媒体的元数据已经加载完成 |
loadstart | 下载已开始 |
pause | 播放已暂停 |
play | 媒体已经接收到指令开始播放 |
playing | 媒体已经实际开始播放 |
progress | 正在下载 |
ratechange | 播放媒体的速度改变 |
seeked | 搜索结束 |
seeking | 正移动到新位置 |
stalled | 浏览器尝试下载,但未接收到数据 |
timeupdate | currentTime 被不合理或意外的方式更新 |
volumechange | volume 属性值或 muted 属性值已改变 |
waiting | 播放暂停,等待下载更多数据 |
3、自定义媒体播放器
使用 <audio> 和 <video> 元素的属性、事件,以及 play() 和 pause() 这两个方法,可以手动控制媒体文件的播放。
举个栗子:自定义播放一个小视频
<body>
<div class="mediaplayer">
<div class="video">
<video id="player" src="./movie/movie.mp4" poster="./img/66.jpeg" width="300" height="200">
视频播放器不可用。
</video>
</div>
<div class="controls">
<input type="button" value="播放" id="video-btn">
<span id="curtime">0</span> / <span id="duration">0</span>
</div>
</div>
</body>
<script type="text/javascript">
var player = document.getElementById("player"),
btn = document.getElementById("video-btn");
curtime = document.getElementById("curtime");
duration = document.getElementById("duration");
// 更新播放时间
window.onload = function () {
duration.innerHTML = player.duration;
};
// 为按钮添加事件处理程序
EventUtil.addHandler(btn, "click", function (e) {
if (player.paused) {
player.play();
btn.value = "暂停";
} else {
player.pause();
btn.value = "播放";
}
});
// 定时更新当前时间
var timer;
clearInterval(timer);
timer = setInterval(function () {
curtime.innerHTML = player.currentTime;
}, 200);
</script>
4、检测编解码器的支持情况
(1)、新的书写格式
并非所有的浏览器都支持 <audio> 和 <video> 元素的所有 “编解码器”。所以,开发者必须提供媒体来源。为此,不用在标签中指定 src 属性了,而是要像下面这样使用一个或多个 <source> 元素。
<!-- 嵌入视频 -->
<video id="myVideo">
<source src="myvideo.mp4" type="video/mp4"></source>
<source src="myvideo.ogv" type="video/ogg"></source>
<source src="myvideo.webm" type="video/webm"></source>
当前浏览器不支持 video。
</video>
<!-- 嵌入音频 -->
<audio id="myAudio">
<source src="song.ogg" type="audio/ogg">
<source src="song.mp3" type="audio/mpeg">
当前浏览器不支持 audio。
</audio>
(2)、如何检测浏览器是否支持某种格式的编解码器呢?
JavaScript 提供了一个方法:canPlayType() 方法。
canPlayType() 方法接收 1 个参数:一个格式或者编码器字符串。可能的返回值如下:
- "probably":当返回该值时,为 true。
- "maybe":当返回该值时,为 true。
- "":当返回该值时,为 false。
如果给 canPlayType() 方法传入一种 MIME 类型,返回的值可能是 "maybe" 或者 ""。
如果给 canPlayType() 方法同时传入 MIME 类型和编解码器,返回的值还可能是 "probably"。
canPlayType() 方法的应用:
var audio = document.getElementById("audio-player");
// 可能是 "maybe"
if(audio.canPlayType("audio/mpeg")){
// 进一步处理
}
// 可能是 "probably"
if(audio.canPlayType("audio/ogg; codecs=\"vorbis\"")){
// 进一步处理
}
下表列出了已知的已得到支持的 音频 格式和解码器:
音频 | 字符串 | 支持的浏览器 |
---|---|---|
AAC | audio/mp4; codecs="mp4a.40.2" | IE9+、Safari4+、iOS版Safari |
MP3 | audio/mpeg | IE9+、Chrome |
Vorbis | audio/ogg; codecs="vorbis" | Firefox3.5+、Chrome、Opera10.5+ |
MAV | audio/wav; codecs="1" | Firefox3.5+、Chrome、Opera10.5+ |
下表列出了已知的已得到支持的 视频 格式和解码器:
视频 | 字符串 | 支持的浏览器 |
---|---|---|
H.264 | video/mp4; codecs="avc1.42E01E, mp4a.40.2" | IE9+、Safari4+、iOS版Safari、Andriod版WebKit |
Theora | video/ogg; codecs="theora" | Firefox3.5+、Chrome、Opera10.5 |
WebM | video/webm; codecs="vp8, vorbis" | Firefox4+、Chrome、Opera10.6+ |
四、历史状态管理
在开发中,用户每次操作不一定会打开一个全新的页面,因此“后退”和“前进”按钮也就失去了作用,导致用户很难在不同状态间切换。要解决这个问题,有两种办法:
- 首选使用 hashchange 事件:可以知道 URL 的参数什么时候发生了变化。
- 其次可以使用“历史状态管理 API”:能够在不加载新页面的情况下改变浏览器的 URL。
1、hashchange 事件
hashchange 事件的作用:当 URL 的参数列表(及 URL 中“#”后面的所有字符串)发生变化时,通知开发人员。
必须要把 hashchange 事件添加给 window 对象。
hashchange 事件的 event 对象新增了两个属性:oldURL 和 newURL。这两属性分别保存着参数列表变化前后的完整 URL。
使用下面的方法可以跨浏览器检测浏览器是否支持 hashchange 事件:
var isSupported = ("onhashchange" in window) && (document.documentMode === undefined || document.documentMode > 7);
hashchange 事件的应用:
var isSupported = ("onhashchange" in window) && (document.documentMode === undefined || document.documentMode > 7);
if(isSupported){
EventUtil.addHandler(window, "hashchange", function(event){
alert(`当前 hash 值${location.hash}`);
});
}
支持 hashchange 事件的浏览器有 IE8+、Firefox3.6+、Safari5+、Chrome 和 Opera10.6+。
支持 oldURL 和 newURL 属性的只有 Firefox6+、Chrome 和 Opera10.6+。因此,最好使用 location 对象来确定当前参数列表。
2、历史状态管理 API
历史状态管理 API 包括两个方法:
- history.pushState() 方法:能够在不加载新页面的情况下改变浏览器的 URL。
- history.replaceState() 方法:更新当前 event.state 的状态。
(1)、history.pushState() 方法
pushState() 方法接收 3 个参数:状态对象、新状态的标题 和(可选的)相对 URL。
- 状态对象:应该尽可能提供初始化页面状态所需的各种信息。
- 新状态的标题:目前还没有完全实现,因此完全可以只传入一个空字符串("")或者一个短标题也可以。
- 相对 URL:可选的参数。如果传入这个参数,浏览器地址栏的地址会更新。
例如:
history.pushState({name:"Nicholas"}, "Nicholas' page", "nicholas.html");
执行 pushState() 方法后,新的状态信息就会被加入历史状态栈,而浏览器地址栏也会变成新的相对 URL。但是,浏览器并不会真的向服务器发送请求,即使状态改变之后查询 location.href,也会返回与地址栏中相同的地址。
因为 pushState() 方法会创建新的历史状态,所以 “后退” 按钮就能够使用了。
返回的浏览器加载的第一个页面时,event.state 的值为 null。
在按下 “后退” 按钮时,就从状态栈中的一个状态跳到另一个状态了,就会触发 window对象的 popstate 事件,popstate 事件对象中有一个 state 属性,存储着当初传递给 pushState() 方法的 “状态对象”。
得到这个状态对象后,必须手动把页面重置为状态对象中状态,因为浏览器不会自动帮你重置。
(2)、history.replaceState() 方法
replaceState() 方法接收 2 个参数:状态对象 和 新状态的标题。(与 pushState() 方法的前两个参数相同)
调用这个方法不会在历史状态栈中创建新状态,只会重写当前状态——按当前地址栏中的地址给服务器发送请求,把新的页面实现出来。
在A页面,调用 pushState() 方法后 URL 地址指向B页面。再通过在 A 页面调用 replaceState() 方法,然后,刷新一下 A 页面,变成了B页面了。
3、hash 和 history 路由对比
- hash 模式的实现原理是通过监听hashChange事件来实现的。
- history 模式是通过调用 history.pushState方法(或者replaceState) 并且 监听popstate事件来实现的。history.pushState会追加历史记录,并更换地址栏地址信息,但是页面不会刷新,需要手动调用地址变化之后的处理函数,并在处理函数内部决定跳转逻辑;监听popstate事件是为了响应浏览器的前进后退功能。
- hash 模式会在地址栏中有 # 号。
- history 模式没有;同时由于 history 模式的实现原理用到 H5 的新特性,所以它对浏览器的兼容性有要求(IE >= 10)。
- hash 模式 SEO 表现差。history 模式 SEO 表现良好。
- 使用 hash 模式时,要求正确配置服务器。而使用 history 模式时无需配置服务器。
【推荐阅读】