ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Blockly入门:从可视化编程到自定义积木开发

Blockly入门:从可视化编程到自定义积木开发 1. 这玩意到底是什么为什么各个大厂都在用它先给结论Blockly 不是一款面向消费者的软件而是一套开源的、用于构建“可视化编程界面”的前端工具库。也就是说它是给“程序员”用的“积木编辑器生成器”。你用浏览器打开乐高官网搭积木、用 Scratch 做小游戏、在 App Inventor 里拖拖拽拽做安卓应用、甚至在某些机器人教育硬件配套软件里看到的图形化编程界面——它们的底层很可能就是 Blockly 或它的衍生版本。我第一次接触 Blockly 是在给一家教育公司做教学平台的时候。当时的需求很明确孩子们不懂 JavaScript但我们又希望他们能通过“拖动积木”来编写控制小车的逻辑然后实时看到生成的代码。调研了一圈发现 GitHub App Inventor 系列、micro:bit 官方编辑器、以及国内外大量少儿编程平台核心骨架都指向 Google 开源的 Blockly。那一刻我就知道与其自己从零去画拖拽交互、做积木拼接算法不如把这个经过十余年验证的“轮子”直接用起来。它能解决什么问题简单说通过图形化积木让非专业人员也能构建逻辑同时自动生成等价的专业代码。这句话拆分出来解决的是两个痛点降低编程门槛用户不用记语法不用管分号括号只需要关注“逻辑单元”的拼接。保留代码能力积木背后可以挂载 JavaScript、Python、PHP、Lua 等目标语言代码拖出来的逻辑等于写出来的代码方便迁移和学习。适合谁读这篇教程如果你是面向教育领域的前端工程师、STEAM 教育的创业者、企业内部低代码平台的开发者或者纯粹对“拖拽式编程如何实现”感到好奇的同学这篇“初识”就是为你准备的。我会尽量从“这东西是怎么设计出来的”角度讲而不是上来就丢一堆 API。2. 理解 Blockly 的灵魂五个核心概念2.1 工作区Workspace舞台不是凭空冒出来的Blockly 的界面核心是Workspace。你可以把它理解成一张无限大的画布积木就在这块画布上被拖进来、排列、嵌套、组合。在代码里它对应于Blockly.Workspace类而且区分一个隐藏概念WorkspaceSvgSVG 渲染的可见工作区和Workspace数据模型工作区。刚开始学不用纠结这个区别但你要知道界面上的积木状态本质上是一份 JSON 结构数据工作区做得再多最终要保存或传输的就是这份 JSON。实际项目里我们一般通过Blockly.inject()方法把工作区“注入”到页面的某个div中const workspace Blockly.inject(blocklyDiv, { toolbox: document.getElementById(toolbox), scrollbars: true, trashcan: true, grid: { spacing: 20, length: 3, colour: #ccc, snap: true } });inject这个命名很有意思说明 Blockly 不是把你整个网页变成编程环境而是“嵌入”到你的页面里成为一个组件。用过iframe嵌第三方编辑器的人会懂这个设计有多友好——它不会污染页面的全局 CSS、不会和你的路由体系冲突你只管传递数据进去。2.2 工具箱Toolbox积木从哪里来工具箱就是工作区左侧那一列积木栏。积木按类别分组比如“逻辑”“循环”“数学”“变量”“函数”等。工具箱的定义格式有两种XML 字符串格式更常见也更容易通过代码生成JSON 格式较新版本支持更结构化下面是一段 XML 格式的工具箱定义xml idtoolbox styledisplay: none category name逻辑 colour#5C81A6 block typecontrols_if/block block typelogic_compare/block /category category name数学 colour#5CA65C block typemath_number/block block typemath_arithmetic/block /category /xml注意两个细节styledisplay: none是必须的因为工具箱本身不直接显示在页面上而是被 Blockly 渲染成工作区左侧的面板colour属性决定分类标题的颜色方便用户快速区分。我记得第一次写的时候忘了加display: none结果页面顶部凭空多出一块 XML 内容排查了半天。一个容易忽略的设置是categorystyle分类样式。在复杂项目中我们会预定义若干种分类样式名然后在多个工具箱里复用这样能保证整个产品视觉统一不会出现每个页面颜色都随心所欲的情况。2.3 积木块Block拼接的是“形状”与“插口”Blockly 里的积木块并不仅仅是一张漂亮的图。每个积木块有类型type唯一标识比如logic_compare。输入插口inputs可以连接其他积木的位置分为“值输入”value input和“语句输入”statement input。字段fields可编辑的文本、下拉框、颜色选择器等。连接器connections上一个/下一个连接点负责语句块的纵向拼接。如果你只是想用现成积木那不需要写任何块定义。但如果你要定制积木几乎定制是必然的否则和 Scatch 有什么区别就绕不开“块定义”这一步。用 JSON 格式定义一个最简单的“打印文本”积木{ type: custom_print, message0: 打印 %1, args0: [ { type: input_value, name: TEXT } ], previousStatement: null, nextStatement: null, colour: 160, tooltip: 在控制台输出文本, helpUrl: }这段定义描述的是一个语句块它有一个名为TEXT的值输入插口。previousStatement: null和nextStatement: null表示它可以在语句序列中被连接colour: 160是色相值Blockly 内部用 0-360 的角度值来标记颜色而不是直接用十六进制色码。这一设计很巧妙因为你在工具箱里可以直接用同一个色相统一整类积木的视觉风格。2.4 代码生成器Code Generator拖拽积木是怎么变成代码的这是 Blockly 最让我“哇”一声的设计。Blockly 生成代码并不依赖运行时解释而是基于字符串模板的拼接。以 JavaScript 生成器为例每个积木类型都要注册一个生成函数这个函数的职责是读取当前积木的字段值、递归获取它的“子积木”生成的代码片段然后把它们拼成一段目标代码字符串。举个例子上面定义的custom_print积木对应生成器可以写成Blockly.JavaScript[custom_print] function(block) { const text Blockly.JavaScript.valueToCode(block, TEXT, Blockly.JavaScript.ORDER_ATOMIC); const code console.log( text );\n; return code; };valueToCode是这里的灵魂函数。它负责去取连接到TEXT插口上的那个积木所生成的代码并指定运算优先级ORDER_ATOMIC。如果你接的是一个算术表达式块它生成的字符串就能被正确地插入到console.log(...)的括号里不会因为优先级问题出现括号错乱。再强调一下代码生成是“递归解析”每一层积木只负责把自己那部分代码拼好然后交还给父级。理解了这个模型你之后写任何自定义块的生成器都会很顺手因为你只需要思考“我这块的输入是什么、我要用什么语句包裹它们”。2.5 渲染器Renderer积木不是好看的贴图Blockly 对积木外观的实现方式很几何化每个积木块是基于预先定义的路径模板绘制出来的 SVG 形状不同积木的区别在于“插口数量”“连接位置”和“分支结构”的组合。举个例子一个controls_if积木带有一个“如果”插口和一个“如果为真则执行”的语句插口呈现出的就是一个带“C”形槽的块。这个 C 形槽不是图片素材它是通过矢量路径实时算出来的。所以即便你的积木宽度动态变化比如字段文本变长整个块的锯齿形底部、凸起的连接点也会跟着自动重绘不会有拉伸失真问题。这个机制带来的最大好处你可以通过自定义渲染器彻底改变积木的视觉风格——圆角矩形、无边框、扁平化配色、更紧凑的间距统统可以做。对于品牌定制要求高的教育产品来说这是刚需。3. 零代码体验最快 10 分钟跑起一个积木应用很多人一听到“开发”两个字就以为要先搭工程、配编译环境。其实 Blockly 官方提供了一个非常友好的入口Blockly Developer Tools开发者工具。它甚至不需要你写任何前端工程代码直接在浏览器里就能完成积木的定义、预览、生成代码然后导出成一个完整可运行的 HTML 文件。3.1 使用官方开发者工具快速原型验证打开 blockly-demo.appspot.com/static/demos/blockfactory/index.html 你会看到左侧是“积木类型创建区”中间是实时预览工作区右侧是自动生成的代码。这个工具对于初学 Blockly 的人来说极其重要它有三大用途通过可视化方式构建自定义积木然后导出对应的 JSON 定义。实时测试积木在工作区中的外观、连接是否合理。自动生成积木的init函数或 JSON 定义、代码生成器示例、语言定义文件等。我习惯的做法是先在这个工具里把块的形状“搭”出来确认交互手感没问题再复制 JSON 定义回项目里使用。而不是直接写 JSON因为 JSON 里每个字段的含义对新人不友好一旦少了某个args0子项块可能直接渲染不出来排查起来很痛苦。3.2 用官方 Code Lab 跑通完整流程另一个新人友好型资源是Blockly Code Lab它是一个交互式学习环境界面左边是文字讲解右边是真实可编辑的代码区可以实时看到运行结果。建议把 01 到 08 的课程全部过一遍因为它是目前少有的对“代码生成器generator”“自定义块定义”都做了细致演示的教学工具。在 Code Lab 里你会学到一个最简可运行的 HTML 模板核心结构如下!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleBlockly Demo/title link relstylesheet hrefhttps://unpkg.com/blockly/css/blocks.css /head body div idblocklyDiv stylewidth: 100%; height: 600px;/div pre idgeneratedCode/pre script srchttps://unpkg.com/blockly/blockly_compressed.js/script script srchttps://unpkg.com/blockly/blocks_compressed.js/script script srchttps://unpkg.com/blockly/javascript_compressed.js/script script srchttps://unpkg.com/blockly/msg/zh-hans.js/script script const workspace Blockly.inject(blocklyDiv, { toolbox: xml category name逻辑 colour#5C81A6 block typecontrols_if/block /category category name文本 colour#5CA65C block typetext/block /category /xml , toolboxPosition: start }); workspace.addChangeListener(() { const code Blockly.JavaScript.workspaceToCode(workspace); document.getElementById(generatedCode).innerText code; }); /script /body /html这段代码的工作流程非常好理注入工作区 → 定义工具箱 → 监听内容变化 → 实时生成 JavaScript 代码并显示在页面底部。你拖入一个“如果”积木然后在里面放一个“文本”积木右边代码区就会立刻生成对应的控制流语句。这种“所见即所得”的反馈对于教初学者理解“图形化逻辑和代码的对应关系”简直是杀手级功能。3.3 如何把工作区内容保存和加载实际的商业化项目离不开“保存用户作品”。Blockly 工作区保存有标准方案调用Blockly.serialization.workspaces.save(workspace)获得 JSON 对象然后JSON.stringify后存到后端加载时用Blockly.serialization.workspaces.load(json, workspace)还原。这是较新版本的推荐方式。老版本用的是workspaceToDom/domToWorkspace的 XML 序列化方式网上大量旧教程都在讲这个但现在官方路线已经逐渐以 JSON 为主。我的建议是新项目直接用 JSON 序列化不要再踩 XML 的旧坑。JSON 可读性更强和前端生态的融合度更高而且在版本升级时兼容性维护更好。4. 第一次动手做一个“打招呼”的自定义积木纸上得来终觉浅。我建议你用一节真实的自定义积木开发把概念串起来。这一节我用一个最简单的“打招呼”积木把块定义、语言文件、代码生成器、事件监听全部走一遍。4.1 定义一个带下拉选项的积木需求积木形状是一个语句块内容为“对 XX 说你好”其中“XX”是从下拉框里选一个人物如“老师”“同学”“爸爸”。这么设计是为了同时演示“字段类型”和“值输入”的区别。JSON 块定义{ type: say_hello, message0: 对 %1 说 你好, args0: [ { type: field_dropdown, name: PERSON, options: [ [老师, teacher], [同学, classmate], [爸爸, father] ] } ], previousStatement: null, nextStatement: null, colour: 210, tooltip: 向指定人物打招呼 }这里field_dropdown的两个关键点显示文本和实际取值可以不同界面显示“老师”底层代码生成时拿到的是teacher。这个设计很常见比如界面显示中文生成代码用英文或者界面用单词简写、代码用完整变量名。4.2 注册块定义与生成器块定义注册如下Blockly.defineBlocksWithJsonArray([sayHelloJson]);代码生成器Blockly.JavaScript[say_hello] function(block) { const person block.getFieldValue(PERSON); const code console.log(你好 ${person});\n; return code; };getFieldValue(PERSON)从积木实例中读取当前选中的下拉值然后拼到代码里。注意这里是字符串模板直接插值所以最终生成的代码是console.log(你好 teacher);——很直白适合教育场景。4.3 加一点国际化让积木显示中文但生成英文代码不少做教育产品的同学一开始会把积木文本直接写死成中文比如message0: 对 %1 说你好生成代码时也直接把中文字符串输出短期能跑但后续如果要做多语言版本就会非常难改。Blockly 官方提供了msg系统把积木里所有用户可见文本抽出来定义为一个带 key 的存根。Blockly.Msg.SAY_HELLO_MESSAGE0 对 %1 说 你好; Blockly.Msg.SAY_HELLO_TOOLTIP 向指定人物打招呼;然后在块定义里引用{ type: say_hello, message0: %{BKY_SAY_HELLO_MESSAGE0}, tooltip: %{BKY_SAY_HELLO_TOOLTIP} }%{BKY_...}是一种延迟替换语法Blockly 在渲染积木时会去Blockly.Msg表里找到真实的字符串。以后想翻译成日语、英语只需要加载对应的 msg 文件而不需要动块定义代码。这是被很多教程一笔带过但实际项目里特别重要的设计。5. 踩坑经验我从初学 Blockly 到生产落地的避坑笔记这里记录几个我实际开发中真实踩过的坑以及同行交流中频繁被提到的疑难杂症统一梳理成速查表。这些问题单独拿出来都不难查但组合起来足以劝退新手所以值得单独开一节。常见问题典型表现排查思路与解决方式积木块显示为空白工具箱里能看到分类名但里面是空白的块定义没有被正确加载。检查是否调用了Blockly.defineBlocksWithJsonArray且 JSON 结构合法。积木拖出来了但无法连接语句块不能拼到另一个语句块下方缺previousStatement/nextStatement字段。值类型积木不能直接和语句类型插口连接这是 Blockly 的类型检查机制。代码生成报undefined值输入口没接积木valueToCode返回了空字符串这是正常行为而非 Bug。需要自己处理“输入为空”的情况返回默认值或提示用户补充逻辑。工作区页面被其他 CSS 影响积木变形、工具箱定位错乱Blockly 的 SVG 渲染对继承样式敏感。检查全局是否设置了* { box-sizing: border-box }或 CSS 统一样式覆盖到了 Blockly 的 class。中文输入法下数字块编辑卡顿用户在数字积木里输入中文后丢失焦点老版本 Blockly 的 field_input 对 IME 兼容不好。升级到较新版本或在初始化时设置media路径和renderer选项。工作区数据加载时积木重叠多块积木被恢复到了同一位置序列化 JSON 中的每个块都有x、y坐标字段如果坐标丢失就会叠放。确认保存的数据完整不能只存块列表。关于工具链的另一个建议不要用 CDN 的在线脚本做生产环境。本地开发时用 unpkg 或 jsdelivr 很方便但生产环境最好把blockly_compressed.js、blocks_compressed.js、javascript_compressed.js、zh-hans.js下载下来做本地静态资源。因为 Blockly 体积较大压缩后大约 600KB 级别CDN 不稳定会影响首屏加载速度而且离线教学场景很多教室没有外网要求必须本地化部署。代码分割方面标准压缩文件里已经内置了所有官方积木块的定义和英文语言包。如果你只用到少数几种积木可以考虑用Blockly.defineBlocksWithJsonArray按需注册子集配合自定义的语言文件能有效削减打包体积。我做过一次极端优化只保留逻辑、循环、数学三类积木打包后比默认体积减少了约 40%。另外提醒一下国内开发者Blockly 官方文档站点访问速度不稳定用镜像或本地文档会舒服很多。GitHub 上的google/blockly仓库里demos目录有大量可运行的示例直接把整个仓库 clone 下来跑一个本地静态服务器比在线读 API 文档的效率高得多。6. 通过一个综合示例串起整个流程前面讲了概念、示例和避坑这一节我建议你动手做一个更完整的示例一个“小明的一天”小练习。整个页面分为左中右三栏左侧工具箱中间工作区右侧展示生成代码。工具箱中包含“开始”积木、“行动”积木自定义、“循环”积木和“数字”积木。6.1 设计需求假设我们在做一个儿童教育游戏让小朋友编排小明一天的行程然后点击“执行”按钮浏览器控制台依次输出行程。这个教学内容的核心是“顺序结构”和“循环结构”所以积木设计要足够简单直观。6.2 三个自定义积木的完整代码定义“开始”积木{ type: start_block, message0: 开始, nextStatement: null, colour: 300 }定义“行动”积木{ type: action_block, message0: 做 %1, args0: [ { type: field_dropdown, name: ACTION, options: [ [起床, get up], [吃饭, eat], [学习, study], [睡觉, sleep] ] } ], previousStatement: null, nextStatement: null, colour: 60 }定义“重复循环”积木{ type: repeat_times, message0: 重复 %1 次 %2, args0: [ { type: input_value, name: TIMES, check: Number }, { type: input_statement, name: DO } ], previousStatement: null, nextStatement: null, colour: 120 }对应 JavaScript 生成器Blockly.JavaScript[start_block] function() { return ; }; Blockly.JavaScript[action_block] function(block) { const action block.getFieldValue(ACTION); return console.log(${action});\n; }; Blockly.JavaScript[repeat_times] function(block) { const times Blockly.JavaScript.valueToCode(block, TIMES, Blockly.JavaScript.ORDER_ATOMIC) || 1; const branch Blockly.JavaScript.statementToCode(block, DO); const code for (let i 0; i ${times}; i) {\n${branch}}\n; return code; };这里新增了statementToCode它的作用是把嵌入到DO语句插口里的所有积木生成代码拼接成一段代码块然后包进 for 循环的花括号里。你会发现它的返回值里自带换行和缩进这是生成器规范的一部分——把缩进直接拼进代码字符串而不是靠前端工具美化因为输送出去的目标代码可能是在 Node.js 或浏览器控制台直接执行的没有格式化环节。6.3 执行代码的按钮逻辑为了让示例更像一个完整应用可以加一个按钮点击时执行右侧生成的代码function runCode() { const code Blockly.JavaScript.workspaceToCode(workspace); try { // 使用 Function 构造器避免直接使用 eval const runnable new Function(code); runnable(); } catch (e) { console.error(运行出错, e); } }用new Function而不是eval有几个好处作用域隔离更好不会直接污染当前作用域在浏览器中性能略优代码看起来也更清楚。但注意这种动态执行代码的方式只适合受控场景如果是教学平台、用户自己拖出的代码那么冲突风险和注入风险都存在——你的积木集合本身限制了能生成代码的形式所以风险是可控的如果你是做开放编辑器就得另外考虑沙箱方案。7. 接下里往哪走学习路线参考看完这篇“初识”以后你已经知道 Blockly 的整体框架了。下一步建议按这个顺序走第一步把官方 Code Lab 的所有关卡过一遍尤其是自定义块的练习至少亲手定义 5 种不同类型的积木。第二步熟悉 Blockly 事件机制。会区分Blockly.Events.BLOCK_CREATE、BLOCK_MOVE、BLOCK_CHANGE并理解为什么做在线协作编辑时需要用事件来同步工作区状态。第三步学习自定义主题 Theme。了解如何通过主题配置改变工作区背景、块默认颜色、字体等。第四步考虑将 Blockly 与你的后端语言结合。当前端工作区里的积木要真正运行在服务器上时你需要后端也具备对应语言的代码生成器或者把前端生成的代码 JSON 传到后端解释执行。根据我个人实际经验这里面最容易忽略的是“代码生成的执行环境”问题。前端生成了一段 JavaScript可能在浏览器里能跑但是如果你要把它转成 Python 发给用户就要同时维护两套生成器。好在 Blockly 官方已经自带 JavaScript、Python、PHP、Ruby、Lua 等语言的生成器你可以在同一个块定义下注册多个语言生成函数一套积木多语言输出这是做教育出海产品的必杀技。最后再分享一个技巧开发自定义积木的时候给块定义加一个helpUrl字段指向你自己的文档页。这项工作看似不起眼但你的积木数量一旦超过 30 个使用者学生或老师就非常依赖这个入口去查看积木说明能省下大量答疑的时间。
返回列表