当前位置: 首页 > news >正文

开福区网站建设中网络整合营销

开福区网站建设中,网络整合营销,给自己企业怎么做网站,网站ftp地址查询基于 yjs 实现实时在线多人协作的绘画功能 支持多客户端实时共享编辑自动同步&#xff0c;离线支持自动合并&#xff0c;自动冲突处理 1. 客户端代码&#xff08;基于Vue3&#xff09; 实现绘画功能 <template><div style"{width: 100vw; height: 100vh; over…

基于 yjs 实现实时在线多人协作的绘画功能

在这里插入图片描述

  • 支持多客户端实时共享编辑
  • 自动同步,离线支持
  • 自动合并,自动冲突处理

1. 客户端代码(基于Vue3)

实现绘画功能

<template><div style="{width: 100vw; height: 100vh; overflow: hidden;}"><canvas ref="canvasRef" style="{border: solid 1px red;}" @mousedown="startDrawing" @mousemove="draw"@mouseup="stopDrawing" @mouseleave="stopDrawing"></canvas></div><div style="position: absolute; bottom: 10px; display: flex; justify-content: center; height: 40px; width: 100vw;"><div style="width: 100px; height: 40px; display: flex; align-items: center; justify-content: center; color: white;":style="{ backgroundColor: color }"><span>当前颜色</span></div><Button style="width: 100px; height: 40px; margin-left: 10px;" @click="switchMode(DrawType.Point)">画点</Button><Button style="width: 100px; height: 40px; margin-left: 10px;" @click="switchMode(DrawType.Line)">直线</Button><Button style="width: 100px; height: 40px; margin-left: 10px;" @click="switchMode(DrawType.Draw)">涂鸦</Button><Button style="width: 100px; height: 40px; margin-left: 10px;" @click="clearCanvas">清除</Button></div>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Button, Modal, Input } from "ant-design-vue";
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
import { v4 as uuidv4 } from 'uuid';const canvasRef = ref<null | HTMLCanvasElement>(null);
const ctx = ref<CanvasRenderingContext2D | null>(null);
const drawing = ref(false);
const color = ref<string>("black");class Point {x: number = 0.0;y: number = 0.0;
}enum DrawType {None,Point,Line,Draw,
}const colors = ["#FF5733", "#33FF57", "#5733FF", "#FF33A2", "#A2FF33","#33A2FF", "#FF33C2", "#C2FF33", "#33C2FF", "#FF3362","#6233FF", "#FF336B", "#6BFF33", "#33FFA8", "#A833FF","#33FFAA", "#AA33FF", "#FFAA33", "#33FF8C", "#8C33FF"
];// 随机选择一个颜色
function getRandomColor() {const randomIndex = Math.floor(Math.random() * colors.length);return colors[randomIndex];
}class DrawElementProp {color: string = "black";
}class DrawElement {id: string = "";version: string = "";type: DrawType = DrawType.None;geometry: Point[] = [];properties: DrawElementProp = new DrawElementProp();
}// 选择的绘画模式
const drawMode = ref<DrawType>(DrawType.Draw);
// 定义变量来跟踪第一个点的坐标和鼠标是否按下
const point = ref<Point | null>(null);// 创建 ydoc, websocketProvider
const ydoc = new Y.Doc();// 创建一个 Yjs Map,用于存储绘图数据
const drawingData = ydoc.getMap<DrawElement>('drawingData');drawingData.observe(event => {if (ctx.value && canvasRef.value) {const context = ctx.value!// 清空 Canvascontext.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);// 遍历绘图数据,绘制点、路径等drawingData.forEach((data: DrawElement) => {if (data.type == DrawType.Point) {context.fillStyle = data.properties.color; // 设置点的填充颜色context.strokeStyle = data.properties.color; // 设置点的边框颜色context.beginPath();context.moveTo(data.geometry[0].x, data.geometry[0].y);context.arc(data.geometry[0].x, data.geometry[0].y, 2.5, 0, Math.PI * 2); // 创建一个圆形路径context.fill(); // 填充路径,形成圆点context.closePath();} else if (data.type == DrawType.Line) {context.fillStyle = data.properties.color; // 设置点的填充颜色context.strokeStyle = data.properties.color; // 设置点的边框颜色context.beginPath();// 遍历所有点data.geometry.forEach((p: Point, index: number) => {if (index == 0) {context.moveTo(p.x, p.y);context.fillRect(p.x, p.y, 5, 5);} else {context.lineTo(p.x, p.y);context.stroke();context.fillRect(p.x, p.y, 5, 5);}})} else if (data.type == DrawType.Draw) {context.fillStyle = data.properties.color; // 设置点的填充颜色context.strokeStyle = data.properties.color; // 设置点的边框颜色context.beginPath();// 遍历所有点data.geometry.forEach((p: Point, index: number) => {if (index == 0) {context.moveTo(p.x, p.y);} else {context.lineTo(p.x, p.y);context.stroke();}})} else {console.log("Invalid draw data", data)}})}
})const websocketProvider = new WebsocketProvider('ws://localhost:8080/ws', 'demo', ydoc
)onMounted(() => {if (canvasRef.value) {// 随机选择一种颜色color.value = getRandomColor()canvasRef.value.height = window.innerHeight - 10;canvasRef.value.width = window.innerWidth;const context = canvasRef.value.getContext('2d');if (context) {ctx.value = context;context.lineWidth = 5;context.fillStyle = color.value; // 设置点的填充颜色context.strokeStyle = color.value; // 设置点的边框颜色context.lineJoin = 'round';}}window.addEventListener('keydown', handleKeyDown);
});const handleSaveUserName = () => {if (userName.value) {modalOpen.value = false;}
}const handleKeyDown = (event: KeyboardEvent) => {if (event.key === 'Escape') {// 重置编号if (currentID.value) {currentID.value = "";}// 结束路径和绘画if (drawing.value && ctx.value) {ctx.value.closePath();drawing.value = false;}}
}const switchMode = (mode: DrawType) => {// 重置状态currentID.value = "";drawing.value = false;drawMode.value = mode;point.value = null
}// 记录当前路径的编号
const currentID = ref<string>("");const startDrawing = (e: any) => {// 获取当前时间的秒级时间戳const timestampInSeconds = Math.floor(Date.now() / 1000);// 将秒级时间戳转换为字符串const version = timestampInSeconds.toString();if (ctx.value) {if (drawMode.value === DrawType.Point) {// 分配编号currentID.value = uuidv4();let point: DrawElement = {id: currentID.value,version: version,type: DrawType.Point,geometry: [{ x: e.clientX, y: e.clientY }],properties: { color: color.value }}drawingData.set(currentID.value, point);// 重置编号currentID.value = ""return}if (drawMode.value === DrawType.Line) {// 分配编号if (currentID.value == "") {currentID.value = uuidv4();}// 没有正在绘画if (!drawing.value) {// 开始绘画drawing.value = true;}// 获取当前线的信息,如果没有则创建let line: DrawElement | undefined = drawingData.get(currentID.value)if (line) {line.version = version;line.geometry.push({ x: e.clientX, y: e.clientY });} else {line = {id: currentID.value,version: version,type: DrawType.Line,geometry: [{ x: e.clientX, y: e.clientY }],properties: { color: color.value }}}drawingData.set(currentID.value, line);return}if (drawMode.value === DrawType.Draw) {// 分配编号if (currentID.value == "") {currentID.value = uuidv4();let path: DrawElement = {id: currentID.value,version: version,type: DrawType.Draw,geometry: [{ x: e.clientX, y: e.clientY }],properties: { color: color.value }}drawingData.set(currentID.value, path);}// 没有正在绘画if (!drawing.value) {// 开始绘画drawing.value = true;}}}
};const draw = (e: any) => {if (drawing.value && ctx.value) {if (drawMode.value === DrawType.Draw) {// 获取当前线的信息,如果没有则创建let path: DrawElement | undefined = drawingData.get(currentID.value)if (path) {path.geometry.push({ x: e.clientX, y: e.clientY });drawingData.set(currentID.value, path);return}console.log("error: not found path", currentID.value)}}
};const stopDrawing = () => {if (drawing.value && ctx.value) {if (drawMode.value === DrawType.Draw) {// 鼠标放开时,关闭当前路径绘画currentID.value = "";drawing.value = false;}}
};const clearCanvas = () => {if (canvasRef.value && ctx.value) {ctx.value.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);drawingData.clear();}
};
</script>

2. 服务端代码

基于 yjs 的多人协助其实只需要前端,使用 y-webtrc 也可以实现数据共享,但是为了增加一些功能,如权限控制、数据库存储等,需要使用服务端;不考虑复杂功能,我们使用 websocket 进行客户端之间的通信,所以服务端也很简单,实现了 websocket 服务端的功能即可

  1. 可以使用 yjs 推荐的 y-websocket 的 nodejs 服务
HOST=localhost PORT=8080 npx y-websocket
  1. 也可以自己实现一个 websocket 服务端,这里选择用 golang 实现一个
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.package mainimport ("net/http""github.com/olahol/melody"
)func main() {m := melody.New()m.Config.MessageBufferSize = 65536m.Config.MaxMessageSize = 65536m.Upgrader.CheckOrigin = func(r *http.Request) bool { return true }http.HandleFunc("/ws/demo", func(w http.ResponseWriter, r *http.Request) {m.HandleRequest(w, r)})// 不重要m.HandleConnect(func(session *melody.Session) {println("connect")})// 不重要m.HandleDisconnect(func(session *melody.Session) {println("disconnect")})// 不重要m.HandleClose(func(session *melody.Session, i int, s string) error {println("close")return nil})// 不重要m.HandleError(func(session *melody.Session, err error) {println("error", err.Error())})// 不重要m.HandleMessage(func(s *melody.Session, msg []byte) {m.Broadcast(msg)})// 主要内容,对 yjs doc 的改动内容进行广播到其他客户端m.HandleMessageBinary(func(s *melody.Session, msg []byte) {m.BroadcastBinary(msg)})http.ListenAndServe(":8080", nil)
}

3. 特殊的 nodejs 客户端,用于保存数据

yjs 在客户端上进行文档冲突处理以及合并,每个客户端都维护着自己的文档,为了使数据能够持久化到文件或者数据库中,需要使用一个客户端作为基准,并且这个客户端对文档应该是只读不改的,运行在服务器上;基于以上考量,我们选择使用 nodejs 实现一个客户端运行在服务器上(如果选用golang的话,没有 yjs 实现的方法可以解析 ydoc 的数据)

nodejs 客户端,只需要连接上 y-websocket 并且当文档更新时,保存数据


const fs = require('fs');
const Y = require('yjs');
const { WebsocketProvider } = require('y-websocket');
const WebSocket = require('websocket').w3cwebsocket;// 创建 Yjs 文档
const ydoc = new Y.Doc();const websocketProvider = new WebsocketProvider('ws://localhost:8080/ws', 'demo', ydoc, {WebSocketPolyfill: WebSocket,
})const drawingData = ydoc.getMap('drawingData');// 当文档发生更改时,将更改内容打印出来
ydoc.on('update', () => {console.log('Document updated', ydoc.clientID);const document = [];drawingData.forEach((data) => {document.push(data)})// 要写入的文件路径const filePath = 'doc/data.json';const fileContent = JSON.stringify(document);// 使用 fs.writeFile 方法写入文件fs.writeFile(filePath, fileContent, (err) => {if (err) {console.error('save error', err);} else {console.log('document saved');}});
});
http://www.ds6.com.cn/news/95598.html

相关文章:

  • 杭州响应式网站制作免费网站在线客服软件
  • 大气精美网站设计工作室织梦模板(附赠精美织梦后台模板)注册公司网站
  • 新手可以做网站营运吗广州网站排名专业乐云seo
  • 河南网站建设详细流程做网络销售感觉自己是骗子
  • 公司年前做网站好处企业网站模板
  • ie浏览器手机版下载太原网站seo
  • 韩国代购网站开发关键词优化是什么意思?
  • 冠县快搜网站建设有限公司营销推广费用预算表
  • 做地产网站哪家好专注于seo顾问
  • 公司注册代理免费宁波seo软件免费课程
  • 企业英文网站青岛网站建设方案
  • 江苏茂盛建设有限公司网站国际热点新闻
  • 网站设计制作开发小时seo百度关键词点击器
  • 美国做礼品的网站别人恶意点击我们竞价网站
  • 购物网站开发用什么软件关键洞察力
  • 哈尔滨网站建设排免费百度广告怎么投放
  • 公司做网站需要提供什么条件石家庄网站建设方案优化
  • 做网站外链需要多少钱百度账号客服人工电话
  • 建设主题网站的顺序是什么意思东莞建设网
  • 做庭院景观的那个网站推广好福州seo按天付费
  • 建设网站要注册公司吗百度一下你就知道官网下载安装
  • 厦门企业网站制作比较好的网络推广平台
  • 用手机可以做网站app注册拉新平台
  • 网站开发法律可行性模板建站价格
  • 山西公司怎么做网站如何做运营推广
  • 广州市人民政府新闻办公室发布会seo搜索引擎优化薪酬
  • 加油站顶棚网架价多少钱一平网店推广方法有哪些
  • 免费网站建站排行榜网站设计方案
  • 如何制作自己的网站模版seo站长综合查询工具
  • 党史网站建设重要性百度数据网站