JS高程:讀書摘要(十四)拖放、音視頻

一、 跨文檔消息傳遞

跨文檔消息傳送(cross-document messaging),有時候簡稱為XDM,指的是在來自不同域的頁面間傳遞消息。

XDM 的核心是postMessage()方法,向另一個地方(包含在當(dāng)前頁面中的<iframe>元素,或者由當(dāng)前頁面彈出的窗口。)傳遞數(shù)據(jù)。

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

  • otherWindow:其他窗口的一個引用,比如iframecontentWindow屬性、執(zhí)行window.open返回的窗口對象、或者是命名過或數(shù)值索引的window.frames
  • message : 將要發(fā)送到其他 window的數(shù)據(jù)。它將會被結(jié)構(gòu)化克隆算法序列化。這意味著你可以不受什么限制的將數(shù)據(jù)對象安全的傳送給目標(biāo)窗口而無需自己序列化。

  • targetOrigin:通過窗口的origin屬性來指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示無限制,不建議使用)或者一個URI。如果目標(biāo)窗口的協(xié)議、主機(jī)地址或端口這三者的任意一項不匹配targetOrigin提供的值,那么消息就不會被發(fā)送;只有三者完全匹配,消息才會被發(fā)送。這個機(jī)制用來控制消息可以發(fā)送到哪些窗口(非常重要);

  • transfer[可選]:是一串和message同時傳遞的 Transferable 對象. 這些對象的所有權(quán)將被轉(zhuǎn)移給消息的接收方,而發(fā)送一方將不再保有所有權(quán)。

接收到XDM消息時,會觸發(fā)window對象的message事件。這個事件是以異步形式觸發(fā)的,因此從發(fā)送消息到接收消息(觸發(fā)接收窗口的message事件)可能要經(jīng)過一段時間的延遲。觸發(fā)message事件后,傳遞給onmessage處理程序的事件對象包含以下三方面的重要信息。

  • data:從其他 window中傳遞過來的對象。
  • origin:調(diào)用 postMessage 時消息發(fā)送方窗口的 origin. 這個字符串由 協(xié)議、域名、端口號拼接而成。端口號(443https的默認(rèn)值)
  • source:對發(fā)送消息的窗口對象的引用; 可以使用此來在具有不同origin的兩個窗口之間建立雙向通信。

安全相關(guān):如果您不希望從其他網(wǎng)站接收message,請不要為message事件添加任何事件偵聽器。 這是一個完全萬無一失的方式來避免安全問題;如果您確實希望從其他網(wǎng)站接收message,請始終使用originsource屬性驗證發(fā)件人的身份。 當(dāng)您使用postMessage將數(shù)據(jù)發(fā)送到其他窗口時,始終指定精確的目標(biāo)origin,而不是"*"

//  示例:
// 主窗口域名是<http://example.com:8080> 傳遞"i am main" 給彈出頁
// 彈出頁域名是<http://example.org> 傳遞"i am pop"給主窗口

// 主窗口 JS

var popup = window.open('http://example.org','pop') 
// 參數(shù)是(url,新窗口名稱) 不是title 而是可以用來作為超鏈接 <a> 或表單 <form> 元素的目標(biāo)屬性值。字符串中不能含有空白字符。

popup.postMessage("i am main", "http://example.org");
// 發(fā)送->  傳遞消息給彈出頁 第二個參數(shù)必須精準(zhǔn)匹配

// 接收-> 定義事件處理程序處理接收到的消息
function receiveMessage(event){
    // 必須使用判斷 以保證安全 網(wǎng)站不被攻擊
    if (event.origin !== "http://example.org"){
        return;
    }else{
        // do something
    }
    // event.source 是我們通過window.open打開的彈出頁面 popup
    // event.data 是 popup發(fā)送給當(dāng)前頁面的消息
}

window.addEventListener("message",receiveMessage,false)
// 彈出頁 JS
function popReceiveMessage(event){
    if (event.origin !== "http://example.com:8080"){
        return;
    }else{
        // 向來源窗口發(fā)送回執(zhí)
        event.source.postMessage("i am pop" +event.origin);
    }
}

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

二、拖放

