Gradio 自定义 CSS 与 JavaScript 完全指南:从样式注入到事件级前端函数 Gradio 自定义 CSS 与 JavaScript 完全指南从样式注入到事件级前端函数【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradioGradio 内置的主题Theme机制可以快速改变应用观感但在构建生产级 ML 演示时我们往往还需要定制动画、页面head元信息、埋点统计乃至纯前端交互逻辑。本文以仓库中 guides/03_building-with-blocks/07_custom-CSS-and-JS.md 为主线系统讲解在 Gradio 中注入自定义 CSS、为组件指定选择器、以及通过三种方式添加 JavaScript 代码的完整方法。读完本文你将掌握launch()/事件监听器/head三个维度的定制能力并能写出可稳定运行的自定义样式与脚本。一、自定义 CSS 的两条基础途径要让应用长得不一样首先应该考虑 Gradio 的主题Theme系统。在启动时把主题对象传给launch()的theme参数即可切换整套观感import gradio as gr with gr.Blocks() as demo: # ... your code here ... demo.launch(themegr.themes.Glass())gr.themes.*命名空间提供了一组预置主题同时支持在它们之上扩展或从零创建自定义主题更完整的主题能力见 theming-guide 与 themes 指南。当主题无法满足更高自由度的视觉定制时可以向launch()传入css参数其值是一段 CSS 字符串。从当前仓库 gradio/blocks.py 的launch()签名可以看到与样式定制相关的参数共有四组launch()参数类型作用cssstr直接以字符串传入的自定义 CSS注入演示页面css_pathsstr/Path/Sequence一个或多个 CSS 文件路径启动时被读取、拼接后注入。若与css同时设置css字符串的内容会放在前面headstr自定义 HTML 代码注入演示页面的head可放 meta 标签、脚本、样式表等head_pathsstr/Path/Sequence一个或多个 HTML 文件路径读取拼接后注入headhead字符串内容优先其中css_paths、head_paths的读取并拼接逻辑实现在 gradio/blocks.py先把路径规整为列表逐个read()文件后以换行符追加到已有的css/head字符串之后最终统一注入页面。也就是说把样式写在独立.css文件中与直接写字符串在结果上是等价的只是更利于工程化维护。作用于整个应用的容器选择器.gradio-containerGradio 应用根元素的基础类名是gradio-container因此一条最简单的全局样式可以这样写import gradio as gr with gr.Blocks() as demo: # ... your code here ... demo.launch(css.gradio-container {background-color: red})Blocks和Interface都适用上述注入方式Interface同样继承自Blocks其启动入口一致。在 CSS 中引用外部文件如果自定义 CSS 需要引用图片等外部资源路径需要以/gradio_api/file为前缀后接相对或绝对路径import gradio as gr with gr.Blocks() as demo: # ... your code here ... demo.launch(css.gradio-container {background: url(/gradio_api/fileclouds.jpg)})安全提醒默认情况下宿主机上的大部分文件对访问应用的浏览器用户并不可见。示例中被引用的clouds.jpg要么是公网 URL要么必须位于 allowed paths文件访问指南 所允许的目录范围内否则浏览器将无法加载该资源。兼容性警告重要在自定义 JS/CSS 中使用针对 Gradio 自身 HTML 元素的 query selector不能保证跨版本生效因为 Gradio 的 HTML DOM 结构可能随版本变化。官方建议克制使用 query selector优先采用下文介绍的elem_id/elem_classes这类稳定的自定义钩子。二、elem_id与elem_classes稳定、精准的样式锚点任意组件都支持两个与选择器相关的构造参数elem_id为组件对应的 HTML 元素设置idelem_classes为该元素设置一个类名或类名列表。由此可以绕过 Gradio 内部可能变化的内建类名/ID用自定义的锚点来命中目标元素如前面警告所述由于 DOM 结构本身可能变化即便使用自定义 CSS 也无法保证版本间完全兼容但这一方式已是最稳妥的路径。import gradio as gr css #warning {background-color: #FFCCCB} .feedback textarea {font-size: 24px !important} with gr.Blocks() as demo: box1 gr.Textbox(valueGood Job, elem_classesfeedback) box2 gr.Textbox(valueFailure, elem_idwarning, elem_classesfeedback) demo.launch(csscss)效果如下#warning规则只命中第二个 Textbox因为其elem_idwarning.feedback textarea规则同时命中两个 Textbox二者都带feedback类。参数本身在 gradio/components/base.py 的Component基类构造函数中定义随后会同步写入组件配置并最终渲染为真实 DOM 上的id/class。需要注意覆盖 Gradio 默认样式时针对类的规则可能需要加上!important才能生效如上例对font-size的处理。三、三种方式添加自定义 JavaScriptGradio 提供三种注入 JS 的途径分别服务于页面加载时执行事件触发时执行可与后端函数联动和向head注入任意脚本三种场景。方式一launch(js...)—— 页面首次加载时执行把一段 JS 代码以字符串形式传给Blocks/Interface的js参数这段代码会在演示页面首次加载时自动执行。仓库中的可运行示例 demo/blocks_js_load/run.py 演示了加载时逐字母淡入的欢迎动画import gradio as gr def welcome(name): return fWelcome to Gradio, {name}! js function createGradioAnimation() { var container document.createElement(div); container.id gradio-animation; container.style.fontSize 2em; container.style.fontWeight bold; container.style.textAlign center; container.style.marginBottom 20px; var text Welcome to Gradio!; for (var i 0; i text.length; i) { (function(i){ setTimeout(function(){ var letter document.createElement(span); letter.style.opacity 0; letter.style.transition opacity 0.5s; letter.innerText text[i]; container.appendChild(letter); setTimeout(function() { letter.style.opacity 1; }, 50); }, i * 250); })(i); } var gradioContainer document.querySelector(.gradio-container); gradioContainer.insertBefore(container, gradioContainer.firstChild); return Animation created; } createGradioAnimation(); with gr.Blocks() as demo: inp gr.Textbox(placeholderWhat is your name?) out gr.Textbox() inp.change(welcome, inp, out) if __name__ __main__: demo.launch(jsjs)直接运行python demo/blocks_js_load/run.py即可观察效果。示例中通过document.querySelector(.gradio-container)找到根容器并向其头部插入动画节点这正是第一节所述容器类名的典型用法若希望规避对内建类的依赖可改为给某个组件设置elem_id后定位。从 gradio/blocks.py 的launch()文档可以看到js参数还支持字面量True配合其他手段注入时使用。如果需要更精细的加载控制例如插入多个脚本或希望脚本位于自定义script标签中则应改用下文的方式三head。方式二事件监听器的js参数 —— 把函数直接跑在浏览器端使用Blocks与事件监听器时每个事件如click、change都带有js参数接受一个 JS 函数字符串其行为与 Python 事件函数对等同时传 Pythonfn与 JSjs前端 JS 函数先执行其返回值再交给 Python 函数只传 JS、将 Pythonfn置为None整个事件完全在前端完成不发起后端请求。从源码层面看该逻辑实现在 gradio/events.py当js是字符串且未配合 Python 装饰器使用时会先以fnNone注册一个纯前端事件js-only eventEventListener体系如Events.click、Events.change见 gradio/events.py将其统一包装最终每个事件依赖中都携带js字段。这意味着纯前端变换可以做到零后端往返。仓库中的可运行示例 demo/blocks_js_methods/run.py 是一个前端加工句子的完整演示import gradio as gr blocks gr.Blocks() with blocks as demo: subject gr.Textbox(placeholdersubject) verb gr.Radio([ate, loved, hated]) object gr.Textbox(placeholderobject) with gr.Row(): btn gr.Button(Create sentence.) reverse_btn gr.Button(Reverse sentence.) foo_bar_btn gr.Button(Append foo) reverse_then_to_the_server_btn gr.Button( Reverse sentence and send to server. ) def sentence_maker(w1, w2, w3): return f{w1} {w2} {w3} output1 gr.Textbox(labeloutput 1) output2 gr.Textbox(labelverb) output3 gr.Textbox(labelverb reversed) output4 gr.Textbox(labelfront end process and then send to backend) btn.click(sentence_maker, [subject, verb, object], output1) # 纯前端把三个输入直接拼成句子不经过后端 reverse_btn.click( None, [subject, verb, object], output2, js(s, v, o) o v s ) # 纯前端把 Radio 值反转后写回 verb.change(None, verb, output3, js(x) [...x].reverse().join()) # 纯前端给输入追加后缀 foo_bar_btn.click(None, [], subject, js(x) x foo) # 前端 JS 先反转再把结果发给后端函数 reverse_then_to_the_server_btn.click( None, [subject, verb, object], output4, js(s, v, o) [s, v, o].map(x [...x].reverse().join()).join( ), ) if __name__ __main__: demo.launch()关键点解读js函数的入参是各输入组件当前的值返回值按顺序对应输出组件前三个事件完全在浏览器端执行不会产生网络请求适合做字符串拼接、翻转等即时反馈最后一个按钮演示了先前端、后后端的混合模式此时fn收到的是 JS 处理后的结果可用于把客户端预处理与 Python 端逻辑衔接起来。这一机制与前端状态管理client-side-functions相辅相成是构建富交互 Blocks 应用的重要工具。方式三head参数 —— 注入任意head内容脚本、Meta 标签head参数接受任何通常可以放进 HTML 文档head的标签。最常见的用途之一是给应用接入 Google Analyticsgoogle_analytics_tracking_id G-XXXXXXXXXX head f script async srchttps://www.googletagmanager.com/gtag/js?id{google_analytics_tracking_id}/script script window.dataLayer window.dataLayer || []; function gtag(){{dataLayer.push(arguments);}} gtag(js, new Date()); gtag(config, {google_analytics_tracking_id}); /script with gr.Blocks() as demo: gr.HTML(h1My App/h1) demo.launch(headhead)另一个高频场景是定制社交分享预览。head中支持写入标准 meta 标签以及 Open Graph / Twitter Card 协议让应用链接在社交平台被分享时展示自定义标题、描述与封面图import gradio as gr custom_head !-- HTML Meta Tags -- titleSample App/title meta namedescription contentAn open-source web application showcasing various features and capabilities. !-- Facebook Meta Tags -- meta propertyog:url contenthttps://example.com meta propertyog:type contentwebsite meta propertyog:title contentSample App meta propertyog:description contentAn open-source web application showcasing various features and capabilities. meta propertyog:image contenthttps://cdn.britannica.com/98/152298-050-8E45510A/Cheetah.jpg !-- Twitter Meta Tags -- meta nametwitter:card contentsummary_large_image meta nametwitter:creator contentexample_user meta nametwitter:title contentSample App meta nametwitter:description contentAn open-source web application showcasing various features and capabilities. meta nametwitter:image contenthttps://cdn.britannica.com/98/152298-050-8E45510A/Cheetah.jpg meta propertytwitter:domain contentexample.com meta propertytwitter:url contenthttps://example.com with gr.Blocks(titleMy App) as demo: gr.HTML(h1My App/h1) demo.launch(headcustom_head)注意即使通过head注入脚本仍需把业务逻辑与事件监听关联起来如通过elem_id与document.getElementById绑定元素head与launch(js...)的差异在于——前者只是把 HTML 放进head不保证时机也不自动执行任意裸 JS 字符串若要页面加载即执行一段裸 JS应使用js参数gradio/blocks.py 的说明正是如此。四、注入脚本时的浏览器行为与可访问性注意自定义 JS 可能影响浏览器默认行为与无障碍体验如果应用被嵌入其他网页iframe 等脚本中的键盘快捷键可能与宿主页面冲突导致意外行为不同浏览器对事件与默认行为的处理存在差异应跨浏览器实测。官方给出的是一个安全范围内启用快捷键的范例按下Shift s时只有当焦点不在输入类组件如 Textbox上才触发指定按钮的click事件import gradio as gr shortcut_js script function shortcuts(e) { var event document.all ? window.event : e; switch (e.target.tagName.toLowerCase()) { case input: case textarea: break; default: if (e.key.toLowerCase() s e.shiftKey) { document.getElementById(my_btn).click(); } } } document.addEventListener(keypress, shortcuts, false); /script with gr.Blocks() as demo: action_button gr.Button(valueName, elem_idmy_btn) textbox gr.Textbox() action_button.click(lambda: button pressed, None, textbox) demo.launch(headshortcut_js)本例同时展示了如何把前两节的知识组合起来head注入事件监听脚本elem_idmy_btn提供稳定的元素锚点再通过普通.click()把前端触发映射到 Python 逻辑。这既是注入 JS 的完整闭环也是键盘事件过滤后再联动组件事件的推荐写法。五、仓库中的对应实现与延伸阅读如果想深入理解参数落地细节可以直接阅读当前仓库中的相关实现launch()参数定义与文档gradio/blocks.py 定义了theme、css、css_paths、js、head、head_paths的签名gradio/blocks.py 给出了每个参数的语义说明配置最终被序列化进页面配置见 gradio/blocks.py 的get_config。值得注意的是在 Gradio 6.x 中这些参数已从Blocks构造函数迁移至launch()若仍把它们传给构造函数会收到弃用警告见 gradio/blocks.py因此文中示例统一使用demo.launch(...)的写法。CSS 文件读取与拼接gradio/blocks.py 展示了css_paths/head_paths的读取注入实现。事件级js的前端直跑机制gradio/events.py 展示了纯 JS 事件的注册逻辑事件常量click、change、input等定义在 gradio/events.py。组件级elem_id/elem_classesgradio/components/base.py。可运行示例集中在demo目录除上面直接使用的 blocks_js_load 与 blocks_js_methods 外还可以参考html_head_script_async、html_head_script_order等与页面头部脚本注入相关的 demo。想要系统进阶建议按顺序阅读 custom-HTML-components、theming-guide、client-side-functions 与 styling-the-gradio-dataframe。最后再次强调两条工程红线优先使用elem_id/elem_classes而不是猜测 Gradio 内建 DOM 结构对外部资源保持敬畏——CSS 引用的文件必须位于允许访问的范围之内详见 file-access。遵循这两点你的定制样式与脚本才能跨版本长期稳定运行。【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考