JS高程:读书摘要(十四)拖放、音视频

一、 跨文档消息传递

跨文档消息传送(cross-document messaging),有时候简称为XDM,指的是在来自不同域的页面间传递消息。

XDM 的核心是postMessage()方法,向另一个地方(包含在当前页面中的<iframe>元素,或者由当前页面弹出的窗口。)传递数据。

otherWindow.postMessage(message, targetOrigin, [transfer]);

  • otherWindow:其他窗口的一个引用,比如iframecontentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames
  • message : 将要发送到其他 window的数据。它将会被结构化克隆算法序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化。

  • targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制,不建议使用)或者一个URI。如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口(非常重要);

  • transfer[可选]:是一串和message同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

接收到XDM消息时,会触发window对象的message事件。这个事件是以异步形式触发的,因此从发送消息到接收消息(触发接收窗口的message事件)可能要经过一段时间的延迟。触发message事件后,传递给onmessage处理程序的事件对象包含以下三方面的重要信息。

  • data:从其他 window中传递过来的对象。
  • origin:调用 postMessage 时消息发送方窗口的 origin. 这个字符串由 协议、域名、端口号拼接而成。端口号(443https的默认值)
  • source:对发送消息的窗口对象的引用; 可以使用此来在具有不同origin的两个窗口之间建立双向通信。

安全相关:如果您不希望从其他网站接收message,请不要为message事件添加任何事件侦听器。 这是一个完全万无一失的方式来避免安全问题;如果您确实希望从其他网站接收message,请始终使用originsource属性验证发件人的身份。 当您使用postMessage将数据发送到其他窗口时,始终指定精确的目标origin,而不是"*"

//  示例:
// 主窗口域名是<http://example.com:8080> 传递"i am main" 给弹出页
// 弹出页域名是<http://example.org> 传递"i am pop"给主窗口

// 主窗口 JS

var popup = window.open('http://example.org','pop') 
// 参数是(url,新窗口名称) 不是title 而是可以用来作为超链接 <a> 或表单 <form> 元素的目标属性值。字符串中不能含有空白字符。

popup.postMessage("i am main", "http://example.org");
// 发送->  传递消息给弹出页 第二个参数必须精准匹配

// 接收-> 定义事件处理程序处理接收到的消息
function receiveMessage(event){
    // 必须使用判断 以保证安全 网站不被攻击
    if (event.origin !== "http://example.org"){
        return;
    }else{
        // do something
    }
    // event.source 是我们通过window.open打开的弹出页面 popup
    // event.data 是 popup发送给当前页面的消息
}

window.addEventListener("message",receiveMessage,false)
// 弹出页 JS
function popReceiveMessage(event){
    if (event.origin !== "http://example.com:8080"){
        return;
    }else{
        // 向来源窗口发送回执
        event.source.postMessage("i am pop" +event.origin);
    }
}

window.addEventListener("message", popReceiveMessage, false);

二、拖放

拖放事件
  • 发生在被拖放的元素上。
    • dragstart:按下鼠标键并开始移动鼠标时触发。
    • drag:在元素被拖动期间会持续触发该事件
    • dragend:当拖动停止时触发(无论是否将元素放到了有效目标)
  • 发生在放置目标上。
    • dragenter:有元素被拖入时触发。
    • dragover:被拖动元素在放置目标内部移动时触发。
    • dragleave:被拖动元素被拖出放置目标后触发。(进来之后出去)
    • drop:元素被放到了放置目标中时触发。

如果拖动元素经过不允许放置的元素,无论用户如何操作,都不会发生drop 事件。不过,你可以把任何元素变成有效的放置目标,方法是阻止dragenterdragover 事件的默认行为。你就会发现当拖动着元素移动到放置目标上时,光标变成了允许放置的符号。当然,释放鼠标也会触发drop事件。

Firefox 3.5+中,放置事件的默认行为是打开被放到放置目标上的URL(被放入元素是图片则页面转向图像文件,如果是文本,则导致错误,无效的URL),如果想正常处理放置事件,也需要阻止drop事件打开URL的行为

dataTransfer对象

它是事件对象的一个属性,用于从被拖动元素向放置目标传递字符串格式的数据。只能在拖放事件的事件处理程序中访问dataTransfer对象。有两个主要方法:getData()setData()

//设置和接收文本数据
event.dataTransfer.setData("text", "some text");
var text = event.dataTransfer.getData("text");
//设置和接收URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
var url = event.dataTransfer.getData("URL");

IE只定义了"text""URL"两种有效的数据类型,而HTML5则对此加以扩展,允许指定各种MIME类型。考虑到向后兼容,HTML5 也支持"text""URL",但这两种类型会被映射为"text/plain""text/uri-list"

