html中运行python代码

文章说明

文章初始是为了实现在HTML页面中运行python的绘图代码,其中主要为turtle的代码,然后先后采用了 pyodide、flask、Brython 这三个技术

最初采用 pyodide 的时候研究了蛮久的,但是一直不行,无法将绘图绑定到页面元素

后续转为采用 flask 进行服务提供,然后在后台绘图,绘图完成后将内容传到前台;效果上还是可以的,但是仍然存在着环境部署相关的复杂性

最后采用 Brython 来实现绘图操作,效果不错,且环境部署简单

下面简单介绍这三个内容

pyodide的绘图版本

  1. 采用pyodide实现在html中运行python代码
  2. 由于采用cdn下来pyodide的相关资源的话,会下的很慢,然后报错,可直接采用本地加载
  3. 由于无法绑定页面元素,所以无法实现turtle的绘图操作

核心代码

运行简单的python代码

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>使用 Pyodide 的简单示例</title>
    <script src="./resource/pyodide.js"></script>
</head>

<body>

    <h1>欢迎来到 Pyodide 示例页面</h1>

    <button type="button" onclick="runPythonCode()">点击运行 Python 代码</button>

    <script type="text/javascript">
        let pyodide;

        async function initPyodide() {
            // 初始化 Pyodide
            pyodide = await loadPyodide();
            alert("Pyodide 已初始化");
        }

        async function runPythonCode() {
            if (!pyodide) {
                alert("正在初始化,请稍后再试...");
                return;
            }

            // 运行一些 Python 代码
            let result = pyodide.runPython(`
                def say_hello(name):
                    return "你好, " + name + "!"
                say_hello("世界")
            `);

            // 显示结果
            alert(result);
        }

        // 页面加载完成后初始化 Pyodide
        window.onload = async () => {
            await initPyodide();
        };
    </script>

</body>

</html>

运行turtle代码

<!DOCTYPE html>
<html lang="zh">

<head>
  <meta charset="UTF-8">
  <title>Pyodide Turtle 示例</title>
  <style>
    .container {
      display: flex;
      height: 90vh;
    }

    #editor {
      width: 50%;
      padding: 10px;
      box-sizing: border-box;
    }

    #canvas-container {
      width: 50%;
      position: relative;
    }

    canvas {
      border: 1px solid black;
      width: 100%;
      height: 100%;
    }
  </style>
  <!-- 加载 Pyodide -->
  <script src="./resource/pyodide.js"></script>
</head>

<body>
  <div class="container">
    <textarea id="editor" placeholder="在这里输入turtle代码...">
import turtle

t = turtle.Turtle()
t.forward(100)
t.left(90)
t.forward(100)
    </textarea>
    <div id="canvas-container">
      <canvas id="myCanvas"></canvas>
    </div>
  </div>
  <button onclick="runTurtleCode()">运行 Turtle 代码</button>

  <script type="text/javascript">
    let pyodide;

    async function initPyodide() {
      // 初始化 Pyodide
      pyodide = await loadPyodide({
        indexURL: "./resource/"
      });
      alert("Pyodide 已初始化");
    }

    async function installTurtlePackage() {
      await pyodide.loadPackage("./resource/turtle-0.0.1-py3-none-any.whl");
    }

    async function runTurtleCode() {
      if (!pyodide) {
        alert("正在初始化,请稍后再试...");
        return;
      }

      const editor = document.getElementById('editor');
      const code = editor.value;

      try {
        // 清空画布内容
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 设置canvas大小
        canvas.width = 600;
        canvas.height = 400;

        // 如果未安装turtle包,则先安装
        if (!pyodide.globals.has('turtle')) {
          await installTurtlePackage();
        }

        // 使用 Pyodide 运行 Python turtle 代码
        await pyodide.runPythonAsync(`
from js import document
import turtle

# 设置turtle使用的canvas
# screen = turtle.Screen()
# screen.cv._js_object = document.getElementById('myCanvas')
        `);

        // 执行用户提供的turtle代码
        await pyodide.runPythonAsync(code);
      } catch (error) {
        console.log(error);
        alert("执行出错:" + error.toString());
      }
    }

    window.onload = async () => {
      await initPyodide();
    };
  </script>
