From 955e25f254cf8ec10941a978d5eab2adeb2d14cd Mon Sep 17 00:00:00 2001 From: Alena Batitskaia Date: Sat, 18 Jul 2026 17:05:31 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D0=B4=D0=B5=D0=BC=D0=BA=D1=83=20=D0=B2=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BA=D0=B5=20=D0=BF=D1=80=D0=BE=20WebSocket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/websocket/demos/example/index.html | 358 +++++++++++++++++++++----- js/websocket/index.md | 2 +- 2 files changed, 298 insertions(+), 62 deletions(-) diff --git a/js/websocket/demos/example/index.html b/js/websocket/demos/example/index.html index 06065de2ee..19b72ee2f5 100644 --- a/js/websocket/demos/example/index.html +++ b/js/websocket/demos/example/index.html @@ -36,9 +36,19 @@ max-width: 480px; display: flex; flex-direction: column; - gap: 20px; + gap: 16px; } + /* Строка с адресом сервера */ + + .server-url { + font-family: monospace; + font-size: 14px; + color: #FFD829; + } + + /* Индикатор состояния соединения */ + .status { display: flex; align-items: center; @@ -50,6 +60,7 @@ } .status-dot { + flex-shrink: 0; width: 10px; height: 10px; border-radius: 50%; @@ -84,11 +95,88 @@ font-size: 14px; } - .status-code { + .status-ready-state { font-family: monospace; + color: rgb(255 255 255 / 0.5); + } + + /* Панель рукопожатия. Не появляется и не исчезает — всегда на месте, + меняется только «активность»: яркая во время подключения, + приглушённая до и после него */ + + .handshake { + display: flex; + flex-direction: column; + gap: 10px; + padding: 14px 16px; + border: 1px solid rgb(255 255 255 / 0.12); + border-radius: 10px; + background-color: #282A2E; + transition: border-color 0.3s ease; + } + + .handshake[data-active="true"] { + border-color: rgb(255 216 41 / 0.35); + } + + .handshake-title { + font-size: 13px; + font-weight: 500; + color: rgb(255 255 255 / 0.4); + transition: color 0.3s ease; + } + + .handshake[data-active="true"] .handshake-title { color: #FFD829; } + .handshake-step { + display: flex; + flex-direction: column; + gap: 4px; + } + + .handshake-step-label { + font-size: 12px; + color: rgb(255 255 255 / 0.4); + } + + .handshake-step pre { + margin: 0; + padding: 10px 12px; + overflow-x: auto; + border-radius: 6px; + background-color: #18191C; + font-family: monospace; + font-size: 12px; + line-height: 1.5; + color: rgb(255 255 255 / 0.3); + transition: color 0.3s ease; + } + + /* Активный, ещё не полученный шаг — мигает, привлекает внимание */ + .handshake-step.is-pending pre { + color: rgb(255 216 41 / 0.5); + animation: blink 1s ease-in-out infinite; + } + + /* Шаг получен во время текущего (ещё живого) рукопожатия — зелёный */ + .handshake[data-active="true"] .handshake-step.is-received pre { + color: #58E28C; + } + + /* Рукопожатие когда-то прошло, но уже не актуально — просто приглушённый след */ + .handshake[data-active="false"] .handshake-step.is-received pre { + color: rgb(255 255 255 / 0.4); + } + + .handshake-note { + font-size: 12px; + color: rgb(255 255 255 / 0.4); + } + + /* Кнопки управления */ + .controls { display: flex; gap: 10px; @@ -115,6 +203,10 @@ outline-offset: -1px; } + input[type="text"]:disabled { + color: rgb(255 255 255 / 0.3); + } + button { padding: 10px 16px; border: none; @@ -143,8 +235,10 @@ color: #FFFFFF; } + /* Журнал событий */ + .log { - height: 220px; + height: 200px; padding: 14px 16px; overflow-y: auto; border: 1px solid rgb(255 255 255 / 0.12); @@ -155,6 +249,10 @@ line-height: 1.6; } + .log-placeholder { + color: rgb(255 255 255 / 0.3); + } + .log-entry { color: rgb(255 255 255 / 0.6); } @@ -171,6 +269,39 @@ color: #F498AD; } + .legend { + display: flex; + flex-wrap: wrap; + gap: 6px 16px; + font-size: 12px; + color: rgb(255 255 255 / 0.5); + } + + .legend-item { + display: flex; + align-items: center; + gap: 6px; + } + + .legend-dot { + flex-shrink: 0; + width: 8px; + height: 8px; + border-radius: 50%; + } + + .legend-dot.sent { + background-color: #FFD829; + } + + .legend-dot.received { + background-color: #58E28C; + } + + .legend-dot.error { + background-color: #F498AD; + } + @media (width <= 480px) { body { padding: 24px; @@ -190,9 +321,28 @@
-
+
wss://echo.websocket.org
+ +
- Не подключено + Не подключено + +
+ +
+
Рукопожатие: HTTP-запрос на переключение протокола
+ +
+ Клиент → сервер +
— ждём подключения —
+
+ +
+ Сервер → клиент +
— ждём подключения —
+
+ +
Значения заголовков иллюстративные: браузер формирует их сам и не отдаёт в JavaScript, поэтому увидеть настоящие можно только во вкладке «Сеть» инструментов разработчика.
@@ -205,108 +355,194 @@
-
+
+ Здесь появятся события соединения +
+ +
+ Отправлено клиентом + Получено от сервера + Ошибка или закрытие +
diff --git a/js/websocket/index.md b/js/websocket/index.md index c02c9b2e6b..37a290b66b 100644 --- a/js/websocket/index.md +++ b/js/websocket/index.md @@ -28,7 +28,7 @@ tags: `WebSocket` — браузерный API для постоянного соединения с сервером. Клиент и сервер обмениваются данными в реальном времени, клиенту не нужно постоянно отправлять HTTP-запросы. - +
- +
+ + 0 / 200 +
@@ -371,6 +392,7 @@ const closeBtn = document.getElementById('close-btn'); const sendBtn = document.getElementById('send-btn'); const messageInput = document.getElementById('message-input'); + const messageCounterEl = document.getElementById('message-counter'); const logEl = document.getElementById('log'); const handshakeEl = document.getElementById('handshake'); const handshakeRequestStepEl = document.getElementById('handshake-request-step'); @@ -529,6 +551,16 @@ } }) + messageInput.addEventListener('input', updateMessageCounter) + + function updateMessageCounter() { + const length = messageInput.value.length + const limit = Number(messageInput.maxLength) + + messageCounterEl.textContent = length + ' / ' + limit + messageCounterEl.classList.toggle('is-near-limit', length >= limit) + } + function sendMessage() { const text = messageInput.value.trim() @@ -539,6 +571,7 @@ socket.send(text) logMessage('Отправлено: ' + text, 'sent') messageInput.value = '' + updateMessageCounter() }