保存在dataTransfer对象中的数据只能在drop事件处理程序中读取。如果在ondrop处理程序中没有读到数据,那就是dataTransfer对象已经被销毁,数据也丢失了。

在拖动文本框中的文本或链接、图像时,浏览器会调用setData()方法以"text""URL"保存,然后,在这些元素被拖放到放置目标时,就可以通过getData()读到这些数据。当然,作为开发人员,你也可以在dragstart事件处理程序中调用setData(),手工保存自己要传输的数据,以便将来使用。如果将数据保存为URL,浏览器会将其当成网页中的链接。换句话说,如果你把它放置到另一个浏览器窗口中,浏览器就会打开该URL

// 兼容Firefox5之前 不能正确映射"text"和"url" 只能把"Text"(T 大写)映射为"text/plain"。 
var dataTransfer = event.dataTransfer;
//读取URL
var url = dataTransfer.getData("url") ||dataTransfer.getData("text/uri-list");
//读取文本
var text = dataTransfer.getData("Text");
dataTransfer 对象的两个属性 dropEffecteffectAllowed
  • dropEffect属性可以知道被拖动的元素能够执行哪种放置行为
    • "none":不能把拖动的元素放在这里。这是除文本框之外所有元素的默认值。
    • "move":应该把拖动的元素移动到放置目标。
    • "copy":应该把拖动的元素复制到放置目标。
    • "link":表示放置目标会打开拖动的元素(但拖动的元素必须是一个链接,有URL)。

使用dropEffect属性,必须在ondragenter事件处理程序中针对放置目标来设置它。在把元素拖动到放置目标上时,以上每一个值都会导致光标显示为不同的符号。然而,浏览器只能帮你改变光标的样式,而其他的都要靠你自己来实现。

dropEffect 属性只有搭配effectAllowed 属性才有用。effectAllowed 属性表示允许拖动元素的哪种dropEffect,必须在ondragstart 事件处理程序中设置effectAllowed 属性,定义此次拖动可以允许哪些操作,而当ondragenter触发时,针对放置目标来设置dropEffect属性来进行该操作。

  • effectAllowed 属性表示允许拖动元素的哪种dropEffect
    • "uninitialized":没有给被拖动的元素设置任何放置行为。
    • "none":被拖动的元素不能有任何行为。s
    • "copy":只允许值为"copy"dropEffect
    • "link":只允许值为"link"dropEffect
    • "move":只允许值为"move"dropEffect
    • "copyLink":允许值为"copy""link"dropEffect
    • "copyMove":允许值为"copy""move"dropEffect
    • "linkMove":允许值为"link""move"dropEffect
    • "all":允许任意dropEffect
draggable 可拖动

文本在被选中的情况下才能拖动,而图像和链接在任何时候都可以拖动。HTML5 为所有HTML元素规定了一个draggable 属性,表示元素是否可以拖动。图像和链接的draggable 属性自动被设置成了true,而其他元素这个属性的默认值都是false。要想让其他元素可拖动,或者让图像或链接不能拖动,都可以设置这个属性。

其他成员

dataTransfer 对象还应该包含下列方法和属性

  • addElement(element):为拖动操作添加一个元素。添加这个元素只影响数据(即增加作为拖动源而响应回调的对象),不会影响拖动操作时页面元素的外观。部分浏览器支持
  • clearData(format):清除以特定格式保存的数据。实现这个方法的浏览器有IEFireforx 3.5+ChromeSafari 4+
  • setDragImage(element, x, y):指定一幅图像,当拖动发生时,显示在光标下方。这个方法接收的三个参数分别是要显示的HTML元素和光标在图像中的x、y坐标。其中,HTML元素可以是一幅图像,也可以是其他元素。是图像则显示图像,是其他元素则显示渲染后的元素。实现这个方法的浏览器有Firefox 3.5+Safari 4+Chrome
  • types:当前保存的数据类型。这是一个类似数组的集合,以"text"这样的字符串形式保存着数据类型。实现这个属性的浏览器有IE10+Firefox 3.5+Chrome

三、媒体元素

<audio><video>

位于开始和结束标签之间的任何内容都将作为后备内容,在浏览器不支持这两个媒体元素的情况下显示。因为并非所有浏览器都支持所有媒体格式,所以可以指定多个不同的媒体来源。为此,不用在标签中指定src属性,而是要像下面这样使用一或多个<source>元素。

<!-- 嵌入视频 -->
<video id="myVideo">
    <source src="conference.webm" type="video/webm; codecs='vp8, vorbis'">
    <source src="conference.ogv" type="video/ogg; codecs='theora, vorbis'">
    <source src="conference.mpg">
Video player not available.
</video>