拖放事件
  • 發(fā)生在被拖放的元素上。
    • dragstart:按下鼠標(biāo)鍵并開始移動鼠標(biāo)時觸發(fā)。
    • drag:在元素被拖動期間會持續(xù)觸發(fā)該事件
    • dragend:當(dāng)拖動停止時觸發(fā)(無論是否將元素放到了有效目標(biāo))
  • 發(fā)生在放置目標(biāo)上。
    • dragenter:有元素被拖入時觸發(fā)。
    • dragover:被拖動元素在放置目標(biāo)內(nèi)部移動時觸發(fā)。
    • dragleave:被拖動元素被拖出放置目標(biāo)后觸發(fā)。(進(jìn)來之后出去)
    • drop:元素被放到了放置目標(biāo)中時觸發(fā)。

如果拖動元素經(jīng)過不允許放置的元素,無論用戶如何操作,都不會發(fā)生drop 事件。不過,你可以把任何元素變成有效的放置目標(biāo),方法是阻止dragenterdragover 事件的默認(rèn)行為。你就會發(fā)現(xiàn)當(dāng)拖動著元素移動到放置目標(biāo)上時,光標(biāo)變成了允許放置的符號。當(dāng)然,釋放鼠標(biāo)也會觸發(fā)drop事件。

Firefox 3.5+中,放置事件的默認(rèn)行為是打開被放到放置目標(biāo)上的URL(被放入元素是圖片則頁面轉(zhuǎn)向圖像文件,如果是文本,則導(dǎo)致錯誤,無效的URL),如果想正常處理放置事件,也需要阻止drop事件打開URL的行為

dataTransfer對象

它是事件對象的一個屬性,用于從被拖動元素向放置目標(biāo)傳遞字符串格式的數(shù)據(jù)。只能在拖放事件的事件處理程序中訪問dataTransfer對象。有兩個主要方法:getData()setData()

//設(shè)置和接收文本數(shù)據(jù)
event.dataTransfer.setData("text", "some text");
var text = event.dataTransfer.getData("text");
//設(shè)置和接收URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
var url = event.dataTransfer.getData("URL");

IE只定義了"text""URL"兩種有效的數(shù)據(jù)類型,而HTML5則對此加以擴(kuò)展,允許指定各種MIME類型。考慮到向后兼容,HTML5 也支持"text""URL",但這兩種類型會被映射為"text/plain""text/uri-list"

保存在dataTransfer對象中的數(shù)據(jù)只能在drop事件處理程序中讀取。如果在ondrop處理程序中沒有讀到數(shù)據(jù),那就是dataTransfer對象已經(jīng)被銷毀,數(shù)據(jù)也丟失了。

在拖動文本框中的文本或鏈接、圖像時,瀏覽器會調(diào)用setData()方法以"text""URL"保存,然后,在這些元素被拖放到放置目標(biāo)時,就可以通過getData()讀到這些數(shù)據(jù)。當(dāng)然,作為開發(fā)人員,你也可以在dragstart事件處理程序中調(diào)用setData(),手工保存自己要傳輸?shù)臄?shù)據(jù),以便將來使用。如果將數(shù)據(jù)保存為URL,瀏覽器會將其當(dāng)成網(wǎng)頁中的鏈接。換句話說,如果你把它放置到另一個瀏覽器窗口中,瀏覽器就會打開該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屬性可以知道被拖動的元素能夠執(zhí)行哪種放置行為
    • "none":不能把拖動的元素放在這里。這是除文本框之外所有元素的默認(rèn)值。
    • "move":應(yīng)該把拖動的元素移動到放置目標(biāo)。
    • "copy":應(yīng)該把拖動的元素復(fù)制到放置目標(biāo)。
    • "link":表示放置目標(biāo)會打開拖動的元素(但拖動的元素必須是一個鏈接,有URL)。

使用dropEffect屬性,必須在ondragenter事件處理程序中針對放置目標(biāo)來設(shè)置它。在把元素拖動到放置目標(biāo)上時,以上每一個值都會導(dǎo)致光標(biāo)顯示為不同的符號。然而,瀏覽器只能幫你改變光標(biāo)的樣式,而其他的都要靠你自己來實現(xiàn)。