</body>

</html>

效果展示

在这里插入图片描述

在这里插入图片描述

flask实现绘图

采用flask提供绘图服务,在后台进行绘制,绘制完成后将图像传到前台展示

核心源码

app.py

from flask import Flask, render_template_string, send_file, request
import io
import base64
import turtle
from PIL import Image
from multiprocessing import Process, Queue
import os

app = Flask(__name__)

@app.route('/')
def index():
    template = '''
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Turtle Graphics</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                display: flex;
                justify-content: center;
                align-items: center;
                height: 100vh;
                margin: 0;
                background-color: #e6f7ff; /* 浅蓝色背景 */
                color: #333;
            }
            .container {
                text-align: center;
                background: #d9f0ff; /* 浅蓝色背景 */
                padding: 30px;
                border-radius: 15px;
                box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
                max-width: 600px;
                width: 100%;
            }
            h1 {
                margin-bottom: 20px;
                color: #007bff; /* 深蓝色标题 */
            }
            textarea {
                width: 100%;
                height: 150px;
                margin-bottom: 20px;
                padding: 10px;
                border: 1px solid #a0d9ff; /* 浅蓝色边框 */
                border-radius: 8px;
                font-size: 14px;
                resize: none;
                transition: border-color 0.3s ease;
                box-sizing: border-box;
                outline: none;
            }
            textarea:focus {
                border-color: #007bff; /* 聚焦时的深蓝色边框 */
            }
            canvas {
                border: 1px solid #a0d9ff; /* 浅蓝色边框 */
                border-radius: 8px;
                margin-bottom: 20px;
                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            }
            button {
                padding: 12px 24px;
                font-size: 16px;
                cursor: pointer;
                background-color: #007bff; /* 深蓝色按钮 */
                color: white;
                border: none;
                border-radius: 8px;
                transition: background-color 0.3s ease;
            }
            button:hover {
                background-color: #0056b3; /* 悬停时更深的蓝色 */
            }
        </style>
    </head>
    <body>
        <div class="container">
            <h1>Turtle Graphics Demo</h1>
            <textarea id="turtleCode" placeholder="Enter your Turtle code here"></textarea>
            <canvas id="turtleCanvas" width="500" height="500"></canvas>
            <button onclick="drawTurtleGraphics()">Draw Turtle Graphics</button>
        </div>
        <script>
            function drawTurtleGraphics() {
                const turtleCode = document.getElementById('turtleCode').value;
                fetch('/image', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ code: turtleCode })
                })
                .then(response => response.blob())
                .then(blob => {
                    const img = new Image();
                    img.src = URL.createObjectURL(blob);
                    img.onload = () => {
                        const canvas = document.getElementById('turtleCanvas');
                        const ctx = canvas.getContext('2d');
                        ctx.clearRect(0, 0, canvas.width, canvas.height);
                        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                    };
                });
            }
        </script>
    </body>
    </html>
    '''
    return render_template_string(template)

def run_turtle_code(user_code, queue):
    """执行Turtle代码并返回结果"""
    screen = turtle.Screen()
    screen.setup(500, 500)
    t = turtle.Turtle()
    t.speed(0)

    try:
        exec(user_code, {'turtle': turtle, 't': t})
        canvas = screen.getcanvas()
        ps = canvas.postscript(colormode='color')
        img = Image.open(io.BytesIO(ps.encode('utf-8')))
        queue.put(img)
    except Exception as e:
        queue.put(str(e))
    finally:
        turtle.bye()

@app.route('/image', methods=['POST'])
def image():
    user_code = request.json.get('code', '')
    queue = Queue()
    p = Process(target=run_turtle_code, args=(user_code, queue))
    p.start()
    result = queue.get()
    p.join()

    if isinstance(result, str): # 如果结果是一个字符串,则它代表了一个异常信息
        return result, 500

    png_img_io = io.BytesIO()
    result.save(png_img_io, format='PNG')
    png_img_io.seek(0)

    return send_file(png_img_io, mimetype='image/png')

if __name__ == '__main__':
    app.run(debug=True)

效果展示

在这里插入图片描述

Brython实现绘图

采用Brython 可在JavaScript直接运行python代码

核心源码

