在Odoo14中,如何在form表单中最上面插入一个Canvas的画布控件呢?
首先,我发现在Odoo中,form表单会在每次重置后只进入一次form视图的init和renderButtons等相关的初始化视图方法。但是在二次渲染视图时,会出现"不触发"和"找不到相关DOM元素"的问题。
为了解决这个问题,我们需要使用form视图的Renderer配置属性,并改写Renderer中的内容,因为Renderer是百分百会进入和触发的。
ExtendFromController = FormController.extend({ events: Object.assign({}, FormController.prototype.events, { 'click .bump-form-search': '_bump_form_search', 'click .reset-form-bump': '_reset_form_bump', 'click .add-bump-form': '_add_bump_inventory', }), /** * @override */ init: function (parent, model, renderer, params) { this._super.apply(this, arguments); this.initialState.context }, }) var bump_formView = FormView.extend({ config: _.extend({}, FormView.prototype.config, { Controller: ExtendFromController, Renderer: GroupList }), _extractParamsFromAction:function (){ var params = this._super.apply(this, arguments); return params } view_registry.add('borrow_bump_form_list', bump_formView); return bump_formView; })
代码仅仅作为示范,大家可以自己尝试检验一下
改写Renderer部分的代码,记得引入相关模块
/**记得引入*/ var FormController = require('web.FormController'); var FormRenderer = require('web.FormRenderer'); var GroupList = FormRenderer.extend({ /** * @private * @param {Object} node * @returns {jQueryElement} */ _renderTagNotebook: function (node) { var self = this; var $headers = $('<ul class="nav nav-tabs">'); var $pages = $('<div class="tab-content">'); // renderedTabs is used to aggregate the generated $headers and $pages // alongside their node, so that their modifiers can be registered once // all tabs have been rendered, to ensure that the first visible tab // is correctly activated var renderedTabs = _.map(node.children, function (child, index) { var pageID = _.uniqueId('notebook_page_'); var $header = self._renderTabHeader(child, pageID); var $page = self._renderTabPage(child, pageID); self._handleAttributes($header, child); $headers.append($header); $pages.append($page); return { $header: $header, $page: $page, node: child, }; }); // register the modifiers for each tab _.each(renderedTabs, function (tab) { self._registerModifiers(tab.node, self.state, tab.$header, { callback: function (element, modifiers) { // if the active tab is invisible, activate the first visible tab instead var $link = element.$el.find('.nav-link'); if (modifiers.invisible && $link.hasClass('active')) { $link.removeClass('active'); tab.$page.removeClass('active'); self.inactiveNotebooks.push(renderedTabs); } if (!modifiers.invisible) { // make first page active if there is only one page to display var $visibleTabs = $headers.find('li:not(.o_invisible_modifier)'); if ($visibleTabs.length === 1) { self.inactiveNotebooks.push(renderedTabs); } } }, }); }); this._activateFirstVisibleTab(renderedTabs); var $notebookHeaders = $('<div class="o_notebook_headers">').append($headers); var $notebook = $('<div class="o_notebook">').append($notebookHeaders, $pages); $notebook[0].dataset.name = node.attrs.name || '_default_'; this._registerModifiers(node, this.state, $notebook); this._handleAttributes($notebook, node); bump_form_process_status = this.state.data.process_status; states = this.state.data apply_id = this.state.data.apply_id.res_id /**在这里找相关的DOM和触发相关是业务操作,记得代码方法写在*/ $(document).ready(() => { _this.$buttons.find('.my_bump_buttons').hide() this.beforeCanvas();//在这里调用创建Canvas的视图 }); return $notebook; }, /**代码方法写在这里*/ var nodes = [ { x: 100, y: 100, text: '开始' }, { x: 300, y: 100, text: '步骤1' }, { x: 300, y: 200, text: '步骤2' }, { x: 100, y: 200, text: '步骤3' }, { x: 200, y: 300, text: '结束' } ]; beforeCanvas:function (){ let canvasWidth = parseInt($('.o_form_sheet').innerWidth())-400 let canvasBox = `<div class="step-line step-box"><canvas id="flowchartCanvas" width="${canvasWidth}" height="400"></canvas></div>` $('.o_form_sheet').before(canvasBox) this.canvasInit(); }, canvasInit:function (){ var canvas = this.$('#flowchartCanvas')[0]; var context = canvas.getContext('2d'); this.drawFlowchart(); }, // 绘制节点 drawNode:function(x, y, text) { var canvas = this.$('#flowchartCanvas')[0]; var context = canvas.getContext('2d'); context.beginPath(); context.arc(x, y, 30, 0, 2 * Math.PI); context.stroke(); context.font = '14px Arial'; context.textAlign = 'center'; context.fillText(text, x, y + 5); }, // 绘制连线 drawConnections:function() { var canvas = this.$('#flowchartCanvas')[0]; var context = canvas.getContext('2d'); context.beginPath(); context.moveTo(nodesCanvas[0].x, nodesCanvas[0].y); for (var i = 1; i < nodesCanvas.length; i++) { context.lineTo(nodesCanvas[i].x, nodesCanvas[i].y); } context.lineWidth = 5; context.strokeStyle = 'blue' context.lineCap = 'round'; context.stroke(); }, // 绘制节点和连线 drawFlowchart:function() { let self =this; var canvas = this.$('#flowchartCanvas')[0]; var context = canvas.getContext('2d'); // 定义流程图的节点 // 清空画布 context.clearRect(0, 0, canvas.width, canvas.height); // 绘制节点 nodesCanvas.forEach(function (node) { self.drawNode(node.x, node.y, node.text); }); // 绘制连线 self.drawConnections(); }, // 在节点上添加点击事件,点击时移动节点位置这个功能是尝试给相关节点添加事件,就和流程图一样。记得和上面一样封装在function中 canvas.addEventListener('mousedown', function (e) { var mouseX = e.clientX - canvas.offsetLeft; var mouseY = e.clientY - canvas.offsetTop; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; var distance = Math.sqrt(Math.pow(mouseX - node.x, 2) + Math.pow(mouseY - node.y, 2)); if (distance <= 30) { var offsetX = mouseX - node.x; var offsetY = mouseY - node.y; canvas.addEventListener('mousemove', moveNode); canvas.addEventListener('mouseup', stopMovingNode); canvas.addEventListener('mouseout', stopMovingNode); function moveNode(e) { node.x = e.clientX - canvas.offsetLeft - offsetX; node.y = e.clientY - canvas.offsetTop - offsetY; drawFlowchart(); } function stopMovingNode() { canvas.removeEventListener('mousemove', moveNode); canvas.removeEventListener('mouseup', stopMovingNode); canvas.removeEventListener('mouseout', stopMovingNode); } break; } } }); })
会得到一个类似这样的canvas视图。这里我只是举个例子,关于如何绘制canvas,大家需要自己多下功夫,查看API和案例。
添加了事件后,是可以拖拽任意节点变形的。
你也可以使用别人封装好的JS代码。记得将它命名封装到一个单独的.js文件中,然后在你的文件中提前引入。
代码如下
/** *流向图组件,mouyao */ varopsDirectionMap = function(option){ this.const(option); this.init(); }; /* *配置项引入 */ opsDirectionMap.prototype.const=function(option){ this.r=option.r||4;//节点半径 this.config=option; this.data = option.data||[]; this.mLeft = option.mLeft||-20;//起点距左边距离 this.space = option.space||18*this.r;//节点之间距离 this.angle =2*this.r;//分支上下之间的高度 this.nodeArr = []; //存储所有的圆点的信息和坐标 }; /* *配置项引入 */ opsDirectionMap.prototype.init =function(){ varmyCanvas=document.getElementById(this.config.placeId); this.resolveVagueProblem(myCanvas); this.render(myCanvas); }; /* *判定是否数组 */ opsDirectionMap.prototype.isArrayFn =function(o) { returnObject.prototype.toString.call(o) === '[object Array]'; }; /* *根据当前节点的执行状态,渲染圆点前的线条的颜色 */ opsDirectionMap.prototype.drawDashLine =function(ctx, x1, y1, x2, y2,data,index){ ctx.lineWidth=1; ctx.beginPath(); varx=(x2-x1)/2; if(index>0&&!this.isArrayFn(this.data[index-1])){ ctx.moveTo(x1,y1); ctx.lineTo(x1+x ,y1); ctx.moveTo(x1+x,y1); ctx.lineTo(x1+x ,y2); ctx.moveTo(x1+x,y2); ctx.lineTo(x2 ,y2); }elseif(index>0&&this.isArrayFn(this.data[index-1])){ ctx.moveTo(x1,y1); ctx.lineTo(x1+x ,y1); ctx.moveTo(x1+x,y1); ctx.lineTo(x1+x ,y2); ctx.moveTo(x1+x,y2); ctx.lineTo(x2 ,y2); }else{ if(index!==0){ //删除第一个圆点的连接线 ctx.moveTo(x1,y1); ctx.lineTo(x2 ,y2); } } if(data.isExcuted===true){ ctx.strokeStyle="#009aff"; }elseif(data.isExcuted===false&&index!==0&&this.data[index-1].isExcuted===true&&!this.isArrayFn(this.data[index-1])){ ctx.strokeStyle="#009aff"; }elseif(data.isExcuted===false&&this.isArrayFn(this.data[index-1])){ //如果上一个元素是数组 vararr=[]; this.data[index-1].some(function(item){ if(item.isExcuted===true){ arr.push(true); } }); if(arr.length===(this.data[index-1]).length){ ctx.strokeStyle="#009aff"; }else{ ctx.strokeStyle="#959595"; } }else{ ctx.strokeStyle="#959595"; } ctx.stroke(); }; /* *绘制线条,圆点,圆心,和说明文字 */ opsDirectionMap.prototype.render =function(canvas){ varthis_ = this; this_.canvas = canvas; varctx = canvas.getContext("2d");//上下文 this_.ctx = ctx; varx = this_.mLeft; //每个操作的对象的坐标 //var y = canvas.height/2; //x偏移量:this_.r*Math.sin((90-itemY)*Math.PI/180) //y偏移量:this_.r*Math.cos((90-itemY)*Math.PI/180) vary =50; this_.data.forEach(function(item, index){ if(!(item instanceofArray)){ x = index == 0?x:(x + this_.space); if((index-1)>=0&& this_.data[index-1] instanceofArray){ vararr = this_.data[index-1]; if(arr.length % 2==0){ varitemY = this_.angle; for(vari=0;i<arr.length/2;i++){ this_.drawDashLine(ctx, x - this_.space - this_.r+this_.r*Math.sin((90-itemY)*Math.PI/180), y-Math.tan(itemY*Math.PI/180)*this_.space+this_.r*Math.cos((90-itemY)*Math.PI/180), x, y,item,index); itemY = itemY + this_.angle; } varitemY = this_.angle; for(vari=0;i<arr.length/2;i++){ this_.drawDashLine(ctx, x - this_.space - this_.r+this_.r*Math.sin((90-itemY)*Math.PI/180), y+Math.tan(itemY*Math.PI/180)*this_.space-this_.r*Math.cos((90-itemY)*Math.PI/180), x, y,item,index); itemY = itemY + this_.angle; } }else{ varitemY = 0; for(vari=0;i<parseInt(arr.length/2)+1;i++){ console.log(arr[i]); this_.drawDashLine(ctx, x - this_.space - this_.r+this_.r*Math.sin((90-itemY)*Math.PI/180), y-Math.tan(itemY*Math.PI/180)*this_.space+this_.r*Math.cos((90-itemY)*Math.PI/180), x, y,item,index); itemY = itemY + this_.angle; } varitemY = this_.angle; for(vari=0;i<parseInt(arr.length/2);i++){ this_.drawDashLine(ctx, x - this_.space - this_.r+this_.r*Math.sin((90-itemY)*Math.PI/180), y+Math.tan(itemY*Math.PI/180)*this_.space-this_.r*Math.cos((90-itemY)*Math.PI/180), x, y,item,index); itemY = itemY + this_.angle; } } } if(index == 0){ ctx.moveTo(x,y); x = x + this_.space; } //绘制非数组直线 if(!((index-1)>=0&& this_.data[index-1] instanceofArray)){ this_.drawDashLine(ctx,x-this_.space, y, x, y,item,index); } ctx.moveTo(x + 2*this_.r,y); //绘制节点,画圆 ctx.arc(x + this_.r,y,this_.r,0,2*Math.PI); this_.nodeArr.push({x:x + this_.r,y:y,data:item}); ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色 ctx.fill(); //节点标题note ctx.textAlign ="center"; ctx.textBaseline = "middle"; ctx.font = "bold 10px 宋体";//字体大小 ctx.fillStyle =item.noteColor;//字体颜色 //节点的名称设置 ctx.fillText(item.noteName,x + this_.r,y-this_.r-10); ctx.fillStyle = "black";//字体颜色 x = x + 2*this_.r; ctx.stroke(); ctx.beginPath(); ctx.moveTo(x,y); }else{//数组 if(!(this_.data[index-1] instanceofArray)){//上一级不是数组 varitemY = 0; if(item.length%2==0){//偶数 itemY = this_.angle; vardataArr = item.slice(0,item.length/2).reverse(); for(vari=0;i<dataArr.length;i++){ this_.drawDashLine(ctx, x, y, x + this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space, y-Math.tan(itemY*Math.PI/180)*(this_.space),this_.r,0,2*Math.PI); this_.nodeArr.push({x:x + this_.space,y:y-Math.tan(itemY*Math.PI/180)*(this_.space),data:dataArr[i]}); //节点信息 ctx.textAlign ="center"; ctx.textBaseline = "middle"; ctx.font = "bold 10px 宋体";//字体大小 ctx.fillStyle =dataArr[i].isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;;//填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor;//字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space, y-Math.tan(itemY*Math.PI/180)*(this_.space)-this_.r-10); ctx.fill(); ctx.moveTo(x+this_.r,y); itemY = itemY + this_.angle; } itemY = this_.angle; vardataArr = item.slice(item.length/2,item.length); for(vari=0;i<dataArr.length;i++){ this_.drawDashLine(ctx, x, y, x + this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space, y+Math.tan(itemY*Math.PI/180)*(this_.space),this_.r,0,2*Math.PI); this_.nodeArr.push({x:x + this_.space,y:y+Math.tan(itemY*Math.PI/180)*(this_.space),data:dataArr[i]}); //节点信息 ctx.textAlign ="center"; ctx.textBaseline = "middle"; ctx.font = "bold 10px 宋体";//字体大小 ctx.fillStyle =dataArr[i].isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor;//字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space, y+Math.tan(itemY*Math.PI/180)*(this_.space)+this_.r+10); ctx.fill(); itemY = itemY + this_.angle; } }else{//奇数 vardataArr = item.slice(0,parseInt(item.length/2)+1).reverse(); for(vari=0;i<dataArr.length;i++){ this_.drawDashLine(ctx, x, y, x + this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI); this_.nodeArr.push({x:x + this_.space,y:y-Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]}); //节点信息 ctx.textAlign ="center"; ctx.textBaseline = "middle"; ctx.font = "bold 10px 宋体";//字体大小 ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;;//填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor;//字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space-this_.r-10); ctx.fill(); itemY = itemY + this_.angle; } itemY = this_.angle; vardataArr = item.slice(parseInt(item.length/2)+1,item.length); for(vari=0;i<dataArr.length;i++){ this_.drawDashLine(ctx, x, y, x + this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI); this_.nodeArr.push({x:x + this_.space,y:y+Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]}); //节点信息 ctx.textAlign ="center"; ctx.textBaseline = "middle"; ctx.font = "bold 10px 宋体";//字体大小 ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor;//字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space+this_.r+10); ctx.fill(); itemY = itemY + this_.angle; } } ctx.stroke(); ctx.beginPath(); x = x+this_.space+this_.r; ctx.moveTo(x,y); }else{//上一级是数组 if(item.length%2==0){//偶数 varitemY = this_.angle; vardataArr = item.slice(0,item.length/2).reverse(); for(vari=0;i<dataArr.length;i++){ ctx.moveTo(x,y-Math.tan(itemY*Math.PI/180)*this_.space); this_.drawDashLine(ctx, x, y-Math.tan(itemY*Math.PI/180)*this_.space, x+this_.r+ this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x+ this_.space+this_.r, y-Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI); this_.nodeArr.push({x:x+ this_.space+this_.r,y:y-Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]}); //节点信息 ctx.textAlign ="center"; ctx.textBaseline = "middle"; ctx.font = "bold 10px 宋体";//字体大小 ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor;//字体颜色 ctx.fillText(dataArr[i].noteName,x+ this_.space+this_.r, y-Math.tan(itemY*Math.PI/180)*this_.space-this_.r-10); ctx.fill(); itemY = itemY + this_.angle; } varitemY = this_.angle; vardataArr = item.slice(item.length/2,item.length); for(vari=0;i<dataArr.length;i++){ ctx.moveTo(x,y+Math.tan(itemY*Math.PI/180)*this_.space); this_.drawDashLine(ctx, x, y+Math.tan(itemY*Math.PI/180)*this_.space, x+this_.r+ this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x+ this_.space+this_.r, y+Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI); this_.nodeArr.push({x:x+ this_.space+this_.r,y:y+Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]}); //节点信息 ctx.textAlign ="center"; ctx.textBaseline = "middle"; ctx.font = "bold 10px 宋体";//字体大小 ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor;//字体颜色 ctx.fillText(dataArr[i].noteName,x+ this_.space+this_.r, y+Math.tan(itemY*Math.PI/180)*this_.space+this_.r+10); ctx.fill(); itemY = itemY + this_.angle; } }else{//奇数 varitemY = 0; vardataArr = item.slice(0,parseInt(item.length/2)+1).reverse(); for(vari=0;i<dataArr.length;i++){ ctx.moveTo(x,y-Math.tan(itemY*Math.PI/180)*this_.space); this_.drawDashLine(ctx, x, y-Math.tan(itemY*Math.PI/180)*this_.space, x+this_.r+ this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x+ this_.space+this_.r, y-Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI); this_.nodeArr.push({x:x+ this_.space+this_.r,y:y-Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]}); //节点信息 ctx.textAlign ="center"; ctx.textBaseline = "middle"; ctx.font = "bold 10px 宋体";//字体大小 ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor;//字体颜色 ctx.fillText(dataArr[i].noteName,x+ this_.space+this_.r, y-Math.tan(itemY*Math.PI/180)*this_.space-this_.r-10); ctx.fill(); itemY = itemY + this_.angle; } varitemY = this_.angle; vardataArr = item.slice(parseInt(item.length/2)+1,item.length); for(vari=0;i<dataArr.length;i++){ ctx.moveTo(x,y+Math.tan(itemY*Math.PI/180)*this_.space); this_.drawDashLine(ctx, x, y+Math.tan(itemY*Math.PI/180)*this_.space, x+this_.r+ this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x+ this_.space+this_.r, y+Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI); this_.nodeArr.push({x:x+ this_.space+this_.r,y:y+Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]}); //节点信息 ctx.textAlign ="center"; ctx.textBaseline = "middle"; ctx.font = "bold 10px 宋体";//字体大小 ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor;//字体颜色 ctx.fillText(dataArr[i].noteName,x+ this_.space+this_.r, y+Math.tan(itemY*Math.PI/180)*this_.space+this_.r+10); ctx.fill(); itemY = itemY + this_.angle; } } ctx.stroke(); ctx.beginPath(); x = x+this_.space+2*this_.r; ctx.moveTo(x,y); } } }); }; /* *因为canvas绘制的是矢量图,会出现模糊问题,使用下边的方法解决 * 参考链接:https://zhuanlan.zhihu.com/p/31426945 */ opsDirectionMap.prototype.resolveVagueProblem=function(myCanvas) { vargetPixelRatio = function(context) { varbackingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; return(window.devicePixelRatio || 1) / backingStore; }; //画文字 myCanvas.style.border = "1px solid silver"; varcontext = myCanvas.getContext("2d"); varratio = getPixelRatio(context); myCanvas.style.width = myCanvas.width + 'px'; myCanvas.style.height =myCanvas.height+ 'px'; myCanvas.width = myCanvas.width * ratio; myCanvas.height = myCanvas.height * ratio; context.scale(ratio,ratio); };
然后可以在你的form的js中调用
//调用的时候可以直接放到上面的beforeCanvas这个方法中,如果找不到对应的dom记得修改上面封装的js中的dom获取方式,断点查看 vardemo=newopsDirectionMap({ placeId:"renderArea", excutedCirclePointColor:"#009aff",//执行的节点的圆心颜色 circlePointColor:"#ffffff",//未执行的的节点的圆心颜色 data:[{ noteName:'节点1', noteColor:'#000000', //说明文字的颜色 isExcuted:true//如果这里为true,则其前边的线条为蓝色,圆点为实心,否为白色 },{ noteName:'节点2', noteColor:'#000000', isExcuted:true },[ { noteName:'节点3-1', noteColor:'#000000', isExcuted:true }, { noteName:'节点3-2', noteColor:'#000000', isExcuted:false } ],{ noteName:'节点4', noteColor:'#000000', isExcuted:false },{ noteName:'节点5', noteColor:'#000000', isExcuted:false },[ { noteName:'节点6-1', noteColor:'#000000', isExcuted:false },{ noteName:'节点6-2', noteColor:'#000000', isExcuted:false, } ],{ noteName:'节点7', noteColor:'#000000', isExcuted:false } ] });
完成后可以得到一个的步骤图
剩下如何修改样式,代码事件等相关操作需要自己行根据自身的需求进行修改。
三、附赠:贪吃蛇小游戏
这里给大家附赠一个用Canvas制作的贪吃蛇小游戏,大家可以自己去尝试一下。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>canvas</title> </head> <style> .container{ width:800px; height:800px; margin:50px auto; /* border:1px solid #ccc; */ } .statistics{ height:100px; display: flex; background:green; font-size:32px; text-align: center; line-height: 100px; color: #fff; } .statistics div{ width:50%; } canvas{ background: #000; overflow: hidden; vertical-align:middle } .controller{ display: flex; height:100px; font-size:32px; text-align: center; line-height: 100px; color:#fff; } .controller div{ width:100%; cursor: pointer; } .controller div:active{ background:green!important; } .controller div:nth-child(1){ background: #8fd229; } .controller div:nth-child(2){ background: #9bd83c; } .controller div:nth-child(3){ background: #96ce42; } </style> <body> <div class="container"> <div class="statistics"> <div id="score">得分:0分</div> <div id="timer">用时:0秒</div> </div> <canvas id="game" width="800" height="600"></canvas> <div class="controller"> <div onclick="start()">开始</div> <div id="stop" onclick="stop()">暂停</div> <div onclick="reset()">重置</div> </div> </div> </body> <script> var canvas = document.getElementById('game'); var game = { length:3, positions:[[360,500], [380,500],[400,500]], direction:'right', foodPositionInSnack:null, foodPosition:null, score:0 }; var timer; var timeSpend = 0; var startTime = 0; var operateTime = 0; var speedFactor = 300; var willGrowUp = false; var gameStatus = true; var operateTimer; var operateQueue = []; function start(){ console.log('game_ start__'); if( !game.content ){ refreshTime(); gameInit(); } } function stop(){ gameStatus = ! gameStatus; if( gameStatus ){ document.getElementById('stop').innerText = '暂停' refreshTime(); console.log('game_ restart'); }else{ document.getElementById('stop').innerText = '继续' clearInterval(timer); console.log('game_ stop'); } } function reset(){ console.log('game_ reset'); if( !!game.content ){ timeSpend = 0; game = { length:3, positions:[[360,500], [380,500],[400,500]], direction:'right', foodPositionInSnack:null, foodPosition:null, score:0 }; gameStatus = true; document.getElementById('stop').innerText = '暂停' gameInit(); } } function gameInit(){ var ctx = canvas.getContext('2d'); game.content = ctx; generateFood(); renderGame(); operateEventloop(); } function renderGame(timestamp){ var currentTime = performance.now(); if( (currentTime - startTime) < speedFactor || !gameStatus ){ requestAnimationFrame(renderGame); return false; } operateEventloop(); startTime = currentTime; game.content.clearRect(0,0,canvas.width,canvas.height); var x = 0,y = 0; switch (game.direction){ case 'left': x -=20; break; case 'right': x +=20; break; case 'top': y -= 20; break; case 'bottom': y += 20; break; } //判断吃掉食物 const newPosition = [game.positions[game.positions.length - 1][0] +x, game.positions[game.positions.length - 1][1] +y]; //如果头部位置和食物位置重合,则吃掉,分数+1, 并重新生成食物 if( newPosition[0] == game.foodPosition[0] && newPosition[1] == game.foodPosition[1]){ game.foodPositionInSnack = [...game.foodPosition]; generateFood(); game.score ++; document.getElementById('score').innerText = `得分:${game.score}分`; var accCoeff = Math.floor(game.score / 10); speedFactor = 300 - accCoeff * 40 > 50 ? 300 - accCoeff * 40 : 50; } //判断游戏失败 var isFailture = false; //1.出界 if(newPosition[0] < 0 || newPosition[0] >= 800 || newPosition[1] <0 || newPosition[1] >=600 ){ isFailture = true; } //2.吃到自己 game.positions.forEach(position => { if(position[0] == newPosition[0] && position[1] == newPosition[1]){ isFailture = true; } }) if(isFailture){ alert(`Game Over \n 最终得分${game.score}`); return false; } game.positions.push(newPosition); // 如果食物到达尾部,则变长一格 if( !willGrowUp ){ game.positions.shift(); }else{ game.foodPositionInSnack = null; } willGrowUp = false; if( game.foodPositionInSnack ){ if( game.positions[0][0] == game.foodPositionInSnack[0] && game.positions[0][1] == game.foodPositionInSnack[1] ){ willGrowUp = true; } } drawSnack(); drawFood(); requestAnimationFrame(renderGame); } function generateFood(){ var randomPos = randomPosition(); var hasRepeat = false; game.positions.forEach(position => { if( randomPos[0] == position[0] && randomPos[1] == [1]){ hasRepeat = true; } }) if( hasRepeat ){ generateFood(); return; } game.foodPosition = randomPos; } function randomPosition(){ return [Math.floor(Math.random() * 40) * 20, Math.floor(Math.random() * 30) *20]; } function drawSnack(){ var ctx = game.content; ctx.beginPath(); ctx.fillStyle = '#ccc'; game.positions.forEach(position => { ctx.fillRect(position[0] + 2.5, position[1] + 2.5, 15,15); }) } function drawFood(){ var ctx = game.content; ctx.beginPath(); ctx.fillStyle = '#ccc'; ctx.arc(game.foodPosition[0] + 10, game.foodPosition[1] + 10, 8, 0, Math.PI * 2, true); ctx.fill(); } function refreshTime(){ timer = setInterval(() => { timeSpend ++ ; document.getElementById('timer').innerText = `用时:${timeSpend}秒`; }, 1000); } function operateEventloop(){ if( operateQueue.length !=0 ){ triggerOperate(operateQueue[0]); operateQueue.shift(); } } function triggerOperate(e){ switch (e.keyCode){ case 87: if( game.direction != 'bottom' ){ game.direction = 'top'; } break; case 83: if( game.direction != 'top' ){ game.direction = 'bottom'; } break; case 65: if( game.direction != 'right' ){ game.direction = 'left'; } break; case 68: if( game.direction != 'left' ){ game.direction = 'right'; } break; } } document.addEventListener('keyup', function(e){ operateQueue.push(e); }) </script> </html>