dropEffect 屬性只有搭配effectAllowed 屬性才有用。effectAllowed 屬性表示允許拖動元素的哪種dropEffect,必須在ondragstart 事件處理程序中設(shè)置effectAllowed 屬性,定義此次拖動可以允許哪些操作,而當(dāng)ondragenter觸發(fā)時,針對放置目標(biāo)來設(shè)置dropEffect屬性來進(jìn)行該操作。

  • effectAllowed 屬性表示允許拖動元素的哪種dropEffect
    • "uninitialized":沒有給被拖動的元素設(shè)置任何放置行為。
    • "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元素規(guī)定了一個draggable 屬性,表示元素是否可以拖動。圖像和鏈接的draggable 屬性自動被設(shè)置成了true,而其他元素這個屬性的默認(rèn)值都是false。要想讓其他元素可拖動,或者讓圖像或鏈接不能拖動,都可以設(shè)置這個屬性。

其他成員

dataTransfer 對象還應(yīng)該包含下列方法和屬性

  • addElement(element):為拖動操作添加一個元素。添加這個元素只影響數(shù)據(jù)(即增加作為拖動源而響應(yīng)回調(diào)的對象),不會影響拖動操作時頁面元素的外觀。部分瀏覽器支持
  • clearData(format):清除以特定格式保存的數(shù)據(jù)。實現(xiàn)這個方法的瀏覽器有IEFireforx 3.5+ChromeSafari 4+
  • setDragImage(element, x, y):指定一幅圖像,當(dāng)拖動發(fā)生時,顯示在光標(biāo)下方。這個方法接收的三個參數(shù)分別是要顯示的HTML元素和光標(biāo)在圖像中的x、y坐標(biāo)。其中,HTML元素可以是一幅圖像,也可以是其他元素。是圖像則顯示圖像,是其他元素則顯示渲染后的元素。實現(xiàn)這個方法的瀏覽器有Firefox 3.5+Safari 4+Chrome
  • types:當(dāng)前保存的數(shù)據(jù)類型。這是一個類似數(shù)組的集合,以"text"這樣的字符串形式保存著數(shù)據(jù)類型。實現(xiàn)這個屬性的瀏覽器有IE10+Firefox 3.5+Chrome

三、媒體元素

<audio><video>

位于開始和結(jié)束標(biāo)簽之間的任何內(nèi)容都將作為后備內(nèi)容,在瀏覽器不支持這兩個媒體元素的情況下顯示。因為并非所有瀏覽器都支持所有媒體格式,所以可以指定多個不同的媒體來源。為此,不用在標(biāo)簽中指定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>

通過這些屬性可以知道媒體的當(dāng)前狀態(tài)。:

  • src:指向要加載的媒體文件。
  • widthheight:指定視頻播放器的大小
  • poster:屬性指定圖像的URI可以在加載視頻內(nèi)容期間顯示一幅圖像。(封面)
  • controls:布爾值,瀏覽器是否顯示UI 控件,以便用戶直接操作媒體
  • autoplay : 布爾值 取得或設(shè)置autoplay標(biāo)志
  • buffered :時間范圍 表示已下載的緩沖的時間范圍的對象
  • bufferedBytes : 字節(jié)范圍 表示已下載的緩沖的字節(jié)范圍的對象
  • bufferingRate: 整數(shù) 下載過程中每秒鐘平均接收到的位數(shù)
  • bufferingThrottled : 布爾值 表示瀏覽器是否對緩沖進(jìn)行了節(jié)流
  • currentLoop: 整數(shù) 媒體文件已經(jīng)循環(huán)的次數(shù)
  • currentSrc :字符串 當(dāng)前播放的媒體文件的URL
  • currentTime : 浮點數(shù) 已經(jīng)播放的秒數(shù)
  • defaultPlaybackRate :浮點數(shù) 取得或設(shè)置默認(rèn)的播放速度。默認(rèn)值為1.0秒
  • duration :浮點數(shù) 媒體的總播放時間(秒數(shù))
  • ended : 布爾值 表示媒體文件是否播放完成
  • loop: 布爾值 取得或設(shè)置媒體文件在播放完成后是否再從頭開始播放
  • muted: 布爾值 取得或設(shè)置媒體文件是否靜音
  • networkState: 整數(shù) 表示當(dāng)前媒體的網(wǎng)絡(luò)連接狀態(tài):0表示空,1表示正在加載,2表示正在加載元數(shù)據(jù),3表示已經(jīng)加載了第一幀,4表示加載完成
  • paused: 布爾值 表示播放器是否暫停
  • playbackRate : 浮點數(shù) 取得或設(shè)置當(dāng)前的播放速度。用戶可以改變這個值,讓媒體播放速度變快或變慢,這與只能由開發(fā)人員修改的defaultPlaybackRate不同
  • played: 時間范圍 到目前為止已經(jīng)播放的時間范圍
  • readyState :整數(shù) 表示媒體是否已經(jīng)就緒(可以播放了)。0表示數(shù)據(jù)不可用,1表示可以顯示當(dāng)前幀,2表示可以開始播放,3表示媒體可以從頭到尾播放
  • seekable : 時間范圍 可以搜索的時間范圍
  • seeking: 布爾值 表示播放器是否正移動到媒體文件中的新位置
  • start:浮點數(shù) 取得或設(shè)置媒體文件中開始播放的位置,以秒表示
  • totalBytes: 整數(shù) 當(dāng)前資源所需的總字節(jié)數(shù)
  • videoHeight: 整數(shù) 返回視頻(不一定是元素)的高度。只適用于<video>
  • videoWidth: 整數(shù) 返回視頻(不一定是元素)的寬度。只適用于<video>
  • volume:浮點數(shù) 取得或設(shè)置當(dāng)前音量,值為0.0到1.0