通过这些属性可以知道媒体的当前状态。:

  • src:指向要加载的媒体文件。
  • widthheight:指定视频播放器的大小
  • poster:属性指定图像的URI可以在加载视频内容期间显示一幅图像。(封面)
  • controls:布尔值,浏览器是否显示UI 控件,以便用户直接操作媒体
  • autoplay : 布尔值 取得或设置autoplay标志
  • buffered :时间范围 表示已下载的缓冲的时间范围的对象
  • bufferedBytes : 字节范围 表示已下载的缓冲的字节范围的对象
  • bufferingRate: 整数 下载过程中每秒钟平均接收到的位数
  • bufferingThrottled : 布尔值 表示浏览器是否对缓冲进行了节流
  • currentLoop: 整数 媒体文件已经循环的次数
  • currentSrc :字符串 当前播放的媒体文件的URL
  • currentTime : 浮点数 已经播放的秒数
  • defaultPlaybackRate :浮点数 取得或设置默认的播放速度。默认值为1.0秒
  • duration :浮点数 媒体的总播放时间(秒数)
  • ended : 布尔值 表示媒体文件是否播放完成
  • loop: 布尔值 取得或设置媒体文件在播放完成后是否再从头开始播放
  • muted: 布尔值 取得或设置媒体文件是否静音
  • networkState: 整数 表示当前媒体的网络连接状态:0表示空,1表示正在加载,2表示正在加载元数据,3表示已经加载了第一帧,4表示加载完成
  • paused: 布尔值 表示播放器是否暂停
  • playbackRate : 浮点数 取得或设置当前的播放速度。用户可以改变这个值,让媒体播放速度变快或变慢,这与只能由开发人员修改的defaultPlaybackRate不同
  • played: 时间范围 到目前为止已经播放的时间范围
  • readyState :整数 表示媒体是否已经就绪(可以播放了)。0表示数据不可用,1表示可以显示当前帧,2表示可以开始播放,3表示媒体可以从头到尾播放
  • seekable : 时间范围 可以搜索的时间范围
  • seeking: 布尔值 表示播放器是否正移动到媒体文件中的新位置
  • start:浮点数 取得或设置媒体文件中开始播放的位置,以秒表示
  • totalBytes: 整数 当前资源所需的总字节数
  • videoHeight: 整数 返回视频(不一定是元素)的高度。只适用于<video>
  • videoWidth: 整数 返回视频(不一定是元素)的宽度。只适用于<video>
  • volume:浮点数 取得或设置当前音量,值为0.0到1.0

事件有:

  • 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 浏览器尝试下载,但未接收到数据
  • timeupdatecurrentTime被以不合理或意外的方式更新
  • volumechangevolume属性值或muted属性值已改变
  • waiting: 播放暂停,等待下载更多数据
//取得元素的引用
var player = document.getElementById("player"),
    btn = document.getElementById("video-btn"),
    curtime = document.getElementById("curtime"),
    duration = document.getElementById("duration");
//更新播放时间
duration.innerHTML = player.duration;
//为按钮添加事件处理程序
EventUtil.addHandler(btn, "click", function(event){
    if (player.paused){
        player.play(); // play方法 播放
        btn.value = "Pause";
    } else {
        player.pause(); // pause方法 暂停
        btn.value = "Play";
    }
});
//定时更新当前时间 以小于1000ms触发
setInterval(function(){
    curtime.innerHTML = player.currentTime;
}, 250);
检测编解码器的支持情况

canPlayType()方法,该方法接收一种格式/编解码器字符串,返回probably""maybe"" ",如果给canPlayType()传入了一种MIME类型(期望接受的是编码格式字符串),则返回值很可能是"maybe"或空字符串。这是因为媒体文件本身只不过是音频或视频的一个容器,而真正决定文件能否播放的还是编码的格式。在同时传入MIME类型和编解码器的情况下,可能性就会增加,返回的字符串会变成"probably"

var audio = document.getElementById("audio-player");
//很可能"maybe"
if (audio.canPlayType("audio/mpeg")){ // MIME
    //进一步处理
}
//可能是"probably"
if (audio.canPlayType("audio/ogg; codecs=\"vorbis\"")){ // MIME & 编码格式
    //进一步处理
}

常用音频的音频格式和编解码器

  • AACaudio/mp4; codecs="mp4a.40.2" ,浏览器支持的有 IE9+Safari 4+iOS版Safari
  • MP3audio/mpeg,浏览器支持的有 IE9+Chrome
  • Vorbisaudio/ogg; codecs="vorbis",浏览器支持的有Firefox 3.5+ChromeOpera 10.5+
  • WAVaudio/wav; codecs="1",浏览器支持的有Firefox 3.5+Opera 10.5+Chrome

