Gradio 自定义 HTML 组件实战:用gr.HTML构建可交互的自定义前端组件
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
导读
本文聚焦 Gradio 官方指南《Custom Components withgr.HTML》,系统讲解如何借助gr.HTML组件在纯 Python 应用中注入任意 HTML/CSS/JavaScript,从而把高度定制的前端交互能力(星级评分、自定义文件上传、图表渲染、布局容器等)接入 Gradio 的事件系统。读完本文,你将掌握html_template/css_template双模板语法、js_on_load中的props/trigger/upload/watch/server编程模型、@children子组件占位,以及通过api_info/data_model接入 API 与 MCP、通过push_to_hub发布共享等完整技能。
从一行 HTML 开始
gr.HTML的核心价值是:它的value就是一段 HTML 字符串,Gradio 会原样渲染到页面中。最简单的用法如下:
import gradio as gr gr.HTML(value="<h1>Hello World!</h1>")单行静态 HTML 只是起点。真实场景中往往需要把动态数据(Python 端传入的value、后端返回的组件状态)渲染进 HTML 骨架,这就引出html_template模板机制。
模板化:{{}}与${}两种语法并存
gr.HTML支持在html_template中混用两种模板语法:
${}:内嵌 JavaScript 表达式(模板字符串求值),可执行任意自定义 JS 逻辑,例如调用数组方法、字符串拼接;{{}}:Handlebars 结构化模板,用于#each循环、#if条件等场景。
最简单的注入示例:
gr.HTML( value="John", html_template="<h1>Hello, {{value}}!</h1><p>${value.length} letters</p>", )渲染结果为:
<h1>Hello, John!</h1><p>4 letters</p>注意value在两种语法中都可以被引用:{{value}}直接输出字符串,${value.length}则把它当作 JS 变量求值。再看一个渲染列表的完整示例(对应仓库演示 demo/star_rating_simple/run.py 所同属的一批 HTML 演示):
gr.HTML( value=["apple", "banana", "cherry"], html_template=""" <h1>${value.length} fruits:</h1> <ul> {{#each value}} <li>{{this}}</li> {{/each}} </ul> """, )这里${value.length}负责输出数组长度,{{#each value}}负责遍历数组,{{this}}取出当前项。二者配合即能完成“动态数据 + 结构化布局”的组合。
默认样式、apply_default_css与css_template
默认情况下,gr.HTML内容会套用一部分与 Gradio 主题一致的默认 CSS。若想完全脱离主题、裸渲染自己的内容,可设置apply_default_css=False。同时,你可以通过css_template传入组件专属的 CSS。
在 gradio/components/html.py 的构造函数文档中可以看到:css_template同样按 JS 模板字符串与 Handlebars 语法求值,且CSS 会被自动限定(scoped)到该组件作用域——不带选择器前缀的规则直接作用于组件根元素。这一机制让多实例组件之间互不串扰。
下面的星形评分示例同时用到了css_template:
import gradio as gr with gr.Blocks() as demo: three_star_rating = gr.HTML(""" <h2>Star Rating:</h2> <img src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'> <img src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'> <img src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'> <img class='faded' src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'> <img class='faded' src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'> """, css_template=""" img { height: 50px; display: inline-block; } .faded { filter: grayscale(100%); opacity: 0.3; } """) if __name__ == "__main__": demo.launch()完整可运行版本见 demo/star_rating_simple/run.py。
响应式更新:模板随value自动重渲染
gr.HTML并非静态标签——当组件作为某个 Python 事件监听器的输出时,模板会自动用新value重渲染。将上文的静态星星改为由value动态驱动的版本:
import gradio as gr with gr.Blocks() as demo: star_rating = gr.HTML( value=3, html_template=""" <h2>Star Rating:</h2> ${Array.from({length: 5}, (_, i) => `<img class='${i < value ? '' : 'faded'}' src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'>`).join('')}""", css_template=""" img { height: 50px; display: inline-block; } .faded { filter: grayscale(100%); opacity: 0.3; } """) rating_slider = gr.Slider(0, 5, 3, step=1, label="Select Rating") rating_slider.change(fn=lambda x: x, inputs=rating_slider, outputs=star_rating) if __name__ == "__main__": demo.launch()拖动滑块时,value变化会触发模板中${...}表达式的重新求值,星星的亮/灰状态随之刷新。完整演示位于 demo/star_rating_templates/run.py。
传递任意 props:kwargs驱动模板与样式
除value外,模板里可以引用任意自定义属性——只需把模板中出现的占位名作为关键字参数传给gr.HTML,它们即会进入props。下例为评分组件增加size(星星像素尺寸)与max_stars(星星总数),并且html_template与css_template都能访问这些额外 props:
import gradio as gr with gr.Blocks() as demo: star_rating = gr.HTML( 7, size=40, max_stars=10, html_template=""" <h2>Star Rating:</h2> ${Array.from({length: max_stars}, (_, i) => `<img class='${i < value ? '' : 'faded'}' src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'>`).join('')}""", css_template=""" img { height: ${size}px; display: inline-block; } .faded { filter: grayscale(100%); opacity: 0.3; } """ ) rating_slider = gr.Slider(0, 10, step=1, label="Select Rating") rating_slider.change(fn=lambda x: x, inputs=rating_slider, outputs=star_rating) size_slider = gr.Slider(20, 100, 40, step=1, label="Select Size") size_slider.change(fn=lambda x: gr.HTML(size=x), inputs=size_slider, outputs=star_rating) if __name__ == "__main__": demo.launch()更关键的是:这些 props 也可以通过 Gradio 事件监听器被更新。例如size_slider.change返回gr.HTML(size=x),组件便会带着新size重渲染模板与 CSS。演示文件见 demo/star_rating_props/run.py。
用js_on_load触发事件,构建自定义输入组件
gr.HTML不仅能展示内容,还能当输入组件用。机制是给组件提供js_on_load——一段在组件加载时执行的 JavaScript。这段代码的作用域内有两个关键对象:
element:组件自身的 DOM 根元素(可用element.querySelector(...)取子元素);props:组件全部 props 的引用对象,包括value;trigger('事件名', 数据):触发可被 Python 侧监听的事件;- 函数
upload(file):把 JSFile对象上传到 Gradio 服务器,返回含path(服务端路径)与url(公网访问地址)的对象; - 函数
watch(...):监听 prop 变化回调(见后文); server:在声明了server_functions时出现的异步方法集合(见后文)。
下面为星形评分组件加交互:点击星星通过props.value = index + 1更新分值(模板随即重渲染),点击提交按钮触发submit事件:
import gradio as gr with gr.Blocks() as demo: star_rating = gr.HTML( value=3, html_template=""" <h2>Star Rating:</h2> ${Array.from({length: 5}, (_, i) => `<img class='${i < value ? '' : 'faded'}' src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'>`).join('')} <button id='submit-btn'>Submit Rating</button> """, css_template=""" img { height: 50px; display: inline-block; cursor: pointer; } .faded { filter: grayscale(100%); opacity: 0.3; } """, js_on_load=""" const imgs = element.querySelectorAll('img'); imgs.forEach((img, index) => { img.addEventListener('click', () => { props.value = index + 1; }); }); const submitBtn = element.querySelector('#submit-btn'); submitBtn.addEventListener('click', () => { trigger('submit'); }); """) rating_output = gr.Textbox(label="Submitted Rating") star_rating.submit(lambda x: x, inputs=star_rating, outputs=rating_output) if __name__ == "__main__": demo.launch()在 Python 端,star_rating.submit(...)就是普通的事件监听调用(gr.HTML在 gradio/components/html.py 中声明EVENTS = all_events,因此.submit、.click等各类事件均可直接挂接)。对应演示见 demo/star_rating_events/run.py。
向事件传递数据:gr.EventData
trigger可携带任意数据,Python 监听函数通过gr.EventData读取:
trigger('event_name', { key: value, count: 123 });def handle_event(evt: gr.EventData): print(evt.key) # 对应 value print(evt.count) # 对应 123 star_rating.event(fn=handle_event, inputs=[], outputs=[])事件的命名是自由的:只要该事件名以带引号的形式出现在js_on_load字符串中(例如trigger('select')),就能在 Python 端写成component.select(fn, ...)。
动态元素的监听技巧
js_on_load只在组件首次渲染时执行一次。如果你的组件会在运行时动态创建需要绑事件的新元素,应当把监听器挂到加载时即存在且不会消失的父元素上,再用e.target.matches(...)判断实际点击目标:
element.addEventListener('click', (e) => { if (e.target && e.target.matches('.child-element')) { props.value = e.target.dataset.value; } });内置upload:把 JSFile传给 Python
js_on_load作用域内置的异步函数upload可直接上传浏览器File对象到 Gradio 服务器:
const { path, url } = await upload(file);据此可用纯 HTML 构建一个文件上传小部件。完整的自定义上传演示见 demo/html_upload/run.py,其核心逻辑如下:
import gradio as gr from pathlib import Path with gr.Blocks() as demo: file_uploader = gr.HTML( html_template=""" <div> <input type="file" id="file-input" accept=".txt,text/plain" /> <button id="upload-btn" style="margin-left: 8px;">Upload</button> </div> """, js_on_load=""" const input = element.querySelector('#file-input'); const btn = element.querySelector('#upload-btn'); btn.addEventListener('click', async () => { const file = input.files[0]; const { path } = await upload(file); props.value = path; }); """, elem_id="file_uploader" ) view_content_btn = gr.Button("View Uploaded File Content") upload_content = gr.Textbox(label="Uploaded File Content") view_content_btn.click(lambda path: Path(path).read_text(), file_uploader, upload_content) if __name__ == "__main__": demo.launch()用户选择本地文件后,前端把File上传得到服务端path,再写回props.value,于是 Python 端click回调可以直接用该路径读文件内容。
监听 prop 变化:watch
当gr.HTML作为 Python 事件监听器的输出时,js_on_load内的watch函数可在特定 props 变化后执行回调,回调内直接读取props即可拿到最新值:
// 监听单个 prop watch('value', () => { console.log('value is now:', props.value); }); // 同时监听多个 props watch(['value', 'color'], () => { console.log('value or color changed'); });这在需要“Python 端更新了输出 → 前端做出附带反应”的场景(如联动动画、二次渲染)中很实用。
通过head加载第三方脚本库
head参数可向文档<head>注入原生 HTML(典型为<script>/<link>标签),用于加载外部 JS/CSS 库。head内容会在js_on_load运行前被注入并加载完毕,因此js_on_load中可以立即使用该库。用 Chart.js 画柱状图示例:
gr.HTML( value=[30, 70, 45, 90, 60], html_template="<canvas id='chart'></canvas>", js_on_load=""" new Chart(element.querySelector('#chart'), { type: 'bar', data: { labels: props.value.map((_, i) => 'Item ' + (i + 1)), datasets: [{ label: 'Values', data: props.value }] } }); """, head='<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>', )在 gradio/components/html.py 的参数说明中,官方特别提醒:head中的脚本会按src、样式按href去重,多个组件需要同一库时不会被重复加载。需要留意的是,浏览器不会执行通过innerHTML插入的<script>标签,html.py 里有一处专门的告警逻辑:若value/模板内容含有<script>,会提示改用head或js_on_load。
调用 Python:server_functions
把 Python 函数列表传给server_functions后,它们会成为js_on_load中server对象的异步方法。下例把os.listdir风格的list_files暴露给前端按钮调用(见 demo/html_server_functions/run.py):
import os import gradio as gr def list_files(path): try: return os.listdir(path) except (FileNotFoundError, PermissionError) as e: return [f"Error: {e}"] with gr.Blocks() as demo: gr.Markdown("# Server Functions Demo\nClick 'Load Files' to list files in the directory.") filetree = gr.HTML( value=os.path.dirname(__file__), html_template=""" <div> <p>Directory: <strong>${value}</strong></p> <div class='tree'></div> <button class='load-btn'>Load Files</button> </div> """, js_on_load=""" const loadBtn = element.querySelector('.load-btn'); const tree = element.querySelector('.tree'); loadBtn.addEventListener('click', async () => { const files = await server.list_files(props.value); tree.innerHTML = ''; files.forEach(file => { const fileEl = document.createElement('div'); fileEl.textContent = file; tree.appendChild(fileEl); }); }); """, server_functions=[list_files], ) if __name__ == "__main__": demo.launch()封装可复用组件类
如果同一套 HTML 模板在多处复用,应把gr.HTML子类化,将模板与配置固化进构造函数。仓库演示 demo/star_rating_component/run.py 是一个典型实现:
import gradio as gr class StarRating(gr.HTML): def __init__(self, label, value=0, **kwargs): html_template = """ <h2>${label} rating:</h2> ${Array.from({length: 5}, (_, i) => `<img class='${i < value ? '' : 'faded'}' src='https://upload.wikimedia.org/wikipedia/commons/d/df/Award-star-gold-3d.svg'>`).join('')} """ css_template = """ img { height: 50px; display: inline-block; cursor: pointer; } .faded { filter: grayscale(100%); opacity: 0.3; } """ js_on_load = """ const imgs = element.querySelectorAll('img'); imgs.forEach((img, index) => { img.addEventListener('click', () => { props.value = index + 1; }); }); """ super().__init__(value=value, label=label, html_template=html_template, css_template=css_template, js_on_load=js_on_load, **kwargs) def api_info(self): return {"type": "integer", "minimum": 0, "maximum": 5}随后在 Blocks 里如同普通组件一样实例化、参与数据流:
with gr.Blocks() as demo: gr.Markdown("# Restaurant Review") food_rating = StarRating(label="Food", value=3) service_rating = StarRating(label="Service", value=3) ambience_rating = StarRating(label="Ambience", value=3) average_btn = gr.Button("Calculate Average Rating") rating_output = StarRating(label="Average", value=3) def calculate_average(food, service, ambience): return round((food + service + ambience) / 3) average_btn.click( fn=calculate_average, inputs=[food_rating, service_rating, ambience_rating], outputs=rating_output ) if __name__ == "__main__": demo.launch()注意:Gradio 要求所有组件构造器接受某些内部参数(如render)。你无需处理这些参数,但必须在其上接受并转发给父类。最省事的写法就是在__init__中加上**kwargs并原样传给super().__init__(),否则组件行为可能异常——这正是上文StarRating.__init__(self, label, value=0, **kwargs)的用意。
仓库内 gradio/components/custom_html_components 目录收录了audio_gallery.py、colored_checkbox_group.py等子类化示例,可作为参考。
用@children把其他 Gradio 组件嵌入 HTML
gr.HTML是BlockContext子类(见 gradio/components/html.py),因此可作with容器使用。在html_template顶层放置@children占位符,with块内声明的 Gradio 组件便会渲染到该位置——用这种方式能用 HTML/CSS 自由编排自定义布局。
约束:@children必须位于模板顶层,不能被任何 HTML 标签嵌套;若要给子组件容器加样式或交互,需像普通 CSS 一样直接定位包裹子组件的父元素。
演示见 demo/html_children/run.py:
import gradio as gr with gr.Blocks() as demo: with gr.HTML(html_template=''' <button class="maximize">⛶</button> <h2>${form_name}</h2> @children <button class="submit">Submit</button> ''', css_template=''' border: 2px solid gray; border-radius: 12px; padding: 20px; .maximize { position: absolute; top: 10px; right: 10px; background: none; border: none; z-index: 1000; } ''', js_on_load=''' element.querySelector('.submit').addEventListener('click', () => { trigger('submit'); }); element.querySelector('.maximize').addEventListener('click', () => { element.requestFullscreen(); }); ''', form_name="Custom Form") as form: name = gr.Textbox(label="Name") email = gr.Textbox(label="Email") output = gr.Textbox(label="Output") form.submit(lambda name, email: f"Name: {name}, Email: {email}", inputs=[name, email], outputs=output) demo.launch()这里的 Name / Email 两个 Textbox 会替换@children占位符出现在“自定义表单卡片”内部;卡片外层边框、右上角最大化按钮等样式/脚本均作用于容器的父元素。
让组件接入 API 与 MCP
要使自定义 HTML 组件适配 Gradio 内置的 API(含 MCP,即 Model Context Protocol)能力,必须定义其数据的序列化格式,官方提供两种方式:
方式一:定义api_info()方法,返回描述数据格式的 JSON Schema 字典。上面的StarRating.api_info()返回整数区间约束即为此法:
def api_info(self): return {"type": "integer", "minimum": 0, "maximum": 5}方式二:定义 Pydantic 数据模型,适用于更复杂的数据结构。模型需继承GradioModel(数据为具名字段的字典)或GradioRootModel(数据为字符串、列表等无需字典包装的简单类型):
from gradio.data_classes import GradioModel, GradioRootModel class MyComponentData(GradioModel): items: List[str] count: int class MyComponent(gr.HTML): data_model = MyComponentData定义data_model后,组件会自动实现 API 方法,无需手写api_info()。
共享组件:push_to_hub
对任意gr.HTML实例或子类调用push_to_hub,即可把组件推送到社区画廊(HTML Components Gallery),供他人浏览、交互并复制 Python 代码:
star_rating = StarRating() star_rating.push_to_hub( name="Star Rating", description="Interactive 5-star rating with click-to-rate", author="your-hf-username", tags=["input", "rating"], repo_url="https://github.com/your-username/your-repo", )这会向画廊的 HuggingFace 数据集仓库发起一个 Pull Request;审核通过后组件即公开展示。两点使用提示:
push_to_hub自身也有head参数,需要专门关注:如果组件通过head='<script src="...">'加载了外部库,请把同样的head字符串传给push_to_hub,以便画廊渲染组件时也能加载这些脚本;- 推送需要 HuggingFace写权限 token,可直接传参
push_to_hub(..., token="hf_xxxxx"),或提前执行huggingface-cli login使用缓存凭据。
安全注意事项
用gr.HTML做自定义组件,本质是向应用注入原始 HTML 与 JavaScript,因此必须防范注入风险:
- 不要把不可信的用户输入拼进
html_template与js_on_load,否则可能引发跨站脚本(XSS)漏洞; - 凡是以
gr.HTML组件为输入的任何 Python 事件监听器,都可能收到任意值,而非仅前端期望写入value的合法取值——公网应用应对用户输入做充分校验与净化。
延伸阅读
- 若需进一步了解各组件实例,可查看同目录指南 07_custom-CSS-and-JS.md,配合自定义前端交互;
- 组件基类与完整参数定义见 gradio/components/html.py,其中的 docstring 提供了官方示例、
head/server_functions/watch/upload等参数的权威说明; - 可复用子类组件范例见 gradio/components/custom_html_components(含
audio_gallery.py、colored_checkbox_group.py); - 仓库中
super_html等大量演示(demo/super_html/run.py 即为gr.HTML官方引用 demo)可用于对照学习。
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考