JS 应用详解
安装
你需要以下软件来完成本教程,请继续阅读具体的安装说明
- Python
- Python 包管理器,例如 pip(通常随 Python 一起安装)或 uv
- FastHTML
- 网页浏览器
- Railway.app 账户
如果你以前没有接触过 Python,我们建议从 Miniconda 开始。
请注意,每个环境中你只需要执行一次安装部分的步骤。如果创建了一个新的仓库,你不需要重新进行这些操作。
安装 FastHTML
对于 Mac、Windows 和 Linux,请输入
pip install python-fasthtml第一步
在本节结束时,你将拥有自己的 FastHTML 网站,并带有测试,部署在 railway.app 上。
创建一个 hello world
创建一个新文件夹来组织你项目的所有文件。在此文件夹内,创建一个名为 main.py 的文件,并将以下代码添加到其中
main.py
from fasthtml.common import *
app = FastHTML()
rt = app.route
@rt('/')
def get():
return 'Hello, world!'
serve()最后,在你的终端中运行 python main.py,然后在浏览器中打开出现的“链接”。
QuickDraw:一次 FastHTML 探险 🎨✨
本教程的最终成果将是 QuickDraw,一个使用 FastHTML 的实时协作绘图应用。最终网站的外观如下

绘图室
绘图室是我们应用的核心概念。每个房间代表一个独立的绘图空间,用户可以在其中尽情挥洒他们内心的毕加索。以下是详细分解
- 房间的创建与存储
main.py
db = database('data/drawapp.db')
rooms = db.t.rooms
if rooms not in db.t:
rooms.create(id=int, name=str, created_at=str, pk='id')
Room = rooms.dataclass()
@patch
def __ft__(self:Room):
return Li(A(self.name, href=f"/rooms/{self.id}"))或者你可以使用我们的 fast_app 函数,用一行代码创建一个带有 SQLite 数据库和 dataclass 的 FastHTML 应用
main.py
def render(room):
return Li(A(room.name, href=f"/rooms/{room.id}"))
app,rt,rooms,Room = fast_app('data/drawapp.db', render=render, id=int, name=str, created_at=str, pk='id')我们正在指定一个渲染函数,将我们的 dataclass 转换为 HTML,这与我们之前使用的 patch 装饰器扩展 __ft__ 方法是相同的。在本教程的剩余部分,我们将使用这种方法,因为它更简洁、更易于阅读。
- 我们使用 SQLite 数据库(通过 FastLite)来存储我们的房间。
- 每个房间都有一个 id(整数)、一个 name(字符串)和一个 created_at 时间戳(字符串)。
- Room dataclass 会根据这个结构自动生成。
- 创建一个房间
main.py
@rt("/")
def get():
# The 'Input' id defaults to the same as the name, so you can omit it if you wish
create_room = Form(Input(id="name", name="name", placeholder="New Room Name"),
Button("Create Room"),
hx_post="/rooms", hx_target="#rooms-list", hx_swap="afterbegin")
rooms_list = Ul(*rooms(order_by='id DESC'), id='rooms-list')
return Titled("DrawCollab",
H1("DrawCollab"),
create_room, rooms_list)
@rt("/rooms")
async def post(room:Room):
room.created_at = datetime.now().isoformat()
return rooms.insert(room)- 当用户提交“创建房间”表单时,此路由被调用。
- 它会创建一个新的 Room 对象,设置创建时间,并将其插入数据库。
- 它返回一个带有新房间链接的 HTML 列表项,借助 HTMX,该列表项会动态添加到主页的房间列表中。
- 让我们为房间赋予形态
main.py
@rt("/rooms/{id}")
async def get(id:int):
room = rooms[id]
return Titled(f"Room: {room.name}", H1(f"Welcome to {room.name}"), A(Button("Leave Room"), href="/"))- 此路由渲染特定房间的界面。
- 它从数据库中获取房间信息,并渲染一个标题、一个标题和一个段落。
以下是到目前为止的完整代码
main.py
from fasthtml.common import *
from datetime import datetime
def render(room):
return Li(A(room.name, href=f"/rooms/{room.id}"))
app,rt,rooms,Room = fast_app('data/drawapp.db', render=render, id=int, name=str, created_at=str, pk='id')
@rt("/")
def get():
create_room = Form(Input(id="name", name="name", placeholder="New Room Name"),
Button("Create Room"),
hx_post="/rooms", hx_target="#rooms-list", hx_swap="afterbegin")
rooms_list = Ul(*rooms(order_by='id DESC'), id='rooms-list')
return Titled("DrawCollab", create_room, rooms_list)
@rt("/rooms")
async def post(room:Room):
room.created_at = datetime.now().isoformat()
return rooms.insert(room)
@rt("/rooms/{id}")
async def get(id:int):
room = rooms[id]
return Titled(f"Room: {room.name}", H1(f"Welcome to {room.name}"), A(Button("Leave Room"), href="/"))
serve()现在在你的终端中运行 python main.py,然后在浏览器中打开出现的“链接”。你应该会看到一个页面,上面有一个创建新房间的表单和现有房间的列表。
画布 - 让我们开始绘画吧!🖌️
是时候添加实际的绘图功能了。我们将为此使用 Fabric.js
main.py
# ... (keep the previous imports and database setup)
@rt("/rooms/{id}")
async def get(id:int):
room = rooms[id]
canvas = Canvas(id="canvas", width="800", height="600")
color_picker = Input(type="color", id="color-picker", value="#3CDD8C")
brush_size = Input(type="range", id="brush-size", min="1", max="50", value="10")
js = """
var canvas = new fabric.Canvas('canvas');
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = '#3CDD8C';
canvas.freeDrawingBrush.width = 10;
document.getElementById('color-picker').onchange = function() {
canvas.freeDrawingBrush.color = this.value;
};
document.getElementById('brush-size').oninput = function() {
canvas.freeDrawingBrush.width = parseInt(this.value, 10);
};
"""
return Titled(f"Room: {room.name}",
A(Button("Leave Room"), href="/"),
canvas,
Div(color_picker, brush_size),
Script(src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"),
Script(js))
# ... (keep the serve() part)现在我们有了一个绘图画布!FastHTML 使得包含外部库和添加自定义 JavaScript 变得容易。
保存和加载画布 💾
现在我们有了一个可用的绘图画布,让我们添加保存和加载绘图的功能。我们将修改数据库模式以包含一个 canvas_data 字段,并添加新的路由来保存和加载画布数据。以下是我们更新代码的方式
- 修改数据库模式
main.py
app,rt,rooms,Room = fast_app('data/drawapp.db', render=render, id=int, name=str, created_at=str, canvas_data=str, pk='id')- 添加一个保存按钮,获取画布状态并将其发送到服务器
main.py
@rt("/rooms/{id}")
async def get(id:int):
room = rooms[id]
canvas = Canvas(id="canvas", width="800", height="600")
color_picker = Input(type="color", id="color-picker", value="#3CDD8C")
brush_size = Input(type="range", id="brush-size", min="1", max="50", value="10")
save_button = Button("Save Canvas", id="save-canvas", hx_post=f"/rooms/{id}/save", hx_vals="js:{canvas_data: JSON.stringify(canvas.toJSON())}")
# ... (rest of the function remains the same)- 添加用于保存和加载画布数据的路由
main.py
@rt("/rooms/{id}/save")
async def post(id:int, canvas_data:str):
rooms.update({'canvas_data': canvas_data}, id)
return "Canvas saved successfully"
@rt("/rooms/{id}/load")
async def get(id:int):
room = rooms[id]
return room.canvas_data if room.canvas_data else "{}"- 更新 JavaScript 以加载现有的画布数据
main.py
js = f"""
var canvas = new fabric.Canvas('canvas');
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = '#3CDD8C';
canvas.freeDrawingBrush.width = 10;
// Load existing canvas data
fetch(`/rooms/{id}/load`)
.then(response => response.json())
.then(data => {{
if (data && Object.keys(data).length > 0) {{
canvas.loadFromJSON(data, canvas.renderAll.bind(canvas));
}}
}});
// ... (rest of the JavaScript remains the same)
"""通过这些更改,用户现在可以保存他们的绘图,并在返回房间时加载它们。画布数据以 JSON 字符串的形式存储在数据库中,便于序列化和反序列化。试试看!创建一个新房间,画一幅画,保存它,然后重新加载页面。你应该会看到你的画作重新出现,可以进行进一步的编辑。
这是完整的代码
main.py
from fasthtml.common import *
from datetime import datetime
def render(room):
return Li(A(room.name, href=f"/rooms/{room.id}"))
app,rt,rooms,Room = fast_app('data/drawapp.db', render=render, id=int, name=str, created_at=str, canvas_data=str, pk='id')
@rt("/")
def get():
create_room = Form(Input(id="name", name="name", placeholder="New Room Name"),
Button("Create Room"),
hx_post="/rooms", hx_target="#rooms-list", hx_swap="afterbegin")
rooms_list = Ul(*rooms(order_by='id DESC'), id='rooms-list')
return Titled("QuickDraw",
create_room, rooms_list)
@rt("/rooms")
async def post(room:Room):
room.created_at = datetime.now().isoformat()
return rooms.insert(room)
@rt("/rooms/{id}")
async def get(id:int):
room = rooms[id]
canvas = Canvas(id="canvas", width="800", height="600")
color_picker = Input(type="color", id="color-picker", value="#000000")
brush_size = Input(type="range", id="brush-size", min="1", max="50", value="10")
save_button = Button("Save Canvas", id="save-canvas", hx_post=f"/rooms/{id}/save", hx_vals="js:{canvas_data: JSON.stringify(canvas.toJSON())}")
js = f"""
var canvas = new fabric.Canvas('canvas');
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = '#000000';
canvas.freeDrawingBrush.width = 10;
// Load existing canvas data
fetch(`/rooms/{id}/load`)
.then(response => response.json())
.then(data => {{
if (data && Object.keys(data).length > 0) {{
canvas.loadFromJSON(data, canvas.renderAll.bind(canvas));
}}
}});
document.getElementById('color-picker').onchange = function() {{
canvas.freeDrawingBrush.color = this.value;
}};
document.getElementById('brush-size').oninput = function() {{
canvas.freeDrawingBrush.width = parseInt(this.value, 10);
}};
"""
return Titled(f"Room: {room.name}",
A(Button("Leave Room"), href="/"),
canvas,
Div(color_picker, brush_size, save_button),
Script(src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"),
Script(js))
@rt("/rooms/{id}/save")
async def post(id:int, canvas_data:str):
rooms.update({'canvas_data': canvas_data}, id)
return "Canvas saved successfully"
@rt("/rooms/{id}/load")
async def get(id:int):
room = rooms[id]
return room.canvas_data if room.canvas_data else "{}"
serve()部署到 Railway
你可以将你的网站部署到许多托管服务提供商,在本教程中,我们将使用 Railway。首先,请确保你创建了一个 账户 并安装了 Railway CLI。安装后,请务必运行 railway login 登录你的账户。
为了使部署你的网站尽可能简单,FastHTML 内置了一个 CLI 工具,可以为你处理大部分部署过程。要部署你的网站,请在项目的根目录下的终端中运行以下命令
fh_railway_deploy quickdraw你的应用必须位于一个 main.py 文件中才能正常工作。
结论:你现在是 FastHTML 艺术家了!🎨🚀
恭喜!你刚刚使用 FastHTML 构建了一个时尚、交互式的 Web 应用程序。让我们回顾一下我们学到的内容
- FastHTML 允许你用最少的代码创建动态 Web 应用。
- 我们使用 FastHTML 的路由系统来处理不同的页面和操作。
- 我们集成了 SQLite 数据库来存储房间信息和画布数据。
- 我们利用 Fabric.js 创建了一个交互式绘图画布。
- 我们实现了颜色选择、画笔大小调整和画布保存等功能。
- 我们使用 HTMX 实现了无缝的局部页面更新,无需完全重新加载。
- 我们学习了如何将我们的 FastHTML 应用部署到 Railway 以便轻松托管。
你已经迈出了进入 FastHTML 开发世界的第一步。从这里开始,可能性是无限的!你可以通过添加以下功能进一步增强绘图应用
- 实现不同的绘图工具(例如,形状、文本)
- 添加用户身份验证
- 创建一个已保存绘图的画廊
- 使用 WebSockets 实现实时协作绘图
无论你接下来选择构建什么,FastHTML 都会支持你。现在,去创造一些了不起的东西吧!编程愉快!🖼️🚀