代码之家  ›  专栏  ›  技术社区  ›  Robert Tupelo-Schneck

Khan Academy的计算机程序如何离线运行或在我自己的网站上运行?

  •  12
  • Robert Tupelo-Schneck  · 技术社区  · 12 年前

    我在 Khan Academy's Computer Programming lessons 我想在汗学院外面跑步。如何做到这一点?

    4 回复  |  直到 12 年前
        1
  •  17
  •   Robert Tupelo-Schneck    11 年前

    Khan Academy使用 Processing.js ,一个JavaScript库,用于与 <canvas> 元素。虽然加工实际上是一种语言,但Khan Academy使用 JavaScript-only Processing.js code .

    因此,您需要设置一个导入Processing.js的网页 <画布> ,并在画布上构建Processing.js实例。最后,您需要确保Khan Academy代码中包含Processing.js实例的所有成员(我使用 with ),加上相当于 Khan Academy's small modifications to Processing.js 喜欢 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>
    
        2
  •  3
  •   Michael Jay    11 年前

    罗伯特回答补充:

    Processing.js使用弧度作为角度值的默认值,Khan Academy js使用度。如果在上面的Robert代码中添加以下行(在with语句之前),那么可以使用来自KA的rotate命令。

    var rotateFn = processing.rotate;
    processing.rotate = function(angle) {
        rotateFn(processing.radians(angle));
    }
    
        3
  •  1
  •   user9927483 user9927483    7 年前

    是的,对不起,我问这个问题有点晚了。但我已经自己弄明白了。。。

    您可以克隆 this repository 或按照以下说明操作:

    或者你可以这样设置: 在您的 索引html 将源文件放在所有3个文件中:

    正在处理.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() {} })
    ));
    

    索引.js:

    在您的 索引js 文件 用法:

    function main()
    {
          //Your code here (Trust me it really works!)
    }
    
    createProcessing(main);
    

    还可以将更多参数作为函数添加到 创建处理 作用 是的,除了声音和有时(很少)炒作的控件,我什么都有!

        4
  •  1
  •   Vexcess    4 年前

    我已经创建了一个解决这个问题的脚本。您可以在.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

    我的脚本将运行Khan Academy版本的PJS,这意味着getImage()等函数将正常工作(除了某些图像因未知原因拒绝加载),而且angleMode默认为度。它还为您解决了异步加载图像的问题。您可以运行汗学院的几乎任何程序,而无需对代码进行任何修改。我会注意到,它目前不支持KA的getSound/playSound。

    推荐文章