引言
前段時間在 github 上看到了一個很“trick”的項目:用純 CSS(即不使用 JavaScript)實(shí)現(xiàn)一個聊天應(yīng)用 —— css-only-chat。即下圖所示效果。
在我們的印象里,實(shí)現(xiàn)一個簡單的聊天應(yīng)用(消息發(fā)送與多頁面同步)并不困難 —— 這是在我們有 JavaScript 的幫助下。而如果讓你只能使用 CSS,不能有前端的 JavaScript 代碼,那你能夠?qū)崿F(xiàn)么?
原版是用 Ruby 寫的后端。可能大家對 Ruby 不太了解,所以我按照原作者思路,用 NodeJS 實(shí)現(xiàn)了一版 css-only-chat-node,對大家來說可能會更易讀些。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í)時的消息展示,一般會建立一個 WebSocket 連接;
最后,對于后端同步來的消息,我們會在瀏覽器端操作 DOM API 來改變 DOM 內(nèi)容,展示消息記錄。
涉及到 JavaScript 的操作主要就是上面四個了。但是,現(xiàn)在我們只能使用 CSS,那對于上面這幾個操作,可以用什么方式實(shí)現(xiàn)呢?
2. Trick Time 2.1. 解決“點(diǎn)擊監(jiān)聽”的問題使用 JavaScript 的話一行代碼可以搞定:
document.getElementById("btn").addEventListener("click", function () { // …… });
使用 CSS 的話,其實(shí)有個偽類可以幫我們,即:active。它可以選擇激活的元素,而當(dāng)我們點(diǎn)擊某個元素時,它就會處于激活狀態(tài)。
所以,對于上面動圖中的26個字母(再加上 send 按鈕),可以分配不同的classname,然后設(shè)置偽類選擇器,這樣就可以在點(diǎn)擊該字母對應(yīng)的按鈕時觸發(fā)命中某個 CSS 規(guī)則。例如可以對字符“a”設(shè)置如下規(guī)則用于“捕獲”點(diǎn)擊:
.btn_a:active { /* …… */ }2.2. 發(fā)送請求
如果有 JavaScript 的幫助,發(fā)送請求只需要用個 XHR 即可,很方便。而對于 CSS,如果要想發(fā)一個請求的話有什么辦法么?
可以使用background-image屬性,將它指定為某個 URL,這樣前端就會向服務(wù)器發(fā)起一個背景圖片的請求。之所以可以使用background-image屬性還因為:瀏覽器只有在該 CSS 選擇器規(guī)則被實(shí)際應(yīng)用到 DOM 元素后才會實(shí)際發(fā)起background-image的請求。例如下面這個規(guī)則:
.btn_a:active { background-image: url("/keys/a"); }
只有在字符“a”被點(diǎn)擊后,瀏覽器才會向服務(wù)器請求/keys/a這張“圖片”。而在服務(wù)器端,通過判斷 URL 可以知道前端點(diǎn)擊了哪個字符。例如,對于按鈕“b”會有如下規(guī)則:
.btn_b:active { background-image: url("/keys/b"); }
這樣就相當(dāng)于實(shí)現(xiàn)了在 URL(/keys/a與/keys/b) 中“傳參”。
2.3. 實(shí)時消息展示實(shí)時的消息展示,核心會用到一種叫“服務(wù)器推”的技術(shù)。其中比較常見方式有:
使用 JavaScript 來和服務(wù)端建立 WebSocket 連接
使用 JavaScript 創(chuàng)建定時器,定時發(fā)送請求輪詢
使用 JavaScript 和服務(wù)端配合來實(shí)現(xiàn)長輪詢
但這些方法都無法規(guī)避 JavaScript,顯然不符合咱們的要求。其實(shí)還有一種方式,我在《各類“服務(wù)器推”技術(shù)原理與實(shí)例》中也有提到,那就是基于 iframe 的長連接流(stream)模式。
這里我們主要是借鑒了“長連接流”這種模式。讓我們的頁面永遠(yuǎn)處于一個未加載完成的狀態(tài)。但是,由于請求頭中包含Transfer-Encoding: chunked,它會告訴瀏覽器,雖然頁面沒有返回結(jié)束,但你可以開始渲染頁面了。正是由于該請求的響應(yīng)永遠(yuǎn)不會結(jié)束,所以我們可以不斷向其中寫入新的內(nèi)容,來更新頁面展示。
實(shí)現(xiàn)起來也非常簡單。http.ServerResponse類本身就是繼承自Stream的,所以只要在需要更新頁面內(nèi)容時調(diào)用.write()方法即可。例如下面這段代碼,可以每隔2s在頁面上動態(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("2.4. 改變頁面信息
hello"), 2000); }).listen(8085);
在上一節(jié)我們已經(jīng)可以通過 Stream 的方式,不借助 JavaScript 即可動態(tài)改變頁面內(nèi)容了。但是如果你細(xì)心會發(fā)現(xiàn),這種方式只能不斷“append”內(nèi)容。而在我們的例子中,看起來更像是能夠動態(tài)改變某個 DOM 中的文本,例如隨著點(diǎn)擊不同按鈕,“Current Message”后面的文本會不斷變化。
這里其實(shí)也有個很“trick”的方式。下圖這個部分(我們姑且叫它 ChatPanel 吧)
其實(shí)我們每次調(diào)用res.write()時都會返回一個全新的 ChatPanel 的 HTML 片段。于此同時,還會附帶一個元素,將之前的 ChatPanel 設(shè)為display: none。所以看起來像是更新了原來的 ChatPanel 的內(nèi)容,但其實(shí)是 append 了一個新的,同時隱藏之前的 ChatPanel。
2.5. 點(diǎn)擊重復(fù)的按鈕到目前為止,基本的方案都有了,但還有一個重要的問題:
在 CSS 規(guī)則中的background-image只會在第一次應(yīng)用到元素時發(fā)起請求,之后就不會再向服務(wù)器請求了。也就是說,用
.btn_a:active { background-image: url("/keys/a"); }
這種規(guī)則,“a” 這個按鈕點(diǎn)過一次之后,下次再點(diǎn)擊就毫無反應(yīng)了 —— 即后端收不到請求了。
要解決這個問題有一個方法。可以在每次返回的新的 ChatPanel(ChatPanel 是啥咱們在上一節(jié)中提到了,如果忘了可以回去看下)里,為每個字符按鈕都應(yīng)用一套新的樣式規(guī)則,并設(shè)置新的背景圖 URL。例如我們第一次點(diǎn)擊了“h”之后,返回的 ChatPanel 里的按鈕“a”的classname會該成btn_h_a,對應(yīng)的 CSS 規(guī)則改為:
.btn_h_a:active { background-image: url("/keys/h_a"); }
再次點(diǎn)擊“i”之后,ChatPanel 里對應(yīng)的按鈕的樣式規(guī)則改為:
.btn_hi_a:active { background-image: url("/keys/hi_a"); }2.6. 存儲
為了能夠保存未發(fā)送的內(nèi)容(點(diǎn)擊 send 按鈕之前的輸入內(nèi)容),以及同步歷史消息,需要有個地方存儲用戶輸入。同時我們還會為每個連接設(shè)定一個唯一的用戶 ID。在原版的 css-only-chat 中使用了 Redis。我在 css-only-chat-node 中為了簡便,直接存儲在了運(yùn)行時的內(nèi)存變量中了。
3. 最后也許有朋友會問,這個 DEMO 有什么實(shí)用價值么?可以發(fā)展成一個可用的聊天工具么?
好吧,其實(shí)我覺得沒有太大用。但是里面涉及到的一些“知識點(diǎn)”到是了解下也無妨。我們每天面對那么多無趣的需求,偶爾看看這種“有意思”的項目也算是放松一下吧。
最后,如果想看具體的運(yùn)行效果,或者想了解代碼的細(xì)節(jié),可以看這里:
css-only-chat-node:由于原版是 Ruby 寫的,所以實(shí)現(xiàn)了一個 NodeJS 版的便于大家查看
css-only-chat:css-only-chat 的原版?zhèn)}庫,使用 Ruby 實(shí)現(xiàn)
Just have fun!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/114668.html
引言 前段時間在 github 上看到了一個很trick的項目:用純 CSS(即不使用 JavaScript)實(shí)現(xiàn)一個聊天應(yīng)用 —— css-only-chat。即下圖所示效果。 在我們的印象里,實(shí)現(xiàn)一個簡單的聊天應(yīng)用(消息發(fā)送與多頁面同步)并不困難 —— 這是在我們有 JavaScript 的幫助下。而如果讓你只能使用 CSS,不能有前端的 JavaScript 代碼,那你能夠?qū)崿F(xiàn)么? 原版...
摘要:用偽代碼來模擬下長輪詢的過程前端利用下面函數(shù)進(jìn)行請求后端代碼做如下更改利用隨機(jī)數(shù)的大小來模擬是否有新數(shù)據(jù)有新數(shù)據(jù)來了長輪詢的確減少了請求的次數(shù),但是它也有著很大的問題,那就是耗費(fèi)服務(wù)器的資源。 寫在前面 最近由于利用node重構(gòu)某個項目,項目中有一個實(shí)時聊天的功能,于是就研究了一下聊天室,在線demo|源碼,歡迎大家反饋。這個聊天室的主要利用到了socket.io和express。這個...
摘要:利用中間件實(shí)現(xiàn)異步請求,實(shí)現(xiàn)兩個用戶角色實(shí)時通信。目前還未深入了解的一些概念。往后會寫更多的前后臺聯(lián)通的項目。刪除分組會連同組內(nèi)的所有圖片一起刪除。算是對自己上次用寫后臺的一個強(qiáng)化,項目文章在這里。后來一直沒動,前些日子才把后續(xù)的完善。 歡迎訪問我的個人網(wǎng)站:http://www.neroht.com/? 剛學(xué)vue和react時,利用業(yè)余時間寫的關(guān)于這兩個框架的訓(xùn)練,都相對簡單,有的...
摘要:并且指定收到消息,以及端口的監(jiān)聽方法。四代碼示例多房間實(shí)時聊天室配置版本須在里配置定義,并設(shè)置。使同一個的請求能夠落在同一個機(jī)器同一個進(jìn)程中。通過主進(jìn)程統(tǒng)一管理維護(hù)子進(jìn)程,每個進(jìn)程監(jiān)聽一個端口。 showImg(http://7tszky.com1.z0.glb.clouddn.com/FkhApdRySR927nkdDZuUPBQbJtXG); 一、相關(guān)技術(shù)介紹: 消息實(shí)時推送,指的...
摘要:在零信任世界中,六種提高安全性的最佳做法在亞馬遜網(wǎng)絡(luò)服務(wù)公司,報告稱,年第三季度的收入為億美元,年前三個財政季度的收入為億美元。在零信任世界中增加對的安全性被盜的特權(quán)訪問憑據(jù)是當(dāng)今違規(guī)的主要原因。在零信任世界中,六種提高AWS安全性的最佳做法在亞馬遜網(wǎng)絡(luò)服務(wù)公司(Amazon Web Services,AWS)報告稱,2018年第三季度的收入為66億美元,2018年前三個財政季度的收入為18...
閱讀 2065·2021-10-08 10:04
閱讀 3094·2021-09-22 10:02
閱讀 2247·2019-08-30 15:56
閱讀 835·2019-08-30 15:54
閱讀 933·2019-08-30 15:54
閱讀 1288·2019-08-30 15:53
閱讀 2517·2019-08-30 11:21
閱讀 3566·2019-08-30 10:56