事件有:

  • abort 下載中斷
  • canplay 可以播放時;readyState值為2
  • canplaythrough: 播放可繼續(xù),而且應(yīng)該不會中斷;readyState值為3
  • canshowcurrentframe :當(dāng)前幀已經(jīng)下載完成;readyState值為1
  • dataunavailable:因為沒有數(shù)據(jù)而不能播放;readyState值為0
  • durationchange :duration屬性的值改變
  • emptied:網(wǎng)絡(luò)連接關(guān)閉
  • empty :發(fā)生錯誤阻止了媒體下載
  • ended :媒體已播放到末尾,播放停止
  • error:下載期間發(fā)生網(wǎng)絡(luò)錯誤
  • load: 所有媒體已加載完成。這個事件可能會被廢棄,建議使用canplaythrough
  • loadeddata:媒體的第一幀已加載完成
  • loadedmetadata: 媒體的元數(shù)據(jù)已加載完成
  • loadstart:下載已開始
  • pause: 播放已暫停
  • play: 媒體已接收到指令開始播放
  • playing: 媒體已實際開始播放
  • progress: 正在下載
  • ratechange: 播放媒體的速度改變
  • seeked:搜索結(jié)束
  • seeking :正移動到新位置
  • stalled 瀏覽器嘗試下載,但未接收到數(shù)據(jù)
  • timeupdatecurrentTime被以不合理或意外的方式更新
  • volumechangevolume屬性值或muted屬性值已改變
  • waiting: 播放暫停,等待下載更多數(shù)據(jù)
//取得元素的引用
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";
    }
});
//定時更新當(dāng)前時間 以小于1000ms觸發(fā)
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
    //進(jìn)一步處理
}
//可能是"probably"
if (audio.canPlayType("audio/ogg; codecs=\"vorbis\"")){ // MIME & 編碼格式
    //進(jìn)一步處理
}

常用音頻的音頻格式和編解碼器

  • 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 構(gòu)造函數(shù)Audio,可以在任何時候播放音頻。從同為DOM元素的角度看,AudioImage很相似,但Audio不用像Image那樣必須插入到文檔中。只要創(chuàng)建一個新實例,并傳入音頻源文件即可。

var audio = new Audio("sound.mp3");
EventUtil.addHandler(audio, "canplaythrough", function(event){
    audio.play();
})
// 創(chuàng)建實例即可開始下載指定的文件。下載完成后,調(diào)用play()就可以播放音頻。

iOS 中,調(diào)用play()時會彈出一個對話框,得到用戶的許可后才能播放聲音。如果想在一段音頻播放后再播放另一段音頻,必須在onfinish事件處理程序中調(diào)用play()方法。

四、歷史狀態(tài)管理

history.pushState()

通過狀態(tài)管理API, 能夠在不加載新頁面的情況下改變?yōu)g覽器的URL 。為此, 需要使用history.pushState()方法,該方法可以接收三個參數(shù):狀態(tài)對象、新狀態(tài)的標(biāo)題和新URL

