Shiny中如何确保shinyjs::runjs在DOM更新完成后执行
Shiny中确保JS在uiOutput渲染完成后执行的原生方法
问题核心原因
Shiny服务端会将同一次响应周期内的所有更新指令(包括UI更新、runjs指令等)批量发送给客户端,shinyjs::runjs默认和renderUI的更新指令同时到达,JS执行时很可能还没完成DOM渲染,就会出现获取元素内容滞后的问题。
原生解决方案
方案1:服务端使用 session$onFlushed (无需额外写客户端JS,推荐动态JS场景)
session$onFlushed是Shiny原生的服务端回调方法,会等待本次响应周期内所有输出都完成客户端渲染后再执行回调逻辑,参数once=TRUE表示仅执行一次当前回调。
修改你的observeEvent逻辑即可:
# 注意server函数要传入session参数:function(input, output, session) { observeEvent(content(), { session$onFlushed(function() { # 这里的JS会在DOM更新完成后才会执行 js = 'console.log($("#mydiv").text());' shinyjs::runjs(js) }, once = TRUE) })
如果需要控制不是每次更新都执行JS,只需要在onFlushed外层加判断条件即可,符合条件才注册回调。
方案2:客户端监听shiny:value事件(推荐JS逻辑固定的场景)
Shiny客户端原生提供shiny:value事件,当任意output完成渲染后会触发该事件,我们可以只监听对应uiOutput的更新事件,在回调里执行JS:
首先在你的Rmd文档中加入以下JS代码:
<script> $(document).on('shiny:value', function(event) { // 仅监听contentBox这个output的渲染完成事件 if (event.name !== 'contentBox') return; // 若不是每次更新都要执行JS,可以给渲染的元素加自定义属性做判断 const needRunJs = $('#mydiv').data('run-js'); if (needRunJs) { console.log($("#mydiv").text()); // 执行完移除标记,避免下次不满足条件时误执行 $('#mydiv').removeData('run-js'); } }); </script>
需要执行JS的场景下,在服务端渲染UI时给元素加对应标记即可:
content <- reactive({ input$button x = rnorm(1) # 满足执行JS的条件时加data-run-js属性 shiny::tags$div(x, id = 'mydiv', `data-run-js` = TRUE) })
两种方案对比
- 方案1实现更简单,适合JS代码需要服务端动态生成、或者判断逻辑集中在服务端的场景
- 方案2性能更好,适合JS逻辑固定、不需要服务端介入判断的场景
内容的提问来源于stack exchange,提问作者richarddmorey
相关产品推荐
相关产品推荐

