前面搭建了一个单人可用的简易画板,这次我们实现一个多人协作画板。代码库地址
主要用到的技术是 websocket 。因为websocket采取的方式是让所有客户端连接服务端,服务器将不同客户端发送给自己的消息进行转发或者广播。使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在开发方面,WebSocket API 也十分简单,我们只需要实例化 WebSocket,创建连接,然后服务端和客户端就可以相互发送和响应消息。
首先,我们创建客户端代码 client.ts。
export default class websocketManager{
static getInstance(): websocketManager {
if (websocketManager.instance == null) {
websocketManager.instance = new websocketManager();
}
return websocketManager.instance;
}
private static instance: any = null;
websocket = new WebSocket('ws://localhost:3000');
// 创建websocket链接
createWebSocket(drawFunc) {
this.websocket.onopen = function() {
console.log('开始链接')
}
this.websocket.onerror = (err) => {
console.log('websocket错误 ' + err)
// 需要重连
}
this.websocket.onclose = (err) => {
console.log('websocket 关闭 ' + err.reason)
}
this.websocket.onmessage = (event) => {
console.log('接收服务端返回的信息')
}
}
// 关闭websocket
closeWebSocket() {
this.websocket && this.websocket.close()
}
}
其次,我们基于nodejs建立下服务端代码 server.ts,用的 ws 这个库。
// 导入WebSocket模块:
const serverWebSocket = require('ws');
// 实例化:
const wss = new serverWebSocket.Server({
//端口号
port: 3000
});
wss.on('connection', function (ws) {
console.log('服务端连接');
ws.on('message', function (message) {
ws.send(message, (err) => {
if (err) {
console.log(`连接错误: ${err}`);
}
});
})
});
运行下node ./webSocket/server.ts
, 可以看到控制台和终端都响应了连接。
客户端new了一个websocket对象后,会向服务器对应端口发起一个get请求。这里绑定的是3000端口,默认情况下,websocket使用80端口。后续客户端和服务端会在这个预定的端口上进行通信。
请求报文中的 upgrade 字段 是告诉服务端需要将通信协议切换到websocket,如果服务端支持websocket协议,那么它就会将请求报文中的Sec-WebSocket-Key解析出来,然后进行相应的拼接加密编码,将最后的结果作为 Sec-WebSocket-Accept 的值返回给客户端,并将自己的通信协议切换到websocket,返回状态码101。
以上过程都是利用http通信完成的,称之为websocket协议握手。握手之后,客户端和服务端就建立了websocket连接,以后的通信走的都是websocket协议。
“Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的密文,要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答,否则客户端会抛出“Error during WebSocket handshake”错误,并关闭连接。
建立连接之后,我们就可以进行数据传输了,websocket提供两种数据传输:文本数据和二进制数据。
回归到画板,我们开多个窗口来模拟下,每个链接后面我们拼一个用户id。然后一个用户在画的时候,每当笔抬起,就发送一次send请求,修改下绘制函数,这样其他用户能实时看到。服务端新增一个保存当前房间的大对象,里面是各个用户id下的绘图数据。
// client.ts add
this.websocket.onmessage = (event) => {
drawFunc(JSON.parse(event.data))
}
sendMessage(value) {
this.websocket.send(`${JSON.stringify(value)}`)
}
// pointer.ts add
function drawAll(userPathData) {
let canvasDom: any = document.getElementById('drawCanvas');
let curCtx = canvasDom!.getContext('2d');
let rect = canvasDom!.getBoundingClientRect();
curCtx.clearRect(rect.x, rect.y, rect.width, rect.height);
for (const key in userPathData) {
if (Object.prototype.hasOwnProperty.call(userPathData, key)) {
const pathData = userPathData[key];
pathData.map(item => {
if (Object.prototype.toString.call(item) === '[object Array]') {
item.map(info => draw(info, curCtx))
} else {
flowDraw(item, curCtx)
}
})
}
}
}
// 撤销函数更改
function undo() {
pathData.pop();
websocketManager.getInstance().sendMessage(pathData)
// let canvasDom: any = document.getElementById('drawCanvas');
// let curCtx = canvasDom!.getContext('2d');
// let rect = canvasDom!.getBoundingClientRect();
// curCtx.clearRect(rect.x, rect.y, rect.width, rect.height);
// pathData.map(item => {
// if (Object.prototype.toString.call(item) === '[object Array]') {
// item.map(info => draw(info, curCtx))
// } else {
// flowDraw(item, curCtx)
// }
// })
}
// 监听鼠标放开函数中取消绘制函数
function handleMouseMove(event) {
if (mouseButtonDown && !config.flowType) {
let singleData = {beginX: lastPt.x, beginY: lastPt.y, lastX: event.pageX, lastY: event.pageY, strokeStyle: config.strokeStyle, lineWidth: config.lineWidth, drawType: config.drawType, flowType: config.flowType};
singlePathData.push(singleData)
// draw(singleData)
lastPt = {
x: event.pageX,
y: event.pageY
}
}
if (mouseButtonDown && config.flowType) {
let flowPathData = {beginX: flowLastPt.x, beginY: flowLastPt.y, lastX: event.pageX, lastY: event.pageY, strokeStyle: config.strokeStyle, lineWidth: config.lineWidth, drawType: config.drawType, flowType: config.flowType};
tempDomDraw(flowPathData)
}
}
// useEffect函数中增加websocket逻辑
useEffect(() => {
...
websocketManager.getInstance().createWebSocket(drawAll);
return () => {
websocketManager.getInstance().closeWebSocket()
}
}, [])
// server.ts add
let allData = {};
wss.on('connection', function (ws) {
console.log('服务端连接');
ws.on('message', function (message) {
const {userID, pathData} = JSON.parse(message);
allData[userID] = pathData;
ws.send(JSON.stringify(allData), (err) => {
if (err) {
console.log(`连接错误: ${err}`);
}
});
})
});
Navigator.share()
方法通过调用本机的共享机制。是 Web Share API 的一部分。不过这是一个实验中的功能,浏览器兼容性不是很好。语法很简单。
/*
data可用选项包括:
url: 要共享的 URL( USVString )
text: 要共享的文本( USVString )
title: 要共享的标题( USVString)
files: 要共享的文件(“FrozenArray”)
*/
const data = {
title: document.title,
text: '简易画板分享链接',
url: document.location.href
}
const sharePromise = window.navigator.share(data);
接下来我们共享下图片。share API 的files参数需要接收 file 格式的数组,其次生成file的构造函数方法,它接收的是UTF-8 格式的编码(一种针对Unicode的可变长度字符编码)格式的数组。但canvas.toDataURL("image/png")
生成的是Data URL,由四个部分组成:前缀 (data:)、指示数据类型的 MIME 类型、Base64编码标记以及数据本身。所以我们需要将base64编码的数据转化成UTF-8再转化成file。
data URL 也是 URL,网上有base64转化成UTF-8用的是decodeURI。但亲测这个方法会报错,原因是canvas生成的data URI相对来说很长,又经过了base64的编码,一些换行符、制表符、空格的格式化会有问题。
/* file 构造函数
`bits: 一个包含ArrayBuffer,ArrayBufferView,Blob,或者 DOMString 对象的 Array
*/
let file = new window.File([bits], filename[, options]);
// 生成canvas图片地址
let imgUrl:any = canvas.toDataURL("image/png");
// data:image/png;base64 字符需要单独提出来
var arr = imgUrl.split(',');
// 拿到图片格式 image/png
var mime = arr[0].match(/:(.*?);/)[1];
var suffix = mime.split('/')[1];
// 解析后面的文件流
var bstr = atob(arr[1]);
var n = bstr.length;
// 初始化Uint8Array数组
var u8arr = new Uint8Array(n);
while(n--) {
// 对应位置放上相应字符的unicode编码
u8arr[n] = bstr.charCodeAt(n);
}
我们把得到的file文件读取成路径形式,和咱们一开始的toDataURL得到的路径是一样的。
var reader = new FileReader();
reader.readAsDataURL(imgFile);
reader.onload = function() {
console.log(this.result == imgUrl) // true
}
var reader = new FileReader();
reader.readAsDataURL(imgFile);
reader.onload = function() {
console.log(this.result == imgUrl) // true
}
MDN websocket
ws
MDN share
本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/eV2aQs-Z_Iyr-uFJBFcalQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。