Chrome Pointer

2023年12月11日 星期一

使用WebSocket結合node.js實作一個線上聊天室(進階功能:即時時間動態顯示,表情符號emoji等)

參考文章

💓基礎>>使用WebSocket結合node.js實作一個線上聊天室,只需要3個檔案就完成

💓進階>>使用WebSocket結合node.js實作一個線上聊天室(進階功能:即時時間動態顯示,表情符號emoji等)

💓超進階>>使用WebSocket結合vue.js實作一個程式碼簡單明瞭的線上聊天室


Root Directory
  ├── front.js
  ├── server.js
  ├── styles.css
  └── index.html

WebSocket加上發訊息時間,即時時間動態顯示,表情符號emoji,
發文者訊息文字顏色不同等CSS相關新功能。

front.js
// 建立 WebSocket 連接
const socket = new WebSocket('ws://localhost:3000'); // 根據您的伺服器位址和埠號設定

// 連接成功時的事件處理
socket.onopen = function(event) {
  console.log('已連接至 WebSocket 伺服器');
};

// 接收到訊息時的事件處理
socket.onmessage = function(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);

    // 將訊息顯示在畫面上
    let messageBox = document.getElementById('messageBox');
    let nodeDiv=document.createElement("div");
    nodeDiv.style.display = "flex";  //運用Flexbox 佈局
    nodeDiv.style.justifyContent = "space-between"; // 設置 flex 容器屬性,讓兩個元素(ex:span)可以分隔最開

    let nodeSpan1=document.createElement("span");
    let nodeSpan2=document.createElement("span");
    nodeSpan2.classList.add("timestamp");  // 添加 class 到 <span> 元素
    let br = document.createElement("br");

    check_name(data.name, nodeSpan1); //(確認名字name是否相同) 如果發表訊息的人是自己 就將訊息的顏色進行更改

    textnode=document.createTextNode(`- ${data.name} : ${data.message}`);
    texttime=document.createTextNode(`- ${data.time}`);
   
    // 將 <span> 元素和文字節點附加到訊息框中
    messageBox.appendChild(nodeDiv).appendChild(nodeSpan1).appendChild(textnode);
    messageBox.appendChild(nodeDiv).appendChild(nodeSpan2).appendChild(texttime);
    messageBox.appendChild(br); // 在 <span> 元素後面插入 <br> 元素
};

// 關閉連接時的事件處理
socket.onclose = function(event) {
  console.log('WebSocket 連接已關閉');
};

// 送出訊息函數
document.getElementById('text_btn').addEventListener('click', () =>{
    const inputName = document.getElementById('inputName').value;
    const inputMessage = document.getElementById('inputMessage').value;

    // 獲取當前時間
    formattedTime = getTime()

    // 組合名稱和訊息成為一個物件,然後將其轉換成 JSON 格式的字串
    const data = JSON.stringify({ name: inputName, message: inputMessage, time: formattedTime });

    socket.send(data);
    document.getElementById('inputMessage').value = ''; // 清空輸入框

    //產生名字在頁面上
    document.querySelector('.user-name').textContent = "name: "+inputName

})

function check_name(dataname, nodeSpan1){
  let inputName = document.getElementById('inputName').value;
  if(dataname===inputName){
    nodeSpan1.style.color = 'blue'; // 如果是自己發表的文字 將它變成Blue顏色
  }
}

// 獲取當前時間函式
function 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;
}

// 更新時間
function updateClock() {
  // 獲取當前時間
  formattedTime = getTime()
  document.getElementById('realTimeClock').textContent = `Current Time: ${formattedTime}`;
}

// 每秒更新時間  //它會以指定的時間間隔執行指定的函式,不會重新整理整個網頁。
setInterval(updateClock, 1000);

// 網頁載入時先執行一次更新
updateClock();

document.getElementById('smileyButton').addEventListener('click', () => {
  insertEmoji('😊'); // 插入笑臉符號
});

document.getElementById('heartButton').addEventListener('click', () => {
  insertEmoji('❤️'); // 插入心形符號
});

function insertEmoji(emoji) {
  const inputMessage = document.getElementById('inputMessage');
  inputMessage.value += emoji;
}

👄😉 

如果 nodeSpan1 是使用 let 在 socket.onmessage 函式內部宣告的,它僅在該函式的範圍內有效。

換句話說,在 check_name() 函式中是無法訪問到 nodeSpan1 的。


這是 JavaScript 變數作用域的規則:

使用 let 或 const 宣告的變數只在它們所在的區塊(通常是花括號{ }內)中有效。


因此,在 check_name() 函式內部無法直接訪問 nodeSpan1,

除非您將它作為參數傳遞給 check_name() 函式或者在更廣泛的範圍內聲明 nodeSpan1。

