作業環境
python 3.8.6
Django 3.1.5
channels 3.0.3
はじめに
DjangoとChannelsを使ってチャットアプリを作っているのですが、テキストだけでなく画像もチャットで送れるようにしたいと思い、色々試して辿り着いた方法をここに記します。
Channelsの導入方法や設定などは記していません。
チャットアプリを作る際に参考にさせていただいたサイトはこの記事の最後にリンクを貼っています。
結論
画像をBase64形式(文字列)に変換すれば、テキストと同じように画像をやり取りできる。
作業
HTML
chat.html
...
<div id="messages"></div>
<form id="chat-form">
<input type="text" id="text-input">
<input type="file" id="image-input">
</form>
...
★JavaScript(画像をBase64に変換)
chat.js
...
let imageBase64 = ""; // Base64形式に変換した画像を代入する変数
const imageInput = document.getElementById("image-input");
imageInput.addEventListener('change', (e) => {
// 画像ファイルを取得し、画像データ(URL)を変数に代入
const imageFile = e.target.files[0];
const imageURL = window.URL.createObjectURL(imageFile);
// img要素を作成し、src属性に画像のURLを指定
const imageElement = new Image();
imageElement.src = imageURL;
imageElement.onload = function() {
// canvas要素を作成し、img要素を描画
const canvasElement = document.createElement('canvas');
canvasElement.width = imageElement.width;
canvasElement.height = imageElement.height;
const canvasContext = canvasElement.getContext('2d');
canvasContext.drawImage(imageElement, 0, 0);
// canvas要素をbase64形式に変換
imageBase64 = canvasElement.toDataURL("image/png");
};
});
...
- Base64形式に変換できるのは
canvas要素
に描画されている内容 - 画像を
img要素
にすることでcanvas要素
に描画できる
JavaScript(テキストと画像をwebsocketへ送信)
chat.js
...
const ws = new WebSocket( "ws://" + window.location.host + window.location.pathname );
const textInput = document.getElementById("text-input");
document.getElementById("chat-form").addEventListener("submit", (e) => {
// form要素としての元々の送信処理を停止
e.preventDefault();
// テキストを取得
const text = textInput.value;
// テキストも画像も空の場合は送信しない
if(text == "" && imageBase64 == ""){
return;
}
// テキストと画像(Base64)を送信
ws.send(
JSON.stringify({ "text": text, "image": imageBase64 })
);
// 入力フォームと画像情報を空にする
textInput.value = "";
imageInput.value = "";
imageBase64 = "";
});
...
python/websocket(受け取ったデータをチャット参加者へ送信)
consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer( AsyncWebsocketConsumer ):
...
# JavaScriptから送られてきたデータを整理
async def receive( self, text_data ):
text_data_json = json.loads( text_data )
...
text = text_data_json['text']
image = text_data_json['image']
data = {
'type': 'chat_message',
'text': text,
'image' : image,
}
await self.channel_layer.group_send( self.room_name, data )
# 整理したデータをチャット参加者のJavaScriptへ送る
async def chat_message( self, data ):
data_json = {
'text': data['text'],
'image': data['image'],
}
await self.send( text_data=json.dumps( data_json ) )
...
★JavaScript(データを受信して表示)
chat.js
...
const messages = document.getElementById("messages");
ws.onmessage = (event) => {
// 送られてきたデータを変数に格納
const data = JSON.parse(event.data);
// チャット内容をまとめるdiv要素を作成
const message = document.createElement('div');
// 送られてきたテキストをp要素としてmessageに組み込む
const messageText = document.createElement('p');
messageText.innerText = data["message"];
message.append(messageText);
// 送られてきた画像をimg要素としてmessageに組み込む
const messageImage = document.createElement('img');
messageImage.setAttribute('src', data["image"]);
message.append(messageImage);
// チャット内容を表示させる
messages.append(message);
};
...
- Base64形式の画像は、その文字列を
img要素
のsrc属性
に指定することで画像として表示する事ができる。
まとめ
画像URL
→img要素
→canvas要素
→Base64
→img要素
という複雑な流れを経て、画像を非同期通信で送る事ができた。