可汗学院的计算机程序如何离线运行或在我自己的网站上运行



我在可汗学院的计算机编程课程中开发了一些程序,我想在可汗学院之外运行。怎么才能做到呢?

可汗学院使用Processing.js,一个与<canvas>元素交互的JavaScript库。虽然Processing实际上是一门独立的语言,但可汗学院只使用javascript的Processing.js代码。

所以你需要设置一个导入Processing.js的网页,设置一个<canvas>,并在画布上构建一个Processing.js实例。最后,你需要确保你的可汗学院代码有processingjs实例的所有成员范围(我这样做与with),加上可汗学院的一些等效的小修改processingjs,如mouseIsPressedgetImage

这是一些一直为我工作的样板。可能需要进一步的开发才能使其适用于更复杂的示例;当你发现不工作的例子时,请发表评论。

<!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个文件的源代码:

Processing.js:

https://raw.githubusercontent.com/Khan/processing-js/66bec3a3ae88262fcb8e420f7fa581b46f91a052/processing.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函数中。是的,我什么都有,除了声音和有时(很少)夸张的控制!

最新更新