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

济南多语言网站建设在线做3d交互的网站

济南多语言网站建设,在线做3d交互的网站,一站式营销型网站建设服务,安徽建设工程信息网上查询基于 yjs 实现实时在线多人协作的绘画功能 支持多客户端实时共享编辑自动同步#xff0c;离线支持自动合并#xff0c;自动冲突处理 1. 客户端代码#xff08;基于Vue3#xff09; 实现绘画功能 templatediv style{width: 100vw; height: 100vh; over…基于 yjs 实现实时在线多人协作的绘画功能 支持多客户端实时共享编辑自动同步离线支持自动合并自动冲突处理 1. 客户端代码基于Vue3 实现绘画功能 templatediv style{width: 100vw; height: 100vh; overflow: hidden;}canvas refcanvasRef style{border: solid 1px red;} mousedownstartDrawing mousemovedrawmouseupstopDrawing mouseleavestopDrawing/canvas/divdiv styleposition: absolute; bottom: 10px; display: flex; justify-content: center; height: 40px; width: 100vw;div stylewidth: 100px; height: 40px; display: flex; align-items: center; justify-content: center; color: white;:style{ backgroundColor: color }span当前颜色/span/divButton stylewidth: 100px; height: 40px; margin-left: 10px; clickswitchMode(DrawType.Point)画点/ButtonButton stylewidth: 100px; height: 40px; margin-left: 10px; clickswitchMode(DrawType.Line)直线/ButtonButton stylewidth: 100px; height: 40px; margin-left: 10px; clickswitchMode(DrawType.Draw)涂鸦/ButtonButton stylewidth: 100px; height: 40px; margin-left: 10px; clickclearCanvas清除/Button/div /templatescript setup langts 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 refnull | HTMLCanvasElement(null); const ctx refCanvasRenderingContext2D | null(null); const drawing ref(false); const color refstring(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 refDrawType(DrawType.Draw); // 定义变量来跟踪第一个点的坐标和鼠标是否按下 const point refPoint | null(null);// 创建 ydoc, websocketProvider const ydoc new Y.Doc();// 创建一个 Yjs Map用于存储绘图数据 const drawingData ydoc.getMapDrawElement(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 refstring();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 服务端的功能即可 可以使用 yjs 推荐的 y-websocket 的 nodejs 服务 HOSTlocalhost PORT8080 npx y-websocket也可以自己实现一个 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/httpgithub.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.hkea.cn/news/14439806/

相关文章:

  • 杭州 网站建设公司双语对照网站
  • 鑫迪一键建站系统建设网站的安全性
  • 上海建设资质审批网站国际网站 建设
  • 专业建设专题网站时代强个人网站
  • 广西seo网站推广mvc5网站开发实战详解
  • c 做网站开发网络营销app有哪些
  • 外流网站建设微信网站服务器要求
  • 西宁网站制作哪家好如何分析一个网站
  • 网站开发会计科目手机网站静态模板
  • 查找北京国互网网站建设企业系统培训平台
  • 做汽车的网站编辑做网站建设公司怎么选
  • 西安做网站建设的住房与城乡建设部网站特色小镇
  • 网站建设的财务计划django做的电子商务网站
  • 搭建网站服务器多少钱大连金州旅游景点有哪些
  • 网站建设方案200字成都seo优化推广
  • 如何关联网站与网站wordpress开发解析
  • 怎么做网站框架设计培训
  • 广东企业网站建设出售自己的网站
  • 网站的运营费用吗如何做切片网站
  • 电子商务如何做网站销售百度收录的网站
  • php网站开发实例教程案例网站销户说明
  • 网站建设技术分类a5建站
  • 杭州网站制作模板朔州如何做百度的网站
  • 做代练去什么网站安全康保网站建设
  • 网站开发需要哪些基础技术电子商务网站规划的内容
  • dw网站引导页怎么做免费app开发平台
  • 进行公司网站建设方案永久免费手机建站平台
  • c2c商城网站开发苏州模板建站平台
  • 营销网站设计方案邢台交友123
  • 网站seo优化怎么做怎么更换网站logo