国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

如何在零JS代碼情況下實(shí)現(xiàn)一個(gè)實(shí)時(shí)聊天功能?

alin / 2736人閱讀

引言

前段時(shí)間在 github 上看到了一個(gè)很“trick”的項(xiàng)目:用純 CSS(即不使用 JavaScript)實(shí)現(xiàn)一個(gè)聊天應(yīng)用 —— css-only-chat。即下圖所示效果。

在我們的印象里,實(shí)現(xiàn)一個(gè)簡單的聊天應(yīng)用(消息發(fā)送與多頁面同步)并不困難 —— 這是在我們有 JavaScript 的幫助下。而如果讓你只能使用 CSS,不能有前端的 JavaScript 代碼,那你能夠?qū)崿F(xiàn)么?

原版是用 Ruby 寫的后端。可能大家對 Ruby 不太了解,所以我按照原作者思路,用 NodeJS 實(shí)現(xiàn)了一版 css-only-chat-node,對大家來說可能會(huì)更易讀些。

1. 我們要解決什么問題

首先強(qiáng)調(diào)一下,服務(wù)端的代碼肯定還是需要寫的,而且這部分顯然不能是 CSS。所以這里的“純 CSS”主要指在瀏覽器端只使用 CSS。

回憶一下,如果使用 JavaScript 來實(shí)現(xiàn)上圖中展示的聊天功能,有哪些問題需要處理呢?

首先,需要添加按鈕的click

事件監(jiān)聽,包括字符按鈕的點(diǎn)擊與發(fā)送按鈕的點(diǎn)擊; 其次,點(diǎn)擊相應(yīng)按鈕后,要將信息通過 Ajax 的方式發(fā)送到后端服務(wù); 再者,要實(shí)現(xiàn)實(shí)時(shí)的消息展示,一般會(huì)建立一個(gè) WebSocket 連接; 最后,對于后端同步來的消息,我們會(huì)在瀏覽器端操作 DOM API 來改變 DOM 內(nèi)容,展示消息記錄。

涉及到 JavaScript 的操作主要就是上面四個(gè)了。但是,現(xiàn)在我們只能使用 CSS,那對于上面這幾個(gè)操作,可以用什么方式實(shí)現(xiàn)呢?

2. Trick Time

2.1. 解決“點(diǎn)擊監(jiān)聽”的問題

使用 JavaScript 的話一行代碼可以搞定:

document.getElementById("btn").addEventListener("click", function () { // …… }); 復(fù)制代碼

使用 CSS 的話,其實(shí)有個(gè)偽類可以幫我們,即:active

。它可以選擇激活的元素,而當(dāng)我們點(diǎn)擊某個(gè)元素時(shí),它就會(huì)處于激活狀態(tài)。

所以,對于上面動(dòng)圖中的26個(gè)字母(再加上 send 按鈕),可以分配不同的classname

,然后設(shè)置偽類選擇器,這樣就可以在點(diǎn)擊該字母對應(yīng)的按鈕時(shí)觸發(fā)命中某個(gè) CSS 規(guī)則。例如可以對字符“a”設(shè)置如下規(guī)則用于“捕獲”點(diǎn)擊:

.btn_a:active { /* …… */ } 復(fù)制代碼

2.2. 發(fā)送請求

如果有 JavaScript 的幫助,發(fā)送請求只需要用個(gè) XHR 即可,很方便。而對于 CSS,如果要想發(fā)一個(gè)請求的話有什么辦法么?

可以使用background-image

屬性,將它指定為某個(gè) URL,這樣前端就會(huì)向服務(wù)器發(fā)起一個(gè)背景圖片的請求。之所以可以使用background-image

屬性還因?yàn)椋簽g覽器只有在該 CSS 選擇器規(guī)則被實(shí)際應(yīng)用到 DOM 元素后才會(huì)實(shí)際發(fā)起background-image

的請求。例如下面這個(gè)規(guī)則:

.btn_a:active { background-image: url("/keys/a"); } 復(fù)制代碼

只有在字符“a”被點(diǎn)擊后,瀏覽器才會(huì)向服務(wù)器請求/keys/a

這張“圖片”。而在服務(wù)器端,通過判斷 URL 可以知道前端點(diǎn)擊了哪個(gè)字符。例如,對于按鈕“b”會(huì)有如下規(guī)則:

.btn_b:active { background-image: url("/keys/b"); } 復(fù)制代碼

這樣就相當(dāng)于實(shí)現(xiàn)了在 URL(/keys/a

/keys/b

) 中“傳參”。

2.3. 實(shí)時(shí)消息展示

實(shí)時(shí)的消息展示,核心會(huì)用到一種叫“服務(wù)器推”的技術(shù)。其中比較常見方式有:

使用 JavaScript 來和服務(wù)端建立 WebSocket 連接 使用 JavaScript 創(chuàng)建定時(shí)器,定時(shí)發(fā)送請求輪詢 使用 JavaScript 和服務(wù)端配合來實(shí)現(xiàn)長輪詢

但這些方法都無法規(guī)避 JavaScript,顯然不符合咱們的要求。其實(shí)還有一種方式,我在《各類“服務(wù)器推”技術(shù)原理與實(shí)例》中也有提到,那就是基于 iframe 的長連接流(stream)模式。

這里我們主要是借鑒了“長連接流”這種模式。讓我們的頁面永遠(yuǎn)處于一個(gè)未加載完成的狀態(tài)。但是,由于請求頭中包含Transfer-Encoding: chunked

,它會(huì)告訴瀏覽器,雖然頁面沒有返回結(jié)束,但你可以開始渲染頁面了。正是由于該請求的響應(yīng)永遠(yuǎn)不會(huì)結(jié)束,所以我們可以不斷向其中寫入新的內(nèi)容,來更新頁面展示。

實(shí)現(xiàn)起來也非常簡單。http.ServerResponse

類本身就是繼承自Stream

的,所以只要在需要更新頁面內(nèi)容時(shí)調(diào)用.write()

方法即可。例如下面這段代碼,可以每隔2s在頁面上動(dòng)態(tài)添加 "hello" 字符串而不需要任何瀏覽器端的配合(也就不需要寫 JavaScript 代碼了):

const http = require("http"); http.createServer((req, res) => { res.setHeader("connection", "keep-alive"); res.setHeader("content-type", "text/html; charset=utf-8"); res.statusCode = 200; res.write("I will update by myself"); setInterval(() => res.write("
hello"), 2000); }).listen(8085); 復(fù)制代碼

2.4. 改變頁面信息

在上一節(jié)我們已經(jīng)可以通過 Stream 的方式,不借助 JavaScript 即可動(dòng)態(tài)改變頁面內(nèi)容了。但是如果你細(xì)心會(huì)發(fā)現(xiàn),這種方式只能不斷“append”內(nèi)容。而在我們的例子中,看起來更像是能夠動(dòng)態(tài)改變某個(gè) DOM 中的文本,例如隨著點(diǎn)擊不同按鈕,“Current Message”后面的文本會(huì)不斷變化。

這里其實(shí)也有個(gè)很“trick”的方式。下圖這個(gè)部分(我們姑且叫它 ChatPanel 吧)

其實(shí)我們每次調(diào)用res.write()

時(shí)都會(huì)返回一個(gè)全新的 ChatPanel 的 HTML 片段。于此同時(shí),還會(huì)附帶一個(gè)

      <