// 接收到訊息時的事件處理
socket.onmessage = function(event) {
check_name(data.name, nodeSpan1);
//(確認名字name是否相同) 如果發表訊息的人是自己 就將訊息的顏色進行更改
};

下面是呼叫的函式  >>使用參數傳遞的方法

function check_name(dataname, nodeSpan1){
  let inputName = document.getElementById('inputName').value;
  if(dataname===inputName){
    nodeSpan1.style.color = 'blue'; // 如果是自己發表的文字 將它變成Blue顏色
  }
}


👄😉 

Flexbox(彈性盒子布局)是 CSS3 中的一種佈局模型,用於設計彈性且高度自適應的網頁佈局。

Flexbox 提供了簡單而強大的佈局方式,適用於建立一致、靈活且可擴展的網頁佈局,

能夠有效處理多種裝置和屏幕尺寸下的佈局需求。


display: flex; 用於將一個 HTML 元素設置為 Flex 容器,

這使得其內部的子元素可以使用 Flexbox 佈局模型。


justify-content: space-between; 是 Flex 容器的屬性之一,

用於指定 Flex 子元素在主軸上的對齊方式,

space-between 會在 Flex 容器的主軸上平均分配子元素之間的空間,

使得首尾元素分別位於容器的起始和結束處,而其餘的元素則均勻分布在中間。

// 將訊息顯示在畫面上
let messageBox = document.getElementById('messageBox');
let nodeDiv=document.createElement("div");
nodeDiv.style.display = "flex";  //運用Flexbox 佈局
nodeDiv.style.justifyContent = "space-between"; // 設置 flex 容器屬性,讓兩個元素(ex:span)可以分隔最開



👄😉 

可以將指定的 class 添加到新創建的 <span> 元素中。在 classList.add 方法中,

只需將您想要添加的 class 名稱傳遞給它,它就會將該 class 添加到元素中。

    let nodeSpan2=document.createElement("span");
    nodeSpan2.classList.add("timestamp");  // 添加 class 到 <span> 元素


👄😉 

達成即時時間動態顯示


// 更新時間
function updateClock() {
  // 獲取當前時間
  formattedTime = getTime()
  document.getElementById('realTimeClock').textContent = `Current Time: ${formattedTime}`;
}

setInterval // 每秒更新時間,它會以指定的時間間隔執行指定的函式,不會重新整理整個網頁,此例設定間隔為1秒。

// 每秒更新時間  //它會以指定的時間間隔執行指定的函式,不會重新整理整個網頁。
setInterval(updateClock, 1000);

// 網頁載入時先執行一次更新
updateClock();



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}`);
});


index.html

<!DOCTYPE html>
<html>
<head>
  <title>WebSocket Example</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>

  <div class="chat-container">
    <h1>WebSocket Chat Room</h1>
    <span class="user-name">name: empty</span>
 
    <!-- Message display area -->
    <div id="messageBox"></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">😊</button>
      <button id="heartButton" class="emoji-button">❤️</button>
    </div>
    <button id="text_btn" class="send-button">Send</button>

    <!-- Display area for showing real-time clock -->
    <div id="realTimeClock"></div>
  </div>
 
  <script src="front.js"></script>
</body>
</html>


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;
}


實作講解

假設有兩個人在聊天室裡,互相傳訊息,分別為lala和蓮花,藉由network的message,
可以藉由箭頭的方向,看出訊息的傳遞方向。


{"name":"lala","message":"我改了時間拿取方法❤️","time":"11:40:24"} 57 11:40:24.152
{"name":"lala","message":"我改了時間拿取方法❤️","time":"11:40:24"} 57 11:40:24.154
{"name":"蓮花","message":"我有看到唷~😊","time":"11:40:35"} 52 11:40:35.186
{"name":"蓮花","message":"好可愛歐!!!","time":"11:40:43"} 51 11:40:43.282
{"name":"lala","message":"我好喜歡愛心❤️符號~~ 好Q😊","time":"11:41:00"} 63 11:41:00.088
{"name":"lala","message":"我好喜歡愛心❤️符號~~ 好Q😊","time":"11:41:00"} 63 11:41:00.090


{"name":"lala","message":"我改了時間拿取方法❤️","time":"11:40:24"} 57 11:40:24.154
{"name":"蓮花","message":"我有看到唷~😊","time":"11:40:35"} 52 11:40:35.183
{"name":"蓮花","message":"我有看到唷~😊","time":"11:40:35"} 52 11:40:35.185
{"name":"蓮花","message":"好可愛歐!!!","time":"11:40:43"} 51 11:40:43.279
{"name":"蓮花","message":"好可愛歐!!!","time":"11:40:43"} 51 11:40:43.282
{"name":"lala","message":"我好喜歡愛心❤️符號~~ 好Q😊","time":"11:41:00"}



沒有留言:

張貼留言

喜歡我的文章嗎? 喜歡的話可以留言回應我喔! ^^