在前面的章節中,我們已經學習了 Blockly 的基本概念、安裝設置、使用方法、自定義積木以及程式碼生成。本章將探討如何將 Blockly 整合到現有的網站中,包括如何處理事件、儲存和讀取積木配置,以及響應式設計考量等方面。
將 Blockly 嵌入到網頁中是一個相對簡單的過程,但需要注意一些細節以確保良好的使用者體驗。
以下是將 Blockly 嵌入網頁的基本步驟:
1. 引入 Blockly 庫:在 HTML 頁面中引入必要的 Blockly 腳本檔案。
2. 創建容器元素:為 Blockly 工作區創建一個容器元素。
3. 初始化 Blockly 工作區:使用 `Blockly.inject()` 函數初始化工作區。
4. 配置工具箱:定義工具箱的內容和結構。
5. 處理事件:設置事件監聽器以響應使用者操作。
以下是一個完整的示例,展示如何將 Blockly 嵌入到網頁中:
Blockly 整合示例
Blockly 整合示例
JavaScript 程式碼
Python 程式碼
Blockly 提供了豐富的事件系統,允許您監聽和響應使用者的操作。
以下是一些常見的 Blockly 事件類型:
- Blockly.Events.BLOCK_CHANGE:積木屬性變更(如字段值)。
- Blockly.Events.BLOCK_CREATE:創建新積木。
- Blockly.Events.BLOCK_DELETE:刪除積木。
- Blockly.Events.BLOCK_MOVE:移動積木。
- Blockly.Events.VAR_CREATE:創建新變數。
- Blockly.Events.VAR_DELETE:刪除變數。
- Blockly.Events.VAR_RENAME:重命名變數。
- Blockly.Events.UI:使用者界面事件(如選擇積木)。
您可以使用 `workspace.addChangeListener()` 方法來監聽工作區事件:
workspace.addChangeListener(function(event) {
// 檢查事件類型
if (event.type === Blockly.Events.BLOCK_CHANGE) {
console.log('積木變更:', event);
} else if (event.type === Blockly.Events.BLOCK_CREATE) {
console.log('積木創建:', event);
} else if (event.type === Blockly.Events.BLOCK_DELETE) {
console.log('積木刪除:', event);
} else if (event.type === Blockly.Events.BLOCK_MOVE) {
console.log('積木移動:', event);
}
// 更新程式碼顯示
updateCodeDisplay();
});
function updateCodeDisplay() {
const jsCode = Blockly.JavaScript.workspaceToCode(workspace);
document.getElementById('jsCode').textContent = jsCode || '// 沒有積木';
const pythonCode = Blockly.Python.workspaceToCode(workspace);
document.getElementById('pythonCode').textContent = pythonCode || '# 沒有積木';
}
您也可以創建和觸發自定義事件:
// 創建自定義事件類
class CustomEvent extends Blockly.Events.Abstract {
constructor(block) {
super(block);
this.type = 'custom_event';
}
// 實現必要的方法
toJson() {
const json = super.toJson();
return json;
}
fromJson(json) {
super.fromJson(json);
}
}
// 註冊自定義事件類
Blockly.Events.registerClass(CustomEvent.prototype.type, CustomEvent);
// 觸發自定義事件
function triggerCustomEvent(block) {
const event = new CustomEvent(block);
Blockly.Events.fire(event);
}
在實際應用中,我們通常需要儲存使用者創建的積木配置,以便稍後載入。Blockly 提供了多種方式來序列化和反序列化工作區狀態。
XML 是 Blockly 傳統的序列化格式:
// 儲存工作區到 XML
function saveWorkspaceToXml() {
const xmlDom = Blockly.Xml.workspaceToDom(workspace);
const xmlText = Blockly.Xml.domToText(xmlDom);
// 儲存到本地存儲
localStorage.setItem('blocklyWorkspace', xmlText);
// 或者儲存到伺服器
fetch('/save-workspace', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ xml: xmlText }),
})
.then(response => response.json())
.then(data => {
console.log('儲存成功:', data);
})
.catch(error => {
console.error('儲存失敗:', error);
});
}
// 從 XML 載入工作區
function loadWorkspaceFromXml() {
// 從本地存儲載入
const xmlText = localStorage.getItem('blocklyWorkspace');
if (xmlText) {
workspace.clear();
const xmlDom = Blockly.Xml.textToDom(xmlText);
Blockly.Xml.domToWorkspace(xmlDom, workspace);
}
// 或者從伺服器載入
fetch('/load-workspace')
.then(response => response.json())
.then(data => {
if (data.xml) {
workspace.clear();
const xmlDom = Blockly.Xml.textToDom(data.xml);
Blockly.Xml.domToWorkspace(xmlDom, workspace);
}
})
.catch(error => {
console.error('載入失敗:', error);
});
}
JSON 是 Blockly 較新的序列化格式,提供了更多的靈活性:
// 儲存工作區到 JSON
function saveWorkspaceToJson() {
const json = Blockly.serialization.workspaces.save(workspace);
// 儲存到本地存儲
localStorage.setItem('blocklyWorkspaceJson', JSON.stringify(json));
// 或者儲存到伺服器
fetch('/save-workspace-json', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ workspace: json }),
})
.then(response => response.json())
.then(data => {
console.log('儲存成功:', data);
})
.catch(error => {
console.error('儲存失敗:', error);
});
}
// 從 JSON 載入工作區
function loadWorkspaceFromJson() {
// 從本地存儲載入
const jsonText = localStorage.getItem('blocklyWorkspaceJson');
if (jsonText) {
const json = JSON.parse(jsonText);
workspace.clear();
Blockly.serialization.workspaces.load(json, workspace);
}
// 或者從伺服器載入
fetch('/load-workspace-json')
.then(response => response.json())
.then(data => {
if (data.workspace) {
workspace.clear();
Blockly.serialization.workspaces.load(data.workspace, workspace);
}
})
.catch(error => {
console.error('載入失敗:', error);
});
}
在現代網頁設計中,響應式設計是非常重要的,以確保您的應用程式在各種設備上都能良好運行。
Blockly 工作區的大小應該能夠適應不同的螢幕尺寸:
// 監聽視窗大小變化
window.addEventListener('resize', function() {
// 調整 Blockly 工作區大小
Blockly.svgResize(workspace);
});
// 初始調整大小
Blockly.svgResize(workspace);
使用 CSS 媒體查詢來調整佈局:
/* 桌面佈局 */
.content {
display: flex;
flex-direction: row;
}
#blocklyDiv {
flex: 2;
height: 100%;
}
.output {
flex: 1;
padding: 20px;
}
/* 平板和手機佈局 */
@media (max-width: 768px) {
.content {
flex-direction: column;
}
#blocklyDiv {
height: 60vh;
}
.output {
height: 40vh;
overflow: auto;
}
}
確保您的 Blockly 應用程式在觸控設備上也能良好運行:
// 在初始化 Blockly 工作區時啟用觸控支援
const workspace = Blockly.inject('blocklyDiv', {
toolbox: toolbox,
// 其他配置...
touch: {
longPressDelay: 750, // 長按延遲(毫秒)
scrollDelay: 500, // 滾動延遲(毫秒)
tapDelay: 250 // 點擊延遲(毫秒)
}
});
讓我們通過一個實際案例來綜合應用上述知識,創建一個互動式的 Blockly 教學平台,它包含多個教學關卡,每個關卡都有特定的任務和目標。
Blockly 教學平台
Blockly 教學平台
關卡 1:基本操作
在這個關卡中,您需要使用基本積木來完成一個簡單的任務:計算兩個數字的和,並顯示結果。
程式碼
輸出
/* styles.css */
html, body {
height: 100%;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
}
.header {
background-color: #f5f5f7;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.level-selector {
display: flex;
align-items: center;
}
.level-selector select {
margin-left: 10px;
padding: 5px;
border-radius: 5px;
border: 1px solid #ccc;
}
.content {
display: flex;
flex: 1;
}
.task-panel {
width: 250px;
padding: 20px;
background-color: #f9f9f9;
overflow: auto;
}
#blocklyDiv {
flex: 1;
height: 100%;
}
.output-panel {
width: 300px;
padding: 20px;
background-color: #f9f9f9;
overflow: auto;
}
.code {
white-space: pre-wrap;
font-family: monospace;
background-color: #f0f0f0;
padding: 10px;
border-radius: 5px;
margin-bottom: 20px;
}
.output {
background-color: #fff;
padding: 10px;
border-radius: 5px;
border: 1px solid #ddd;
min-height: 100px;
margin-bottom: 20px;
}
button {
padding: 8px 16px;
background-color: #0066cc;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
margin-bottom: 10px;
}
button:hover {
background-color: #0055aa;
}
.task-buttons {
margin-top: 20px;
}
.footer {
background-color: #f5f5f7;
padding: 10px;
text-align: center;
}
/* 響應式設計 */
@media (max-width: 1024px) {
.content {
flex-direction: column;
}
.task-panel, .output-panel {
width: auto;
max-height: 200px;
}
#blocklyDiv {
height: 50vh;
}
}
// script.js
document.addEventListener('DOMContentLoaded', function() {
// 定義關卡配置
const levels = {
1: {
title: '關卡 1:基本操作',
description: '在這個關卡中,您需要使用基本積木來完成一個簡單的任務:計算兩個數字的和,並顯示結果。',
toolbox: {
kind: 'categoryToolbox',
contents: [
{
kind: 'category',
name: '數學',
colour: '%{BKY_MATH_HUE}',
contents: [
{ kind: 'block', type: 'math_number' },
{ kind: 'block', type: 'math_arithmetic' }
]
},
{
kind: 'category',
name: '輸出',
colour: '#5C81A6',
contents: [
{
kind: 'block',
type: 'text_print'
}
]
}
]
},
checkFn: function(code) {
// 檢查是否使用了數學運算和輸出
return code.includes('console.log') && code.includes('+');
},
hint: '嘗試使用數學積木來計算兩個數字的和,然後使用輸出積木來顯示結果。'
},
2: {
title: '關卡 2:條件判斷',
description: '在這個關卡中,您需要使用條件判斷積木來檢查一個數字是否大於 10,如果是,則輸出「大於 10」,否則輸出「不大於 10」。',
toolbox: {
kind: 'categoryToolbox',
contents: [
{
kind: 'category',
name: '邏輯',
colour: '%{BKY_LOGIC_HUE}',
contents: [
{ kind: 'block', type: 'controls_if' },
{ kind: 'block', type: 'logic_compare' },
{ kind: 'block', type: 'logic_operation' },
{ kind: 'block', type: 'logic_negate' },
{ kind: 'block', type: 'logic_boolean' }
]
},
{
kind: 'category',
name: '數學',
colour: '%{BKY_MATH_HUE}',
contents: [
{ kind: 'block', type: 'math_number' }
]
},
{
kind: 'category',
name: '文字',
colour: '%{BKY_TEXTS_HUE}',
contents: [
{ kind: 'block', type: 'text' }
]
},
{
kind: 'category',
name: '輸出',
colour: '#5C81A6',
contents: [
{
kind: 'block',
type: 'text_print'
}
]
}
]
},
checkFn: function(code) {
// 檢查是否使用了條件判斷和輸出
return code.includes('if') && code.includes('console.log') && code.includes('>');
},
hint: '使用「如果」積木來檢查一個數字是否大於 10,然後在不同的條件下使用輸出積木。'
},
// 更多關卡...
};
let currentLevel = 1;
let workspace;
// 初始化 Blockly 工作區
function initBlockly() {
if (workspace) {
workspace.dispose();
}
const level = levels[currentLevel];
workspace = Blockly.inject('blocklyDiv', {
toolbox: level.toolbox,
grid: {
spacing: 20,
length: 3,
colour: '#ccc',
snap: true
},
zoom: {
controls: true,
wheel: true,
startScale: 1.0,
maxScale: 3,
minScale: 0.3,
scaleSpeed: 1.2
},
trashcan: true,
scrollbars: true,
sounds: true
});
// 監聽工作區變化事件
workspace.addChangeListener(function(event) {
if (event.type === Blockly.Events.BLOCK_CHANGE ||
event.type === Blockly.Events.BLOCK_CREATE ||
event.type === Blockly.Events.BLOCK_DELETE ||
event.type === Blockly.Events.BLOCK_MOVE) {
// 生成 JavaScript 程式碼
const code = Blockly.JavaScript.workspaceToCode(workspace);
document.getElementById('codeDiv').textContent = code || '// 沒有積木';
}
});
// 調整工作區大小
Blockly.svgResize(workspace);
}
// 載入關卡
function loadLevel(levelNum) {
currentLevel = levelNum;
const level = levels[currentLevel];
document.getElementById('levelTitle').textContent = level.title;
document.getElementById('levelDescription').textContent = level.description;
document.getElementById('levelSelect').value = currentLevel;
initBlockly();
document.getElementById('codeDiv').textContent = '// 沒有積木';
document.getElementById('outputDiv').textContent = '';
}
// 初始載入第一關
loadLevel(1);
// 關卡選擇事件
document.getElementById('levelSelect').addEventListener('change', function() {
loadLevel(parseInt(this.value));
});
// 重置按鈕事件
document.getElementById('resetButton').addEventListener('click', function() {
workspace.clear();
document.getElementById('codeDiv').textContent = '// 沒有積木';
document.getElementById('outputDiv').textContent = '';
});
// 檢查按鈕事件
document.getElementById('checkButton').addEventListener('click', function() {
const code = Blockly.JavaScript.workspaceToCode(workspace);
const level = levels[currentLevel];
if (level.checkFn(code)) {
alert('恭喜!您已完成這個關卡的任務。');
// 如果有下一關,詢問是否前進
if (levels[currentLevel + 1]) {
if (confirm('是否前進到下一關?')) {
loadLevel(currentLevel + 1);
}
}
} else {
alert('任務尚未完成,請再試一次。');
}
});
// 提示按鈕事件
document.getElementById('hintButton').addEventListener('click', function() {
const level = levels[currentLevel];
alert(level.hint);
});
// 運行按鈕事件
document.getElementById('runButton').addEventListener('click', function() {
const code = Blockly.JavaScript.workspaceToCode(workspace);
const outputDiv = document.getElementById('outputDiv');
if (!code) {
outputDiv.textContent = '請先添加一些積木';
return;
}
// 清空輸出區域
outputDiv.textContent = '';
// 重定向 console.log 到輸出區域
const originalConsoleLog = console.log;
console.log = function() {
const output = Array.from(arguments).join(' ');
outputDiv.textContent += output + '\n';
};
try {
// 運行程式碼
eval(code);
} catch (e) {
outputDiv.textContent += '運行錯誤: ' + e.message;
} finally {
// 恢復原始的 console.log
console.log = originalConsoleLog;
}
});
// 監聽視窗大小變化
window.addEventListener('resize', function() {
Blockly.svgResize(workspace);
});
});
在將 Blockly 整合到網站時,以下是一些最佳實踐和注意事項:
性能優化:對於複雜的工作區,考慮使用虛擬化技術或分頁加載來優化性能。
安全性考慮:在運行生成的程式碼時,使用沙箱環境或其他安全機制,特別是在處理用戶生成的程式碼時。
可訪問性:確保您的 Blockly 實現考慮到可訪問性,包括鍵盤導航、屏幕閱讀器支持和高對比度模式等。
國際化:如果您的網站需要支援多種語言,使用 Blockly 的國際化功能來提供翻譯。
備份和恢復:實現自動保存和恢復功能,避免使用者丟失工作。
漸進式增強:設計您的應用程式,使其在不支援 JavaScript 或有限制的環境中仍能提供基本功能。
測試:在不同的瀏覽器和設備上徹底測試您的 Blockly 實現,確保它在各種環境中都能正常工作。
在本章中,我們探討了如何將 Blockly 整合到網站中,包括基本嵌入步驟、事件處理、儲存與讀取功能,以及響應式設計考量。我們還通過一個實際案例展示了如何創建一個互動式的 Blockly 教學平台。
Blockly 是一個強大而靈活的工具,可以為您的網站添加視覺化程式設計功能。通過本教學,您應該已經掌握了使用 Blockly 的基本知識和技能,能夠開始在自己的專案中實現視覺化程式設計。
無論您是創建教育工具、遊戲開發平台,還是其他需要視覺化程式設計的應用程式,Blockly 都能為您提供一個強大的基礎。希望本教學能夠幫助您充分利用 Blockly 的潛力,創建出令人驚嘆的視覺化程式設計體驗。