测试图像绘制页面

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Brython with Turtle</title>
    <script src="./brython.min.js"></script>
    <script src="./brython_stdlib.min.js"></script>
    <script src="./turtle.min.js"></script>
</head>

<body onload="brython()">
    <h1>Turtle Graphics in Brython</h1>

    <script type="text/python">
        from turtle import *

        def draw_square():
            for _ in range(4):
                forward(100)
                right(90)

        def main():
            speed('fastest')
            draw_square()
            done()

        main()
    </script>
</body>

</html>

效果展示

在这里插入图片描述

Brython实现绘图-弹窗展示版本

采用Brython 可在JavaScript直接运行python代码,调整为页面输入代码,同时弹窗展示绘图结果,然后将输出重定向到页面的展示元素

核心源码

测试图像绘制页面

<!DOCTYPE html>
<html lang="zh-cn">

    <head>
        <meta charset="UTF-8">
        <title>Brython with Turtle</title>
        <script src="./brython.min.js"></script>
        <script src="./brython_stdlib.min.js"></script>
        <script src="./turtle.min.js"></script>
        <style>
            body {
                font-family: Arial, sans-serif;
                background-color: #f4f4f4;
                margin: 0;
                padding: 20px;
            }

            h1 {
                color: #333;
            }

            .container {
                max-width: 800px;
                margin: auto;
                background: white;
                padding: 20px;
                border-radius: 8px;
                box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            }

            textarea {
                width: 100%;
                height: 300px;
                padding: 10px;
                border: 1px solid #ccc;
                border-radius: 4px;
                margin-bottom: 10px;
                box-sizing: border-box;
                outline: none;
                resize: none;
                font-size: 1.2rem;
            }

            textarea:focus {
                border: 1px solid #007bff;
            }

            button {
                padding: 10px 20px;
                background-color: #007bff;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
            }

            button:hover {
                background-color: #0056b3;
            }

            #turtle-canvas-wrapper {
                display: none !important;
            }
        </style>
    </head>

    <body onload="brython()">
        <div class="container">
            <h1>Turtle Graphics in Brython</h1>
            <textarea id="turtleCode" placeholder="Enter your Turtle code here..."></textarea>
            <button onclick="runTurtleCode()" id="run">Run</button>
            <pre id="outputConsole"
                 style="background-color: #eee; padding: 10px; border-radius: 4px; margin-top: 10px;"></pre>
        </div>

        <script type="text/javascript">
            function runTurtleCode() {
                const code = document.getElementById('turtleCode').value;
                // 使用时间戳来创建一个唯一的窗口名称
                const windowName = 'TurtleGraphics_' + new Date().getTime();
                const popupWindow = window.open('', windowName, 'width=800,height=600');

                popupWindow.document.write(`
                    <!DOCTYPE html>
                    <html lang="zh-cn">
                    <head>
                        <title>Turtle Graphics Result</title>
                        <script src="./brython.min.js"><\/script>
                        <script src="./brython_stdlib.min.js"><\/script>
                        <script src="./turtle.min.js"><\/script>
                        <style>
                            body {
                                display: flex;
                                justify-content: center;
                                align-items: center;
                                height: 100vh;
                                margin: 0;
                                background-color: #f4f4f4;
                            }
                        </style>
                    </head>
                    <body onload="brython()">
                        <script type="text/python">${code}<\/script>
                    </body>
                    </html>
                `);
                popupWindow.document.close();

                document.getElementById("outputConsole").innerHTML = "";
            }
        </script>

        <script type="text/python">
import sys
from browser import document

class MyOutput:
    def __init__(self):
        self.console = document["outputConsole"]
    def write(self, text):
        self.console.text += text

sys.stdout = MyOutput()

def run(event):
    code = document["turtleCode"].value
    exec(code)

document["run"].bind("click", run)

        </script>
    </body>

</html>

效果展示

在这里插入图片描述

源码下载

html运行python代码

参考资料

其中Brython的输出重定向参考了这篇文章:如何将所有 Brython 输出重定向到 textarea 元素
同时Brython也参考了官方的示例demo:brython

其中flask的绘图版本中,还参考了Ghostscript的安装指导:python利用Turtle绘图并保存jpg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值