前言
前段时间想要做一个web端的图形化积木式编程(类似少儿编程)的案例,网上冲浪了一圈又一圈,终于技术选型好,然后代码一顿敲,终于出来了一个雏形。
TIPS:该案例设计主要参考iRobot Coding,只用做学习用途,侵删。
最终实现效果
本文实现效果
完整代码
- Blockly代码块编辑区域基本场景搭建
<template>
<div id="blockly">
<!-- 工作区 -->
<div id="blocklyDiv" ref="blocklyDiv" style="height: 500px; width: 700px;"></div>
<button style="position: fixed;left: 50px;top: 10px;" @click="block2code">生成代码</button>
<!-- 代码显示区 -->
<div style="background-color: lightgrey;width: 700px;text-align: left">
{{code?code:'请点击生成代码按钮'}}
</div>
<button style="position: fixed;left: 150px;top: 10px;" @click="runCode">执行代码</button>
</div>
</template>
<script>
import Blockly from 'blockly'
import BlocklyJS from 'blockly/javascript';
export default {
name: "blocklyClass1",
data() {
return {
code:'',
options: {
toolbox: {
"kind": "flyoutToolbox",
"contents": [
{
"kind": "block",
"type": "controls_if"
},
{
"kind": "block",
"type": "controls_repeat_ext"
},
{
"kind": "block",
"type": "logic_compare"
}
,
{
"kind": "block",
"type": "math_number"
}
,
{
"kind": "block",
"type": "math_arithmetic"
}
,
{
"kind": "block",
"type": "text"
}
,
{
"kind": "block",
"type": "text_print"
}
]
}
}
}
},
mounted() {
Blockly.inject(this.$refs.blocklyDiv, this.options);
},
methods:{
/**
* block代码块转为代码
*/
block2code(){
this.code = BlocklyJS.workspaceToCode(this.$refs.blocklyDiv.workspace)
},
/**
* 执行生成代码
*/
runCode(){
if(!this.code){alert('请先点击生成代码');return}
eval(this.code)
},
},
}
</script>
<style scoped>
#blockly {
position: absolute;
left: 50px;
top: 50px;
bottom: 0;
width: calc(100vw - 50px);
height: calc(100vh - 50px);
display: flex;
flex-direction: column;
}
</style>
代码分解
0.项目初始化
- 利用脚手架搭建vue项目
npx @vue/cli create block-class
-
脚手架中选择Vue2、npm
- 启动工程
npm run serve
- 安装Block依赖
npm install --save blockly
- 引入模块
import Blockly from 'blockly';
- 配置白名单!
不配置白名单,解析器在解析到xml、block等标签的时候会报错,而其实我们并不希望解析器解析这些block定义的工具栏标签
//main.js - blocky配置
Vue.config.ignoredElements = ['field','block','category','xml','mutation','value','sep']
1.Blockly基本场景搭建
- blocklyDiv为整个block工作区,个人习惯将工具栏写在工作区里面(不一定嵌套在工作区,Block是通过将获取到的这个工具栏xml元素注入到工作区来实现的)
<template>
<div id="blockly">
<!-- 工作区 -->
<div id="blocklyDiv" ref="blocklyDiv" style="height: 500px; width: 700px;">
<!-- 工具栏 -->
<xml id="toolbox" ref="toolbox" style="display: none">
<block type="controls_if"></block>
<block type="controls_repeat_ext"></block>
<block type="logic_compare"></block>
<block type="math_number"></block>
<block type="math_arithmetic"></block>
<block type="text"></block>
<block type="text_print"></block>
</xml>
</div>
</div>
</template>
- 引入blockly库,通过Blockly.inject(‘在DOM中注入工作区的区域’,'注入选项字典')
注入的选项有很多,具体查看:Blockly注入选项
<script>
import Blockly from 'blockly'
export default {
name: "blocklyClass1",
mounted() {
//目前只配置了工具栏
let options = {
toolbox: this.$refs.toolbox
}
//将工作区注入到blocklyDiv的元素上,并将配置的选项注入
Blockly.inject(this.$refs.blocklyDiv,options);
}
}
</script>
2.工具栏工具列表转化为JSON格式
- 为了让工具栏列表更具灵活性,把它转为JSON格式是首选
<!-- 工具栏 -->
<xml id="toolbox" ref="toolbox" style="display: none">
<block type="controls_if"></block>
<block type="controls_repeat_ext"></block>
<block type="logic_compare"></block>
<block type="math_number"></block>
<block type="math_arithmetic"></block>
<block type="text"></block>
<block type="text_print"></block>
</xml>
- 转为JSON格式
{
"kind": "flyoutToolbox",
"contents": [
{
"kind": "block",
"type": "controls_if"
},
{
"kind": "block",
"type": "controls_repeat_ext"
},
{
"kind": "block",
"type": "logic_compare"
}
,
{
"kind": "block",
"type": "math_number"
}
,
{
"kind": "block",
"type": "math_arithmetic"
}
,
{
"kind": "block",
"type": "text"
}
,
{
"kind": "block",
"type": "text_print"
}
]
}
- 将template中的xml元素删除掉,在data中加入options的选择,里面加入一个key为toolbox的工具栏列表
export default {
name: "blocklyClass1",
data() {
return {
options: {
toolbox: {
"kind": "flyoutToolbox",
"contents": [
{
"kind": "block",
"type": "controls_if"
},
{
"kind": "block",
"type": "controls_repeat_ext"
},
{
"kind": "block",
"type": "logic_compare"
}
,
{
"kind": "block",
"type": "math_number"
}
,
{
"kind": "block",
"type": "math_arithmetic"
}
,
{
"kind": "block",
"type": "text"
}
,
{
"kind": "block",
"type": "text_print"
}
]
}
}
}
},
mounted() {
//选项中的toolbox改为了配置的字典列表
Blockly.inject(this.$refs.blocklyDiv, this.options);
}
}
</script>
当然了,除了这中直列的工具栏以为,还可以配置带分类的工具栏:
-
直列式工具栏
-
带分类工具栏
带分类工具栏配置如下(kind设置为category,然后再嵌套一层contents的列表):
{
"kind": "categoryToolbox",
"contents": [
{
"kind": "category",
"name": "Control",
"contents": [
{
"kind": "block",
"type": "controls_if"
},
{
"kind": "block",
"type": "controls_whileUntil"
},
{
"kind": "block",
"type": "controls_for"
}
]
},
{
"kind": "category",
"name": "Logic",
"contents": [
{
"kind": "block",
"type": "logic_compare"
},
{
"kind": "block",
"type": "logic_operation"
},
{
"kind": "block",
"type": "logic_boolean"
}
]
}
]
}
3.生成代码
- 将代码块拖动到工作区之后,就应该生成对应代码了
- 我们需要生成的是javascript的代码,所以引入'blockly/javascript'的库
- Block还支持生成的代码有Python,PHP,Lua,或Dart,按需引入相关库即可
import BlocklyJS from 'blockly/javascript';
- 定义一个生成代码按钮和一个显示生成代码的区域
<button style="position: fixed;left: 50px;top: 10px;" @click="block2code">生成代码</button>
<!-- 代码显示区 -->
<div style="background-color: lightgrey;width: 700px;text-align: left">
{{code?code:'请点击生成代码按钮'}}
</div>
- 生成代码
//将工作区作为参数传入,返回的是该工作区中的blocks组合块形成的代码
this.code = BlocklyJS.workspaceToCode(this.$refs.blocklyDiv.workspace)
4.执行代码
- 用简单粗暴eval
- 当然啦,eval执行将字符串转化为可执行的代码存在安全、执行函数的作用域、无法监控执行过程、无法断点和单步执行等问题,在以后的篇章中会优化
eval(this.code)
后续计划
Blockly
- 自定义block块
- blockly第三方组件使用
- 接入js-interpreter,步骤运行block块
- ......(想到啥写啥)