// pages/sketchpad.js // 由于 currentBoard backgroundPic 需要在页面上使用 因此需要用setData // 其余data中的数据 均未与页面同步 let tools = require('tools.js'); const ImgLoader = require('img-loader.js'); const FileCache = require('FileCache.js'); Component({ properties: { addData: { type: Object, observer: function (newVal, oldVal) { if (oldVal) { this.addData(newVal); } } }, // 添加data user: { type: String, value: 'temp' }, // 用户名 canDraw: { type: Boolean, value: false }, // 能否画画 height: { type: Number, value: 211, observer: function (newVal, oldVal) { this.setData({ height: newVal }); this.data.needDraw = true; } }, // 显示高度 width: { type: Number, value: 375, observer: function (newVal, oldVal) { this.setData({ width: newVal }); this.data.needDraw = true; } }, // 显示宽度 horizontal: { type: Boolean, value: true, observer: function (newVal, oldVal) { this.setData({ horizontal: newVal }); this.data.needDraw = true; } } // 是否水平 }, data: { width: 375, height: 211, ctx: {}, // ctx drawInterval: 0, // 画画计时器 sendInterval: 0, // 发消息计时器 sendData: [], // 要发送的消息 needDraw: true, // 是否需要重绘 data: {}, // 全部数据 userData: {}, // 用户维度的数据 backgroundPic: { type: Object, value: {}, observer: function (newVal, oldVal) {} }, // 背景图 backgroundColor: '#ffffff', currentBoard: '#DEFAULT', // 当前画板 boardList: ['#DEFAULT'], // 画板列表 seq: 0, // seq color: 4278190335, // 默认颜色 thin: 100, // 默认画笔宽度 horizontal: true, naturalWidth: 0, naturalHeight: 0, imgStyle: '', maxSeq: 0 }, ready: function () { this.init(); this.imgLoader = new ImgLoader(this); // this.fileCache = new FileCache(); }, detached() { clearInterval(this.data.sendInterval); clearInterval(this.data.drawInterval); }, methods: { findLine(boardId, user, action) { var userData = this.data.userData[boardId][user]; var line; for (let i = 0, len = userData.length; i < len; i++) { const element = userData[i]; if (element.type === 'line' && element.belongSeq == action.belongSeq) { // 并是同一根线条 line = element; break; } } return line; }, addData: function (data) { if (!data.value) { return; } let user = data.value.operator; let actions = data.value.actions; let boardId = data.value.boardId; if (!this.data.userData[boardId]) { this.initBoard(boardId); } if (!this.data.userData[boardId][user]) { this.data.userData[boardId][user] = []; } let length = this.data.userData[boardId][user].length; actions.forEach((action) => { action.color && (action.color = tools.dealColor(action.color)); if (action.action == 1) { // let line = new tools.Line(user, action); // line.show = true; // this.data.userData[boardId][user].push(line); // this.data.data[boardId].push(line); // length++; var line; // 找到这条线了,这种情况是action = 2 || 3 先收到的情况 if (line = this.findLine(boardId, user, action)) { line.color = action.color; line.thin = action.thin; line.show = true; line.lines.push({ x: action.x, y: action.y, belongSeq: action.belongSeq, seq: action.seq }); line.sort(); line.setBorder(action.x, action.y); } else { // 找不到这条线, 就新增 // 开始画线 line = new tools.Line(user, action); line.show = true; this.data.userData[boardId][user].push(line); this.data.data[boardId].push(line); } } else if (action.action == 2 || action.action == 3) { // if (length == 0) { // return; // } // let line = this.data.userData[boardId][user][length - 1]; // line.lines.push({ // x: action.x, // y: action.y, // seq: action.seq // }); // line.setBorder(action.x, action.y); // if (action.action == 3) { // line.endSeq = action.seq; // } var line; if (line = this.findLine(boardId, user, action)) { // 找到了这条线,说明消息action=1先到 line.lines.push({ x: action.x, y: action.y, belongSeq: action.belongSeq, seq: action.seq }); line.sort(); line.setBorder(action.x, action.y); } else { // 没找着,则说明action=1还没有到 // 开始画线 line = new tools.Line(user, action); this.data.userData[boardId][user].push(line); this.data.data[boardId].push(line); } if (action.action == 3) { line.endSeq = action.seq; } } else if (action.action == 8) { if (!action.lines) return; action.lines.forEach((line) => { if (!this.data.userData[boardId][line.uid]) return; for (var i = 0; i < this.data.userData[boardId][line.uid].length; i++) { var item = this.data.userData[boardId][line.uid][i]; if (item.type == 'line' && item.startSeq == line.seq) { item.show = action.display ? true : false; } else if (item.type == 'graph' && item.startPoint.seq == line.seq) { item.show = action.display ? true : false; } } }); } else if (action.action == 10 || action.action == 11 || action.action == 12) { let list = { 10: 'line', 11: 'oval', 12: 'rect' }; action.graph = list[action.action]; action.beginPoint.x = action.beginPoint.x; action.endPoint.x = action.endPoint.x; action.beginPoint.y = action.beginPoint.y; action.endPoint.y = action.endPoint.y; action.solid = action.fillRect ? true : false; action.show = true; var graph = new tools.Graph(user, action); this.data.userData[boardId][user].push(graph); this.data.data[boardId].push(graph); length++; } else if (action.action == 101) { this.clear(); } else if (action.action == 201) { this.setBackgroundPic(boardId, action.url); } else if (action.action == 202) { this.cancelBackgroundPic(boardId); } else if (action.action == 401) { if (action.seq * 1 > this.data.maxSeq) { this.setData({ maxSeq: action.seq }, () => { let toBoardId = action.toBoardId; if (!this.data.data[toBoardId]) { this.initBoard(toBoardId); } action.deleteBoards.forEach((deleteBoardId) => { this.removeBoard(deleteBoardId); }) this.setData({ currentBoard: boardId }) this.data.needDraw = true; }); } else { // seq小于maxSeq的说明是消息乱序收到的,直接丢弃 // console.log('===401 乱序 seq:', action.seq); } } }); this.data.needDraw = true; }, updateCurrentBoard(currentBoard) { this.data.currentBoard = currentBoard; }, addHistory: function () {}, // 这版暂无历史消息 setBackgroundPic: function (boardId, url) { let self = this; let temp = {}; temp.url = url; temp.show = true; if (this.data.currentBoard == boardId) { // wx.showLoading({title: '图片加载中'}); // 因为提示在整体UI的中心,randewang 建议屏蔽不要提示 } // this.fileCache.load(url, function (res) { // if (res.code) { // console.error('图片加载失败:', res.errMsg); // return; // } // console.log('图片加载完成', res.tmpFilePath); // temp[boardId].url = res.tmpFilePath; // self.setData({backgroundPic: temp}); // }) this.imgLoader.load(url, (err, data) => { console.log('图片加载完成', err, data.src) //不管成功或者失败都都加入进去,缓存的失败不一定代表真正的失败 self.data.backgroundPic[boardId] = temp; this.setData({ backgroundPic: self.data.backgroundPic }); }); wx.hideLoading(); this.data.needDraw = true; }, cancelBackgroundPic: function (boardId) { let temp = Object.assign({}, this.data.backgroundPic); temp[boardId].show = false; this.setData({ backgroundPic: temp }); this.data.needDraw = true; }, initBoard: function (boardId) { this.data.userData[boardId] = {}; this.data.userData[boardId][this.properties.user] = []; this.data.data[boardId] = []; let temp = Object.assign({}, this.data.backgroundPic); temp[boardId] = { url: '', user: this.properties.user, show: true }; this.setData({ backgroundPic: temp }); this.data.boardList.push(boardId); }, removeBoard: function (boardId) { delete this.data.userData[boardId]; delete this.data.data[boardId]; if (this.data.boardList.indexOf(boardId) > -1) { this.data.boardList.splice(this.data.boardList.indexOf(boardId), 1); } }, getEventLocation: function (event) { let result = { x: 0, y: 0 } result.x = event.changedTouches[0]['x']; result.y = event.changedTouches[0]['y']; if (this.data.horizontal) { // 要转一下x y result.x = parseInt(result.x / this.data.width * 10000); // 16:9 result.y = parseInt(result.y / this.data.height * 10000); } else { result.x = parseInt(result.x / this.data.height * 10000); // 16:9 result.y = parseInt(result.y / this.data.width * 10000); } console.log('width', this.data.width, this.data.height); return result; }, draw: function () { var self = this; // 先清空画板 //this.data.ctx.clearRect(0, 0, this.data.width, this.data.height); if (this.data.backgroundPic[this.data.currentBoard].url && this.data.backgroundPic[this.data.currentBoard].show) { self.updateImgStyle(); // 这里canvas就不画背景了 this.data.ctx.draw(); } else { this.data.ctx.setFillStyle(this.data.backgroundColor); this.data.ctx.fillRect(0, 0, this.data.width, this.data.height); this.data.ctx.draw(); } // 遍历data this.data.data[this.data.currentBoard].forEach((item) => { if (item.type == 'line' && item.show) { let oldx = 0, oldy = 0; if (this.data.horizontal) { oldx = item.lines[0].x / 10000 * this.data.width oldy = item.lines[0].y / 10000 * this.data.height; } else { oldx = (10000 - item.lines[0].y) / 10000 * this.data.height; oldy = item.lines[0].x / 10000 * this.data.width; } item.lines.forEach((lineItem) => { // 按照上次的位置和这次的位置画线 this.data.ctx.beginPath(); if (!this.data.horizontal) { // 垂直方向 this.data.ctx.moveTo(oldx, oldy); } else { this.data.ctx.moveTo(oldx, oldy); } let tempx = 0, tempy = 0; if (this.data.horizontal) { tempx = lineItem.x / 10000 * this.data.width; tempy = lineItem.y / 10000 * this.data.height; } else { tempx = (10000 - lineItem.y) / 10000 * this.data.height; tempy = lineItem.x / 10000 * this.data.width; } // if (this.data.horizontal) { // this.data.ctx.lineTo(tempx, tempy); // } else { // this.data.ctx.lineTo(tempx, tempy); // } this.data.ctx.lineTo(tempx, tempy); this.data.ctx.setStrokeStyle(item.color); let thin = this.data.horizontal ? item.thin / 10000 * this.data.height : item.thin / 10000 * this.data.width; this.data.ctx.setLineWidth(thin); this.data.ctx.setLineCap("round"); this.data.ctx.stroke(); // 更新参数 oldx = tempx; oldy = tempy; }); } else if (item.type == 'graph' && item.show) { // let startX = this.data.horizontal ? // item.startPoint.x / 10000 * this.data.width : // ((10000 - item.startPoint.x) / 10000 * this.data.width); // let startY = item.startPoint.y / 10000 * this.data.height; // let endX = this.data.horizontal ? // item.endPoint.x / 10000 * this.data.width : // ((10000 - item.endPoint.x) / 10000 * this.data.width); // let endY = item.endPoint.y / 10000 * this.data.height; let startX = 0, startY = 0; let endX = 0, endY = 0; if (this.data.horizontal) { startX = item.startPoint.x / 10000 * this.data.width; startY = item.startPoint.y / 10000 * this.data.height; endX = item.endPoint.x / 10000 * this.data.width; endY = item.endPoint.y / 10000 * this.data.height; } else { startX = (10000 - item.startPoint.y) / 10000 * this.data.height; startY = item.startPoint.x / 10000 * this.data.width; endX = (10000 - item.endPoint.y) / 10000 * this.data.height; endY = item.endPoint.x / 10000 * this.data.width; } if (item.graph == 'line') { // 画直线 this.data.ctx.beginPath(); // if (!this.data.horizontal) { // this.data.ctx.moveTo(startY, startX); // this.data.ctx.lineTo(endY, endX); // } else { // this.data.ctx.moveTo(startX, startY); // this.data.ctx.lineTo(endX, endY); // } this.data.ctx.moveTo(startX, startY); this.data.ctx.lineTo(endX, endY); this.data.ctx.setStrokeStyle(item.color); let thin = this.data.horizontal ? item.thin / 10000 * this.data.height : item.thin / 10000 * this.data.width; this.data.ctx.setLineWidth(thin); this.data.ctx.setLineCap("round"); this.data.ctx.stroke(); } else if (item.graph == 'rect') { let x = startX > endX ? endX : startX; let y = startY > endY ? endY : startY; let diffX = startX + endX - x * 2; let diffY = startY + endY - y * 2; this.data.ctx.setStrokeStyle(item.color); let thin = this.data.horizontal ? item.thin / 10000 * this.data.height : item.thin / 10000 * this.data.width; this.data.ctx.setLineWidth(thin); console.log('画矩形'); if (item.solid) { console.log('实心'); this.data.ctx.setFillStyle(item.color); // if (!this.data.horizontal) { // this.data.ctx.fillRect(y, x, diffY, diffX); // } else { // this.data.ctx.fillRect(x, y, diffX, diffY); // } this.data.ctx.fillRect(x, y, diffX, diffY); } else { console.log('空心'); // if (!this.data.horizontal) { // this.data.ctx.strokeRect(y, x, diffY, diffX); // } else { // this.data.ctx.strokeRect(x, y, diffX, diffY); // } this.data.ctx.strokeRect(x, y, diffX, diffY); } } else if (item.graph == 'circle' || item.graph == 'oval') { let x = (startX + endX) / 2; let y = (startY + endY) / 2; let a = Math.abs(startX - endX) / 2; let b = Math.abs(startY - endY) / 2; this.data.ctx.setStrokeStyle(item.color); let thin = this.data.horizontal ? item.thin / 10000 * this.data.height : item.thin / 10000 * this.data.width; this.data.ctx.setLineWidth(thin); // if (!this.data.horizontal) { // this.drawEllipse2(this.data.ctx, y, x, b, a, item.solid, item.color); // } else { // this.drawEllipse2(this.data.ctx, x, y, a, b, item.solid, item.color); // } this.drawEllipse2(this.data.ctx, x, y, a, b, item.solid, item.color); this.data.ctx.stroke(); } } }); this.data.ctx.draw(true); }, drawEllipse: function (context, x, y, a, b, fill, color) { context.save(); let r = (a > b) ? a : b; let ratioX = a / r; let ratioY = b / r; context.beginPath(); context.scale(ratioX, ratioY); context.arc(x / ratioX, y / ratioY, r, 0, 2 * Math.PI, false); context.closePath(); if (fill) { context.fillStyle = color; context.fill(); } context.restore(); }, drawEllipse2: function (context, x, y, a, b, fill, color) { var step = (a > b) ? 1 / a : 1 / b; context.beginPath(); context.moveTo(x + a, y); for (var i = 0; i < 2 * Math.PI; i += step) { context.lineTo(x + a * Math.cos(i), y + b * Math.sin(i)); } context.closePath(); if (fill) { context.fillStyle = color; context.fill(); } }, start: function (e) { if (!this.properties.canDraw) { return; } let { x, y } = this.getEventLocation(e); let line = new tools.Line(this.properties.user, { color: this.data.color, thin: this.data.thin, x: x, y: y, show: true, seq: this.getSeq() }) this.data.userData[this.data.currentBoard][this.properties.user].push(line); this.data.data[this.data.currentBoard].push(line); this.data.sendData.push({ "action": 1, "color": tools.formatColor(this.data.color), "scale": 100, "thin": this.data.thin, "time": parseInt(+new Date() / 1000), "seq": line.seq, "x": this.data.horizontal ? x : y, "y": this.data.horizontal ? y : x }); }, move: function (e) { if (!this.properties.canDraw) { return; } let { x, y } = this.getEventLocation(e); let length = this.data.userData[this.data.currentBoard][this.properties.user].length; let line = this.data.userData[this.data.currentBoard][this.properties.user][length - 1]; let lastPoint = line.lines[line.lines.length - 1]; this.data.ctx.beginPath(); this.data.ctx.moveTo(lastPoint.x / 10000 * this.data.width, lastPoint.y / 10000 * this.data.height); this.data.ctx.lineTo(x / 10000 * this.data.width, y / 10000 * this.data.height); this.data.ctx.setStrokeStyle(this.data.color); let thin = this.data.horizontal ? this.data.thin / 10000 * this.data.height : this.data.thin / 10000 * this.data.width; this.data.ctx.setLineWidth(thin); this.data.ctx.setLineCap('round'); this.data.ctx.stroke(); this.data.ctx.draw(true); line.setBorder(x, y); let seq = this.getSeq(); line.lines.push({ x: x, y: y, seq: seq }); this.data.sendData.push({ "action": 2, "seq": seq, "x": this.data.horizontal ? x : y, "y": this.data.horizontal ? y : x }); }, end: function (e) { if (!this.properties.canDraw) { return; } let { x, y } = this.getEventLocation(e); let length = this.data.userData[this.data.currentBoard][this.properties.user].length; let line = this.data.userData[this.data.currentBoard][this.properties.user][length - 1]; line.setBorder(x, y); let seq = this.getSeq(); line.lines.push({ x: x, y: y, seq: seq }); this.data.sendData.push({ "action": 3, "seq": seq, "x": this.data.horizontal ? x : y, "y": this.data.horizontal ? y : x }); }, clear: function () { this.data.data[this.data.currentBoard] = []; this.data.userData[this.data.currentBoard] = {}; this.data.userData[this.data.currentBoard][this.properties.user] = []; let temp = Object.assign({}, this.data.backgroundPic); temp[this.data.currentBoard] = { url: '', user: '', show: true }; this.setData({ backgroundPic: temp }); this.data.color = tools.dealColor(4278190335); this.data.thin = 100; this.needDraw = true; }, init: function () { // 修改color this.data.color = tools.dealColor(this.data.color); this.data.data[this.data.currentBoard] = []; this.data.userData[this.data.currentBoard] = {}; this.data.userData[this.data.currentBoard][this.properties.user] = []; let temp = Object.assign({}, this.data.backgroundPic); temp[this.data.currentBoard] = { url: '', user: '', show: true }; this.setData({ backgroundPic: temp }) this.data.ctx = wx.createCanvasContext('sketchpad', this); // 计时器 this.data.drawInterval = setInterval(() => { if (this.data.needDraw) { this.draw(); this.data.needDraw = false; } }, 50); this.data.sendInterval = setInterval(() => { if (this.data.sendData.length > 0) { this.triggerEvent('send', this.data.sendData, {}); this.setData({ sendData: [] }); } }, 200); }, getSeq: function () { let time = parseInt(+new Date() / 1000, 10); return time * Math.pow(2, 15) + (this.data.seq++); }, imgLoaded(event) { this.setData({ naturalWidth: event.detail.width, naturalHeight: event.detail.height }, () => { this.data.needDraw = true; }); }, updateImgStyle() { var style = []; if (this.data.horizontal) { // 水平方向 style = ['left: 50%', 'transform: translate(-50%, -50%)', 'top: 50%']; if (this.data.width / this.data.height < this.data.naturalWidth / this.data.naturalHeight) { style.push('width:100%'); style.push('height:' + (this.data.width / this.data.naturalWidth * this.data.naturalHeight) + 'px'); } else { style.push('width:' + this.data.height / this.data.naturalHeight * this.data.naturalWidth + 'px'); style.push('height:100%'); } } else { if (this.data.width / this.data.height < this.data.naturalWidth / this.data.naturalHeight) { style.push('top: 0'); style.push('width:' + this.data.width + 'px'); var height = (this.data.width / this.data.naturalWidth * this.data.naturalHeight); style.push('height:' + height + 'px'); style.push('left:' + (this.data.height + height) / 2 + 'px'); } else { style.push('left: ' + this.data.height + 'px'); var width = this.data.height / this.data.naturalHeight * this.data.naturalWidth; style.push('width:' + width + 'px'); style.push('height:' + this.data.height + 'px'); style.push('top:' + (this.data.width - width) / 2 + 'px'); } } this.setData({ imgStyle: style.join(';') }); } } })