參考文章
💓基礎>>使用WebSocket結合node.js實作一個線上聊天室,只需要3個檔案就完成
💓進階>>使用WebSocket結合node.js實作一個線上聊天室(進階功能:即時時間動態顯示,表情符號emoji等)
💓超進階>>使用WebSocket結合vue.js實作一個程式碼簡單明瞭的線上聊天室
Root Directory
├── vue.js
├── server.js
├── styles.css
└── index.html
這次進階教學是把原始的VanillaJS(原生Java script),轉換成vue.js的形式。
我們可以發現傳換成vue後,程式碼的function都是寫在methods:中,
而data() { return { }}裡面則是存放初始訊息,
像是.html中有使用大括號{{ }},它將資料動態地綁定到html的元素上,
使得資料變更時能夠自動更新。
因此只要使用vue.js,勢必你的.html也必須一起調整,因為這兩個會有滿多地方會關聯在一起。
vue.js
const app = Vue.createApp({
data() {
return {
messages_qu: [], // 用來存放聊天訊息的陣列
currentTime: '',
username: 'empty'
};
},
methods: {
connectWebSocket() {
this.socket = new WebSocket('ws://localhost:3000');
this.socket.onopen = () => {
console.log('已連接至 WebSocket 伺服器');
};
this.socket.onmessage = (event) => {
// console.log('接收到伺服器訊息:', event.data);
// 解析接收到的 JSON 資料
console.log('only event接收到伺服器訊息:', event);
console.log('before JSON.parse接收到伺服器訊息:', event.data);
const data = JSON.parse(event.data);
console.log('JSON.parse接收到伺服器訊息:', data.name+": "+data.message+";時間: "+data.time);
this.messages_qu.push(data);
// 將訊息放進messages_qu: [] 陣列中
};
this.socket.onclose = () => {
console.log('WebSocket 連接已關閉');
};
},
sendMessage() {
let formattedTime = this.getTime();
let inputName = document.getElementById('inputName').value;
let inputMessage = document.getElementById('inputMessage').value;
// 組合名稱和訊息成為一個物件,然後將其轉換成 JSON 格式的字串
const data = JSON.stringify({ name: inputName, message: inputMessage, time: formattedTime });
this.socket.send(data);
document.getElementById('inputMessage').value = ''; // 清空輸入框
//產生名字在頁面上
this.username= inputName
//document.querySelector('.user-name').textContent = "name: "+inputName
},
getTime() {
// 獲取當前時間
const currentTime = new Date();
const hours = currentTime.getHours();
const minutes = currentTime.getMinutes();
const seconds = currentTime.getSeconds();
// 將時間補零到兩位數
// 它的作用是根據條件的真假來返回不同的值。如果條件成立,則返回冒號前面的值,否則返回冒號後面的值。
const formattedHours = hours < 10 ? `0${hours}` : hours;
const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes;
const formattedSeconds = seconds < 10 ? `0${seconds}` : seconds;
const formattedTime = `${formattedHours}:${formattedMinutes}:${formattedSeconds}`; // 格式化為 HH:MM 格式
// 將格式化後的時間值作為物件返回
return formattedTime;
},
updateClock() {
const formattedTime = this.getTime();
this.currentTime = `Current Time: ${formattedTime}`;
},
insertEmoji(emoji) {
const inputMessage = document.getElementById('inputMessage');
inputMessage.value += emoji;
},
},
mounted() {
this.updateClock(); // 在 Vue 實例被掛載後先執行一次更新時間
// 每秒更新一次時間
setInterval(() => {
this.updateClock();
}, 1000);
},
created() { //created 鉤子是在 Vue 實例被建立後立即觸發的
this.connectWebSocket();
}
});
app.mount('.chat-container');
💫💦
created 鉤子函數:
1. created 鉤子在 Vue 實例被創建後立即被調用,這時 Vue 實例的資料已經初始化,
但 DOM 元素還未生成,也無法存取到 DOM。
2. 在 created 鉤子中,你可以進行一些初始化的工作,例如資料的處理、非同步請求、
監聽事件等,但注意此時尚無法存取到模板中的 DOM 元素。
mounted 鉤子函數:
1. mounted 鉤子在 Vue 實例被掛載到 DOM 後調用,此時 Vue 實例已經和 DOM 元素建立了關聯,
可以存取到模板中的 DOM 元素。
2. 在 mounted 鉤子中,你可以執行需要操作 DOM 元素的任務,
例如修改 DOM、進行網路請求、啟動定時器等。
3. 通常在 mounted 鉤子函數中進行一些需要依賴 DOM 的初始化操作或啟動定時器、
監聽器等與介面互動相關的任務。
💫💦
>> created 鉤子中呼叫了 connectWebSocket 方法,
這會在 Vue 實例被建立後立即執行 WebSocket 的連線操作。
>> mounted 鉤子中呼叫了 updateClock 方法,並且設定了一個計時器每秒鐘更新一次時間。
這些操作在 Vue 實例被掛載到 DOM 後進行。
👉
created 主要用於在實例被創建後進行一些初始化的資料處理和邏輯操作,
mounted 則用於操作已經渲染到 DOM 的 Vue 實例,
執行諸如 DOM 操作、定時器、網路請求等與介面交互相關的任務
index.html
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Example</title>
<link rel="stylesheet" href="styles.css">
<script src="https://unpkg.com/vue@3.3.0/dist/vue.global.js"></script>
</head>
<body>
<div class="chat-container">
<h1>WebSocket Chat Room</h1>
<span class="user-name">name: {{ username }}</span>
<!-- Message display area -->
<div id="messageBox">
<div v-for="message in messages_qu" class="message_item">
<span v-bind:class="{'nodeSpan1': message.name === username, 'otherClass': message.name !== username}">
{{ message.name }}: {{ message.message }}
</span>
<span class="timestamp">{{ message.time }}</span>
</div>
</div>
<!-- Input fields and send button -->
<div>
<label for="inputName">Name:</label>
<input type="text" id="inputName" class="chat-input" placeholder="Enter your name..."><br/><br/>
</div>
<div>
<label for="inputMessage">Text:</label>
<input type="text" id="inputMessage" class="chat-input" placeholder="Enter your message...">
<button id="smileyButton" class="emoji-button" v-on:click="insertEmoji('😊')">😊</button>
<button id="heartButton" class="emoji-button" v-on:click="insertEmoji('❤️')">❤️</button>
</div>
<button id="text_btn" class="send-button" v-on:click="sendMessage">Send</button>
<!-- Display area for showing real-time clock -->
<div id="realTimeClock">{{ currentTime }}</div>
</div>
<script src="vue.js"></script>
<!-- 載入vue.js -->
</body>
</html>
💫💦
messages_qu: [], // 用來存放聊天訊息的陣列
在這裡會把收到的訊息推進陣列中,
this.messages_qu.push(data);
// 將訊息放進messages_qu: [] 陣列中
會使用v-for迴圈把訊息列印出來在DOM上面{{ message.name }}: {{ message.message }}
<div v-for="message in messages_qu" class="message_item">
💫💦
>> v-bind:class
在這個範例中,message.name === username 的條件用於動態套用類別class名稱 .nodeSpan1,
這樣可以根據username動態地變更樣式。
當message.name === username時,class為nodeSpan1,
當message.name !== username時,class為otherClass。
然後,您可以在 CSS 中定義 .nodeSpan1 和otherClass的樣式,以便根據需要變更訊息內容的樣式。
<span v-bind:class="{'nodeSpan1': message.name === username, 'otherClass': message.name !== username}">
在css設定文字顏色樣式,當發送訊息的人,和使用者相同時,就變更訊息顏色。
.nodeSpan1{
color: #c70202;
}
💫💦
當有使用到{{ }}時,就會需要設定初始值,不然html一開始run時可能會叫不到東西。
<span class="user-name">name: {{ username }}</span>
<!-- Message display area -->
<div id="messageBox">
<div v-for="message in messages_qu" class="message_item">
<span v-bind:class="{'nodeSpan1': message.name === username, 'otherClass': message.name !== username}">
{{ message.name }}: {{ message.message }}
</span>
<span class="timestamp">{{ message.time }}</span>
</div>
</div>
初始值設定在vue的data(){return{ }}中。
data() {
return {
messages_qu: [], // 用來存放聊天訊息的陣列
currentTime: '',
username: 'empty'
};
},
💫💦
按下按鈕的監聽事件在原始的JS中是要使用addEventListener來進行撰寫。
document.getElementById('smileyButton').addEventListener('click', () => {
insertEmoji('😊'); // 插入笑臉符號
});
document.getElementById('heartButton').addEventListener('click', () => {
insertEmoji('❤️'); // 插入心形符號
});
但在vue中,直接在.html中加上v-on:click或@click,並輸入你要前往的函式,此例為insertEmoji(''),
就可以讓程式碼變得更簡潔。
<button id="smileyButton" class="emoji-button" v-on:click="insertEmoji('😊')">😊</button>
<button id="heartButton" class="emoji-button" v-on:click="insertEmoji('❤️')">❤️</button>
styles.css
/* Reset some default browser styles */
body, ul {
margin: 0;
padding: 0;
}
/* Chat room container */
.chat-container {
width: 80%;
margin: 20px auto;
border: 1px solid #ccc;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
/* Style for chat messages */
#messageBox {
background-color: #f4f4f4;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
border: 1px dashed #7a0404;
justify-content: space-between;
}
/* Style for input fields */
.chat-input {
width: calc(100% - 20px);
padding: 8px;
margin-top: 10px;
border-radius: 5px;
border: 1px solid #ccc;
}
/* Style for the send button */
.send-button {
padding: 8px 20px;
margin-top: 10px;
border-radius: 5px;
background-color: #007bff;
color: #fff;
border: none;
cursor: pointer;
}
p {
color: #460606;
}
/* Style for user names */
.user-name {
font-weight: bold;
color: #333;
font-size: 18px;
}
/* Style for timestamps */
.timestamp {
color: #500a0a;
}
#realTimeClock {
font-size: 20px;
font-weight: bold;
margin-top: 20px;
padding: 10px;
background-color: #f0c6c6;
border-radius: 5px;
}
.message_item{
display: flex;
justify-content: space-between;
}
.nodeSpan1{
color: #c70202;
}
💫💦
.message_item中的space-between;可以達到讓裡面元素分開的效果,詳情可參考上一篇。
💓進階>>使用WebSocket結合node.js實作一個線上聊天室(進階功能:即時時間動態顯示,表情符號emoji等)
.message_item{
display: flex;
justify-content: space-between;
}
💫💦
.nodeSpan1是當message.name 等於username時,把訊息的顏色進行改變。
.nodeSpan1{
color: #c70202;
}
在.html中, 若message.name 等於username, CLASS為nodeSpan1。
<span v-bind:class="{'nodeSpan1': message.name === username, 'otherClass': message.name !== username}">
{{ message.name }}: {{ message.message }}
</span>
server.js
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
// 提供靜態資源(HTML 檔案)
app.use(express.static('.'));
wss.on('connection', function connection(ws) {
console.log('有新的 WebSocket 連接');
ws.on('message', function incoming(message) {
const data = JSON.parse(message);
//message = message.toString('utf-8')
console.log('server接收到Name:'+data.name+" ; "+'server接收到訊息:'+data.message+" ; "+
'server接收到Time:'+data.time);
// 廣播訊息給所有客戶端
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) { //加了client !== ws && 代表訊息不傳給自己
console.log("(沒JSON.parse)-message: ", message );
console.log("(有JSON.parse)-data: ", data );
client.send(JSON.stringify(data)); //重點!! 這裡需要再把data轉成JSON檔案一次,不然front.js前端拿不到
}
});
});
});
// 啟動伺服器監聽指定的埠口
const PORT = process.env.PORT || 3000;
server.listen(PORT, function() {
console.log(`伺服器正在監聽埠口 ${PORT}`);
});
沒有留言:
張貼留言
喜歡我的文章嗎? 喜歡的話可以留言回應我喔! ^^