我在可汗学院的计算机编程课程中开发了一些程序,我想在可汗学院之外运行。怎么才能做到呢?
可汗学院使用Processing.js,一个与<canvas>
元素交互的JavaScript库。虽然Processing实际上是一门独立的语言,但可汗学院只使用javascript的Processing.js代码。
所以你需要设置一个导入Processing.js的网页,设置一个<canvas>
,并在画布上构建一个Processing.js实例。最后,你需要确保你的可汗学院代码有processingjs实例的所有成员范围(我这样做与with
),加上可汗学院的一些等效的小修改processingjs,如mouseIsPressed
和getImage
。
这是一些一直为我工作的样板。可能需要进一步的开发才能使其适用于更复杂的示例;当你发现不工作的例子时,请发表评论。
<!DOCTYPE html>
<html>
<head>
<title>JavaScript</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/processing.js/1.4.8/processing.min.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas = document.getElementById("canvas");
var processing = new Processing(canvas, function(processing) {
processing.size(400, 400);
processing.background(0xFFF);
var mouseIsPressed = false;
processing.mousePressed = function () { mouseIsPressed = true; };
processing.mouseReleased = function () { mouseIsPressed = false; };
var keyIsPressed = false;
processing.keyPressed = function () { keyIsPressed = true; };
processing.keyReleased = function () { keyIsPressed = false; };
function getImage(s) {
var url = "https://www.kasandbox.org/programming-images/" + s + ".png";
processing.externals.sketch.imageCache.add(url);
return processing.loadImage(url);
}
// use degrees rather than radians in rotate function
var rotateFn = processing.rotate;
processing.rotate = function (angle) {
rotateFn(processing.radians(angle));
};
with (processing) {
// INSERT YOUR KHAN ACADEMY PROGRAM HERE
}
if (typeof draw !== 'undefined') processing.draw = draw;
});
</script>
</body>
</html>
添加到Robert的回答:
Processing.js使用弧度作为角度值的默认值,Khan Academy JS使用度。如果您在上面Robert的代码中添加以下行(在with语句之前),那么您就可以使用来自KA的rotate命令。
var rotateFn = processing.rotate;
processing.rotate = function(angle) {
rotateFn(processing.radians(angle));
}
我已经创建了一个解决这个问题的脚本。您可以在.js文件顶部的注释中阅读它的文档https://github.com/vExcess/libraries/blob/main/runPJS.js
这是它最基本的用法:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Using PJS in HTML</title>
</head>
<body>
<script class="pjs-src" type="data">
// WRITE PROCESSING.JS CODE HERE
background(255, 0, 0);
</script>
<!-- import my script which will automatically run the PJS code -->
<script src="https://cdn.jsdelivr.net/gh/vExcess/libraries@main/runPJS.js"></script>
</body>
</html>
你可以在这里看到一个使用它的演示:https://www.khanacademy.org/computer-programming/how-to-use-easily-use-processingjs-in-html/6388181215264768
我的脚本将运行可汗学院版本的PJS意味着函数,如getImage()将工作(除了一些图像拒绝加载未知的原因),也angleMode默认为度。它还为您解决了异步加载图像的问题。您几乎可以运行可汗学院的任何程序,而无需对代码进行任何更改。我要注意的是,它目前不支持getSound/playSound from KA.
是的,抱歉我回答这个问题有点晚了。但是我已经自己弄明白了……
您可以克隆这个存储库,或者按照下面的说明操作:
或者你可以这样设置:在您的index.html中,将所有这3个文件的源代码:
https://raw.githubusercontent.com/Khan/processing-js/66bec3a3ae88262fcb8e420f7fa581b46f91a052/processing.jsProcessing.js:
loadKa.js:
这是将多个独立文件一起加载的生产代码:
!(function(window, JSON, localStorage)
{
function createProcessing()
{
var args = Array.prototype.slice.call(arguments);
args.push({ beginCode: "with(processing)n{", endCode: "}"});
var any = combine.apply(this, args);
this.cache = window.cache = {};
this.cache.loadedImages = window.cache.loadedImages = {};
this.cache.imageNames = window.cache.imageNames = [
"avatars/aqualine-sapling",
"avatars/aqualine-seed",
"avatars/aqualine-seedling",
"avatars/aqualine-tree",
"avatars/aqualine-ultimate",
"avatars/avatar-team",
"avatars/duskpin-sapling",
"avatars/duskpin-seed",
"avatars/duskpin-tree",
"avatars/duskpin-ultimate",
"avatars/leaf-blue",
"avatars/leaf-green",
"avatars/leaf-grey",
"avatars/leaf-orange",
"avatars/leaf-red",
"avatars/leaf-yellow",
"avatars/leafers-sapling",
"avatars/leafers-seed",
"avatars/leafers-seedling",
"avatars/leafers-tree",
"avatars/leafers-ultimate",
"avatars/marcimus",
"avatars/marcimus-orange",
"avatars/marcimus-purple",
"avatars/marcimus-red",
"avatars/mr-pants",
"avatars/mr-pants-green",
"avatars/mr-pants-orange",
"avatars/mr-pants-pink",
"avatars/mr-pants-purple",
"avatars/mr-pants-with-hat",
"avatars/mr-pink",
"avatars/mr-pink-green",
"avatars/mr-pink-orange",
"avatars/old-spice-man",
"avatars/old-spice-man-blue",
"avatars/orange-juice-squid",
"avatars/piceratops-sapling",
"avatars/piceratops-seed",
"avatars/piceratops-seedling",
"avatars/piceratops-tree",
"avatars/piceratops-ultimate",
"avatars/primosaur-sapling",
"avatars/primosaur-seed",
"avatars/primosaur-seedling",
"avatars/primosaur-tree",
"avatars/primosaur-ultimate",
"avatars/purple-pi",
"avatars/purple-pi-pink",
"avatars/purple-pi-teal",
"avatars/questionmark",
"avatars/robot_female_1",
"avatars/robot_female_2",
"avatars/robot_female_3",
"avatars/robot_male_1",
"avatars/robot_male_2",
"avatars/robot_male_3",
"avatars/spunky-sam",
"avatars/spunky-sam-green",
"avatars/spunky-sam-orange",
"avatars/spunky-sam-red",
"avatars/starky-sapling",
"avatars/starky-seed",
"avatars/starky-seedling",
"avatars/starky-tree",
"avatars/starky-ultimate",
"creatures/Hopper-Happy",
"creatures/Hopper-Cool",
"creatures/Hopper-Jumping",
"creatures/OhNoes",
"creatures/OhNoes-Happy",
"creatures/OhNoes-Hmm",
"cute/Blank",
"cute/BrownBlock",
"cute/CharacterBoy",
"cute/CharacterCatGirl",
"cute/CharacterHornGirl",
"cute/CharacterPinkGirl",
"cute/CharacterPrincessGirl",
"cute/ChestClosed",
"cute/ChestLid",
"cute/ChestOpen",
"cute/DirtBlock",
"cute/DoorTallClosed",
"cute/DoorTallOpen",
"cute/EnemyBug",
"cute/GemBlue",
"cute/GemGreen",
"cute/GemOrange",
"cute/GrassBlock",
"cute/Heart",
"cute/Key",
"cute/PlainBlock",
"cute/RampEast",
"cute/RampWest",
"cute/Rock",
"cute/RoofEast",
"cute/RoofNorth",
"cute/RoofNorthEast",
"cute/RoofNorthWest",
"cute/RoofSouth",
"cute/RoofSouthEast",
"cute/RoofSouthWest",
"cute/RoofWest",
"cute/Selector",
"cute/ShadowEast",
"cute/ShadowNorth",
"cute/ShadowNorthEast",
"cute/ShadowNorthWest",
"cute/ShadowSideWest",
"cute/ShadowSouth",
"cute/ShadowSouthEast",
"cute/ShadowSouthWest",
"cute/ShadowWest",
"cute/WoodBlock",
"cute/Star",
"cute/StoneBlock",
"cute/StoneBlockTall",
"cute/TreeShort",
"cute/TreeTall",
"space/girl2",
"space/girl3",
"space/girl4",
"space/girl5",
"space/healthheart",
"space/minus",
"space/octopus",
"space/planet",
"space/plus",
"space/rocketship",
"space/star",
"space/3",
"space/4",
"space/5",
"space/6",
"space/7",
"space/8",
"space/9"
];
window.links = {
proxyUrl : "https://cors-anywhere.herokuapp.com/",
image : ["https://www.kasandbox.org/third_party/javascript-khansrc/live-editor/build/images/",
"https://github.com/Khan/live-editor/tree/master/images",
"https://www.kasandbox.org/programming-images/"],
};
var self = this;
this.setup = function()
{
function code(processing)
{
processing.size(400, 400);
processing.background(255, 255, 255);
processing.angleMode = "degrees";
processing.mousePressed = function() {};
processing.mouseReleased = function() {};
processing.mouseMoved = function() {};
processing.mouseDragged = function() {};
processing.mouseOver = function() {};
processing.mouseOut = function() {};
processing.keyPressed = function() {};
processing.keyReleased = function() {};
processing.keyTyped = function() {};
processing.getSound = function(name)
{
return "Sound";
};
processing.playSound = function(sound)
{
console.log(sound + " is not supported yet...");
};
processing.getImage = function(name)
{
return (window.cache || self.cache).loadedImages[name] || processing.get(0, 0, 1, 1);
};
var lastGet = processing.get;
processing.get = function()
{
try{
return lastGet.apply(this, arguments);
}
catch(e)
{
if(arguments[2] !== 0 && arguments[3] !== 0)
{
console.log(e);
}else{
throw e;
}
}
};
processing.debug = function(event)
{
try{
return window.console.log.apply(this, arguments);
}
catch(e)
{
processing.println.apply(this, arguments);
}
};
processing.Program = {
restart: function()
{
window.location.reload();
},
assertEqual: function(equiv)
{
if(!equiv)
{
console.warn(equiv);
}
},
};
}
code = combine(new Function("return " + code.toString().split("n").join(" "))(), any);
var matched = code.toString().match("this[ ]*[[ ]*[[ ]*("KAInfiniteLoopSetTimeout")[ ]*][ ]*][ ]*([ ]*d*[ ]*);*");
if(matched)
{
code = new Function("return " + code.toString().replace(matched[0], ""))();
}
window.canvas = document.getElementById("canvas");
window.processing = new Processing(canvas, code);
};
this.imageProcessing = new Processing(canvas, function(processing)
{
try{
processing.imageCache = JSON.parse(localStorage.getItem("imageCache"));
}
catch(e)
{
console.log(e);
}
if(!processing.imageCache)
{
processing.imageCache = {};
}
processing.getImage = function(name, callback, url)
{
if(name === undefined) { return get(0, 0, 1, 1); }
url = url || window.links.image[0] + name.split(".")[0] + ".png";
callback = callback || function() {};
if(!processing.imageCache)
{
var img = processing.loadImage(url);
callback(img, name);
return img;
}
if(processing.imageCache[name])
{
var img = processing.loadImage(processing.imageCache[name]);
callback(img, name);
return img;
}
toDataURL(window.links.proxyUrl + url, function(dataUrl)
{
processing.imageCache[name] = dataUrl;
localStorage.setItem("imageCache", JSON.stringify(processing.imageCache));
callback(processing.imageCache[name], name);
});
return processing.loadImage(processing.imageCache[url] || url);
};
window.cache.imageNames.forEach(function(element, index, array)
{
processing.getImage(element, function(img, name)
{
window.cache.loadedImages[name] = img;
if(index === array.length - 1)
{
(window.setTimeout || function(func)
{
return func.apply(this, arguments);
})
(function()
{
self.setup();
}, 50);
}
});
});
});
}
function combine(a, c)
{
var args = Array.prototype.slice.call(arguments);
var config = {};
var funcArgs = "";
var join = "";
for(var i = 0; i < args.length; i++)
{
if(typeof args[i] === "object")
{
config = args[i];
continue;
}
var to = args[i].toString();
var temp = to.substring(to.indexOf('(') + 1, to.indexOf(')'));
if(temp !== "" && temp !== " ")
{
funcArgs += temp + ",";
}
join += to.slice(to.indexOf('{') + 1, -1);
}
funcArgs = funcArgs.slice(0, -1);
return new Function("return function any(" + funcArgs + "){" + (config.beginCode || "").replace("n", "") + join + (config.endCode || "") + "}")();
}
function toDataURL(url, callback)
{
var xhr = new XMLHttpRequest();
xhr.onload = function()
{
var reader = new FileReader();
reader.onloadend = function()
{
callback(reader.result);
}
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
}
return {
createProcessing: window.createProcessing = this.createProcessing = createProcessing,
toDataURL: window.toDataURL = this.toDataURL = toDataURL,
combine: window.combine = this.combine = combine,
};
}(
(window || {}),
(JSON || { stringify: function() { return "{}"; }, parse: function() { return {}; } }),
(localStorage || { getItem: function() { return {} }, setItem: function() {}, removeItem: function() {} })
));
Index.js:
在index.js文件中用法:
function main()
{
//Your code here (Trust me it really works!)
}
createProcessing(main);
还可以将更多参数作为函数添加到createProcessing函数中。是的,我什么都有,除了声音和有时(很少)夸张的控制!