利用canvas搭建白板
首先在html中设置一个白板区域,添加一个清除白板的按钮
<canvas id="whiteboard" width="1280px" height="720px" class="whiteboard-canvas"></canvas> <div> <button id="clear" class="btn-warning">清除画板</button> </div>
接着在js中创建一个App对象,用于管理所有白板相关的属性和方法,并且添加处理鼠标事件的方法。
App.whiteboard = $('#whiteboard'); App.ctx = App.whiteboard[0].getContext("2d"); // 标记是否开始绘图 App._startedDrawing = false; // 鼠标事件关联绘图:下笔、移动、提笔 App.whiteboard.on('mousedown mouseup mousemove', null, function(e) { // 如果没有开始画画并且事件不是下笔 则不开始画画 if (!App._startedDrawing && e.type != "mousedown") return; App._startedDrawing = true; // 获得偏移坐标 var offset = $(this).offset(); // 所有需要的数据 var data = { x: (e.pageX - offset.left), y: (e.pageY - offset.top), type: e.handleObj.type, color: App.ctx.strokeStyle, imageData: App.whiteboard[0].toDataURL() } // 呈现在自己的画板上 App.draw(data); }) // 鼠标事件关联清除画板 $('#clear').on('click', function() { App.clear(); })
然后写draw函数,将传入的data在画布上展现出来;以及clear函数,用于清空画板
// 绘图操作 App.draw = function (data) { var originalColor = App.ctx.strokeStyle; App.ctx.strokeStyle = data.color; if (data.type == "mousedown") { App.ctx.beginPath(); App.ctx.moveTo(data.x, data.y) } else if (data.type == "mouseup") { App.ctx.stroke(); App.ctx.closePath(); App._startedDrawing = false; App.socket.emit('save-data', App.whiteboard[0].toDataURL()); } else { App.ctx.lineTo(data.x, data.y); App.ctx.stroke(); } App.ctx.strokeStyle = originalColor; };
App.clear = function () { App.ctx.clearRect(0, 0, App.whiteboard[0].width, App.whiteboard[0].height); };
这个时候,就完成了鼠标路径到画布显示的路径
利用websocket同步画板
这里在服务端和客户端均是利用socket.io来实现websocket。首先在客户端js中写好连接websocket,发送笔画、接受笔画的方法。
// 建立websocket App.socket = io('http://' + window.location.host); // 初始化图片和色彩 App.socket.on('setup', function (color, dataUrl) { App.ctx.strokeStyle = color; if (dataUrl) { // 从url加载图片 var imageObj = new Image(); imageObj.onload = function () { App.ctx.drawImage(this, 0, 0); }; imageObj.src = dataUrl; } }); // 接受笔画信息 App.socket.on('draw', App.draw); // 接受清除画板请求 App.socket.on('clear', App.clear); // 发送笔画至服务端 App.socket.emit('do-the-draw', data); // 发送画完一笔的信息至服务端 App.socket.emit('save-data', App.whiteboard[0].toDataURL()); // 发送清除画板请求 App.socket.emit('clear');
在客户端,由于socket.io是基于server进行websocket操作的,而server的架设在bin\www文件中,因此为了能够在路由中实现websocket,需要完成一个router.js→app.js→www的过程。
// eoutes\canvas.io var imageData; // 对不同用户给不同的颜色 var colors = [ "#CFF09E", "#A8DBA8", "#79BD9A", "#3B8686", "#0B486B" ]; var i = 0; var IO = null; // 这里定义了一个io函数 router.io = function(io) { io.on('connection', (socket) => { if (i == 5) i = 0; socket.emit('setup', colors[i++], imageData); socket.on('do-the-draw', (data) => { // 用户画了一笔 socket.broadcast.emit('draw', data); imageData = data.imageData; }) socket.on('clear', function() { // 用户清除画板 socket.broadcast.emit('clear'); imageData = null; }) socket.on('save-data', (data) => { // 用户画完一笔 imageData = data; }) }); IO = io; return io; }
这里在路由中定义了一个对传入io进行socket操作的函数,并且将这个函数传出出去。
// app.js // 引入路由 var canvasRouter = require('./routes/canvas') // 引入路由中的io函数 app.canvasIO = canvasRouter.io; // 便于在路由中响应http请求 app.use('/canvas', canvasRouter);
而app.js获得router传出的这个函数,并再次传出去
// bin\www // 架设server var server = http.createServer(app); // 引入socket.io var io = require('socket.io')(server); // 将io传入app.js的canvasIO函数中,最终传入canvas.js的io函数中 app.canvasIO(io);
www获得app.js传出的对io进行操作的函数,并且传入server的io。这个时候,不同客户端之间就可以实时共享白板了。
添加上传画布背景功能
为了便于对某一张图片进行标记和讲解,需要添加一个上传画布背景的功能。
选用的依然是bootstrap-fileinput插件,在html中插入上传框,在上传初始化中将上传的文件格式限制为’jpg’, ‘gif’, ‘png’, ‘jpeg’。上传后,在路由中接受文件并暂存为临时文件。
//上传背景图片 router.post('/upload', function(req, res) { var form = new formidable.IncomingForm(); // ... // formidable对象的临时路径等属性的初始化 form.parse(req, function(err, fields, files) { var filepath = ''; for (var key in files) { // ... // 获取到临时存放的文件地址 } var fileExt = filepath.substring(filepath.lastIndexOf(".")); // 文件类型校验 if ((".jpg.png.bmp.jpeg").indexOf(fileExt.toLowerCase()) == -1) { // ... // 格式不对的操作 } else { // ... // 提供格式正确的返回值,并且利用socket向所有客户端发送背景临时文件的地址 } }); })
然后在客户端js中加入更换图片背景的方法
// 接受图片背景 App.socket.on('drawImage', (data) => { var imgObj = new Image(); imgObj.src = '/canvas/image?path=' + data.data; // 通过url获得服务端图像临时文件 imgObj.onload = function() { App.ctx.drawImage(this, 0, 0, 1280, 720); // canvas 绘制图像 } $("#downblock").hide(); // 或者直接将图像作为背景,但不推荐 //$('#whiteboard').css("background", "url(" + data.data + ") no-repeat"); })
通过上传图片→分发图片→绘制图片的过程,所有的客户端,不论是已经打开的,还是后续打开的,都可以在画布上看见新的图片。
切换黑白板功能
切换黑白板功能只需要在预先存好黑白两个底色,然后在路由中存储一个是否为白色的变量。每次客户端点击切换黑白板的时候,将请求emit到服务端,然后服务端修改这个变量,并且在emit到所有客户端即可
保存画板功能
保存画板需要用到canvas的一个toDataURL方法,然后将获得的url下载下来
// 保存画板 $(document).ready(() => { $('#save').click(() => { downLoad(whiteboard.toDataURL("image/png")); }) }) function downLoad(url){ // 创建一个新的元素,将href值设为url,并点击它 }
通过点击新建的下载元素,可以通过canvas的url,将画布转为png文件下载下来
同样地,这个简单的共享白板也可以添加更多优化,例如撤销恢复功能,添加线条、方框、箭头等基本元素的功能,以及对学生访问白板的权限管理等。