URL不必須為絕對路徑。如果新URL是相對路徑,那么它將被作為相對于當(dāng)前URL處理。新URL必須與當(dāng)前URL同源,否則 pushState()會拋出一個異常。該參數(shù)是可選的,缺省為當(dāng)前URL

// 假設(shè)在 http://mozilla.org/foo.html 中執(zhí)行了以下 JS 代碼:
var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "bar.html");
// 第二個參數(shù)沒有瀏覽器實現(xiàn),可以傳空字符串 或者一個短標(biāo)題(安全的選擇)。
// 這將使瀏覽器地址欄顯示為 http://mozilla.org/bar.html
// 但并不會導(dǎo)致瀏覽器加載 bar.html ,甚至不會檢查bar.html 是否存在。

執(zhí)行pushState()方法后,新的狀態(tài)信息就會被加入歷史狀態(tài)棧,而瀏覽器地址欄也會變成新的相對URL。狀態(tài)改變之后查詢location.href也會返回與地址欄中相同的地址,但是瀏覽器并不會真的向服務(wù)器發(fā)送請求。

假設(shè)現(xiàn)在用戶又訪問了 http://google.com,然后點擊了返回按鈕。此時,地址欄將顯示 http://mozilla.org/bar.html,同時頁面會觸發(fā) popstate 事件,事件對象state中包含了 stateObj 的一份拷貝。頁面本身與 foo.html 一樣,盡管其在 popstate 事件中可能會修改自身的內(nèi)容。

如果我們再次點擊返回按鈕,頁面URL會變?yōu)?code>http://mozilla.org/foo.html,文檔對象document會觸發(fā)另外一個 popstate 事件,這一次的事件對象state objectnull。 這里也一樣,返回并不改變文檔的內(nèi)容,盡管文檔在接收 popstate 事件時可能會改變自己的內(nèi)容,其內(nèi)容仍與之前的展現(xiàn)一致

EventUtil.addHandler(window, "popstate", function(event){
    var state = event.state;
    if (state){ //第一個頁面加載時state 為空
        // 進(jìn)一步操作
    }
})
history.replaceState()

history.replaceState()history.pushState()非常相似,區(qū)別在于 replaceState() 是修改(替換)了當(dāng)前的歷史棧棧頂?shù)臓顟B(tài)信息,而不是新增。 注意這并不會阻止其在全局瀏覽器歷史記錄中創(chuàng)建一個新的歷史記錄項。

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

popstate事件

每當(dāng)活動的歷史記錄項發(fā)生變化時, popstate 事件都會被傳遞給window對象。如果當(dāng)前活動的歷史記錄項是被 pushState創(chuàng)建的,或者是由 replaceState 改變的,那么 popstate 事件的狀態(tài)屬性 state 會包含一個當(dāng)前歷史記錄狀態(tài)對象的拷貝(調(diào)用這兩個方法的第一個參數(shù))

頁面加載時,或許會有個非null的狀態(tài)對象。這是有可能發(fā)生的,舉個例子,假如頁面(通過pushState()replaceState()方法)設(shè)置了狀態(tài)對象而后用戶重啟了瀏覽器。那么當(dāng)頁面重新加載時,頁面會接收一個onload事件,但沒有popstate事件,但是此時訪問history.state屬性會得到如同popstate被觸發(fā)時能得到的狀態(tài)對象。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 12.3 學(xué)習(xí)了python內(nèi)置模塊turtle,很可愛的畫圖模塊 12.4 又開始學(xué)習(xí)Tkinter,看文檔的時...
    橙子樹上的橙皮猴閱讀 263評論 0 0
  • *現(xiàn)在我倒是覺得我處于很危險的狀態(tài)了,明天考試到現(xiàn)在才讀完兩課罷了,心里充滿了不安!而且我剛剛還在看戲,然后不知道...
    午夜里的街燈閱讀 116評論 0 1
  • 以秒為單位轉(zhuǎn)換 我們先來聲明一個時間單位是秒的變量,方面下面用 declare @a int = 2000 ---...
    肉肉要次肉閱讀 3,684評論 0 0
  • 七百年后 已經(jīng)記不清了,上一次這樣靜靜地看著窗外的點點燈光、天上的月亮是什么時候了。今年暴雨之后便是入伏,天很炎熱...
    貓貓love紅豆粥閱讀 229評論 1 1