常用视频的视频格式和编解码器

  • H.264video/mp4; codecs="avc1.42E01E, mp4a.40.2" ,浏览器支持的有IE9+Safari 4+iOS版SafariAndroid版WebKit
  • Theoravideo/ogg; codecs="theora",浏览器支持的有 Firefox 3.5+Opera 10.5Chrome
  • WebMvideo/webm; codecs="vp8, vorbis",浏览器支持的有Firefox 4+Opera 10.6Chrome
Audio类型

<audio>元素还有一个原生的JavaScript 构造函数Audio,可以在任何时候播放音频。从同为DOM元素的角度看,AudioImage很相似,但Audio不用像Image那样必须插入到文档中。只要创建一个新实例,并传入音频源文件即可。

var audio = new Audio("sound.mp3");
EventUtil.addHandler(audio, "canplaythrough", function(event){
    audio.play();
})
// 创建实例即可开始下载指定的文件。下载完成后,调用play()就可以播放音频。

iOS 中,调用play()时会弹出一个对话框,得到用户的许可后才能播放声音。如果想在一段音频播放后再播放另一段音频,必须在onfinish事件处理程序中调用play()方法。

四、历史状态管理

history.pushState()

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

URL不必须为绝对路径。如果新URL是相对路径,那么它将被作为相对于当前URL处理。新URL必须与当前URL同源,否则 pushState()会抛出一个异常。该参数是可选的,缺省为当前URL

// 假设在 http://mozilla.org/foo.html 中执行了以下 JS 代码:
var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "bar.html");
// 第二个参数没有浏览器实现,可以传空字符串 或者一个短标题(安全的选择)。
// 这将使浏览器地址栏显示为 http://mozilla.org/bar.html
// 但并不会导致浏览器加载 bar.html ,甚至不会检查bar.html 是否存在。

执行pushState()方法后,新的状态信息就会被加入历史状态栈,而浏览器地址栏也会变成新的相对URL。状态改变之后查询location.href也会返回与地址栏中相同的地址,但是浏览器并不会真的向服务器发送请求。

假设现在用户又访问了 http://google.com,然后点击了返回按钮。此时,地址栏将显示 http://mozilla.org/bar.html,同时页面会触发 popstate 事件,事件对象state中包含了 stateObj 的一份拷贝。页面本身与 foo.html 一样,尽管其在 popstate 事件中可能会修改自身的内容。

如果我们再次点击返回按钮,页面URL会变为http://mozilla.org/foo.html,文档对象document会触发另外一个 popstate 事件,这一次的事件对象state objectnull。 这里也一样,返回并不改变文档的内容,尽管文档在接收 popstate 事件时可能会改变自己的内容,其内容仍与之前的展现一致

EventUtil.addHandler(window, "popstate", function(event){
    var state = event.state;
    if (state){ //第一个页面加载时state 为空
        // 进一步操作
    }
})
history.replaceState()

history.replaceState()history.pushState()非常相似,区别在于 replaceState() 是修改(替换)了当前的历史栈栈顶的状态信息,而不是新增。 注意这并不会阻止其在全局浏览器历史记录中创建一个新的历史记录项。

AURL push到 BURL,再push到CURL,后退会后退到BURL
AURL push到 BURL,再replace到 CURL,后退会后退到AURL

popstate事件

每当活动的历史记录项发生变化时, popstate 事件都会被传递给window对象。如果当前活动的历史记录项是被 pushState创建的,或者是由 replaceState 改变的,那么 popstate 事件的状态属性 state 会包含一个当前历史记录状态对象的拷贝(调用这两个方法的第一个参数)

页面加载时,或许会有个非null的状态对象。这是有可能发生的,举个例子,假如页面(通过pushState()replaceState()方法)设置了状态对象而后用户重启了浏览器。那么当页面重新加载时,页面会接收一个onload事件,但没有popstate事件,但是此时访问history.state属性会得到如同popstate被触发时能得到的状态对象。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容

  • 12.3 学习了python内置模块turtle,很可爱的画图模块 12.4 又开始学习Tkinter,看文档的时...
    橙子树上的橙皮猴阅读 190评论 0 0
  • *现在我倒是觉得我处于很危险的状态了,明天考试到现在才读完两课罢了,心里充满了不安!而且我刚刚还在看戏,然后不知道...
    午夜里的街灯阅读 111评论 0 1
  • 以秒为单位转换 我们先来声明一个时间单位是秒的变量,方面下面用 declare @a int = 2000 ---...
    肉肉要次肉阅读 3,653评论 0 0
  • 七百年后 已经记不清了,上一次这样静静地看着窗外的点点灯光、天上的月亮是什么时候了。今年暴雨之后便是入伏,天很炎热...
    猫猫love红豆粥阅读 220评论 1 1