
本文介绍在 Shiny 应用中实现「页面启动时立即显示 Spinner,待外部大型 JS 文件完全加载后再自动隐藏」的可靠方案,核心是结合 的 defer 与 onload 属性,配合 Shiny.setInputValue() 触发服务端清理逻辑。
本文介绍在 shiny 应用中实现「页面启动时立即显示 spinner,待外部大型 js 文件完全加载后再自动隐藏」的可靠方案,核心是结合 `<script>` 的 `defer` 与 `onload` 属性,配合 `shiny.setinputvalue()` 触发服务端清理逻辑。</script>
在 Shiny 中,shinybusy::busy_start_up() 默认采用“启动即显”策略,但其底层依赖 Shiny 的初始化完成时机——而 <script src="..."> 同步加载(尤其 >17 MB 的巨型 JS)会阻塞 DOM 解析和 Shiny 初始化流程,导致 Spinner 实际出现时间严重滞后,甚至被用户感知为“无响应”。</script>
根本解法在于解耦 JS 加载与 Shiny 初始化:利用 HTML 原生能力控制脚本加载生命周期,并主动通知 Shiny 状态变更。
一套面向小企业用户的企业网站程序!功能简单,操作简单。实现了小企业网站的很多实用的功能,如文章新闻模块、图片展示、产品列表以及小型的下载功能,还同时增加了邮件订阅等相应模块。公告,友情链接等这些通用功能本程序也同样都集成了!同时本程序引入了模块功能,只要在系统默认模板上创建模块,可以在任何一个语言环境(或任意风格)的适当位置进行使用!
✅ 正确实现步骤
- 启用 defer 属性:确保 JS 文件异步下载、延迟执行(不阻塞 HTML 解析),且保证在 DOM 构建完成后按顺序执行;
- 绑定 onload 回调:当 JS 文件加载并执行完毕后,触发自定义函数;
- 安全通信 Shiny:通过 Shiny.setInputValue() 向服务端发送信号(如 "done": true),避免直接操作 remove_start_up() 导致竞态;
- 添加微小延迟(必要 Hack):因 onload 可能在 Shiny JS 框架就绪前触发,使用 setTimeout(..., 100) 确保 Shiny 全局对象已可用;
- 设置 mode = "manual":禁用 busy_start_up() 的自动超时逻辑,由 JS 主动控制关闭时机。
? 完整可运行示例
library(shiny)
library(shinybusy)
library(bslib)
ui <- page_sidebar(
tags$head(
# 定义 onload 回调函数(必须先声明,确保作用域)
tags$script(HTML("
function notifyJSLoaded() {
setTimeout(() => {
if (typeof Shiny !== 'undefined' && typeof Shiny.setInputValue === 'function') {
Shiny.setInputValue('js_loaded', true);
}
}, 100);
}
")),
# 关键:defer + onload 组合
tags$script(src = "HUGE_JAVASCRIPT_FILE.js", defer = NA, onload = "notifyJSLoaded()")
),
# 手动模式:仅靠 JS 信号关闭,不依赖超时
busy_start_up(
loader = spin_epic("orbit", color = "#FFF"),
text = "Loading large JavaScript library...",
mode = "manual",
color = "#FFF",
background = "#112446"
),
title = "Gröbner implicitization with GIAC",
sidebar = sidebar(
textInput("equations", "Equations:", value = "x = a*cost, y = b*sint"),
textInput("relations", "Relations:", value = "cost^2 + sint^2 = 1"),
textInput("symbols", "Symbols:", value = "cost, sint, a, b"),
actionButton("go", "Run", class = "btn-primary")
)
)
server <- function(input, output, session) {
# 监听 JS 加载完成信号,立即移除启动 Spinner
observeEvent(input$js_loaded, {
remove_start_up()
})
}
shinyApp(ui, server)⚠️ 注意事项与最佳实践
- 文件路径验证:确保 HUGE_JAVASCRIPT_FILE.js 存放于 www/ 目录下,Shiny 会自动托管该目录;
- 避免 async:async 不保证执行顺序,若 JS 依赖其他库或全局变量,请坚持用 defer;
- 错误防御:生产环境建议在 notifyJSLoaded 中增加 try/catch 和加载失败兜底(如 onerror 处理);
- 性能监控:可在 larger.js 开头/结尾插入 console.time("JS load") / console.timeEnd("JS load") 辅助调试;
- 替代方案权衡:若 JS 仅需部分功能,考虑模块化拆分 + 动态 import(),但需重写 JS 为 ES Module 格式。
通过该方案,Spinner 将在 HTML 解析开始后立即呈现,用户获得即时反馈;而关闭时机精准锚定在目标 JS 执行完毕之后,兼顾体验与可靠性。









