Shiny嵌套animatedConditionalPanel动画异常问题求助
解决Shiny animatedConditionalPanel的两个问题
问题分析
- 嵌套面板触发外层重复动画:原代码中
animatedConditionalPanel监听容器内所有div的show/hide事件,内部嵌套的conditionalPanel状态变化也会触发该事件,导致外层动画重复播放。 - 返回步骤后面板内容不显示:原隐藏逻辑使用
$this.hide()直接隐藏元素,切换回原面板时,动画触发逻辑未正确恢复元素显示状态。
修改后的完整代码
library(shiny) library(shinyjs) library(shinydashboard) library(stringi) animateCSS <- function(effect, delay = 0, duration = 500, then = NULL){ effect <- match.arg(effect, c( "bounce", "flash", "pulse", "rubberBand", "shakeX", "shakeY", "headShake", "swing", "tada", "wobble", "jello", "heartBeat", "backInDown", "backInLeft", "backInRight", "backInUp", "backOutDown", "backOutLeft", "backOutRight", "backOutUp", "bounceIn", "bounceInDown", "bounceInLeft", "bounceInRight", "bounceInUp", "bounceOut", "bounceOutDown", "bounceOutLeft", "bounceOutRight", "bounceOutUp", "fadeIn", "fadeInDown", "fadeInDownBig", "fadeInLeft", "fadeInLeftBig", "fadeInRight", "fadeInRightBig", "fadeInUp", "fadeInUpBig", "fadeInTopLeft", "fadeInTopRight", "fadeInBottomLeft", "fadeInBottomRight", "fadeOut", "fadeOutDown", "fadeOutDownBig", "fadeOutLeft", "fadeOutLeftBig", "fadeOutRight", "fadeOutRightBig", "fadeOutUp", "fadeOutUpBig", "fadeOutTopLeft", "fadeOutTopRight", "fadeOutBottomRight", "fadeOutBottomLeft", "flip", "flipInX", "flipInY", "flipOutX", "flipOutY", "lightSpeedInRight", "lightSpeedInLeft", "lightSpeedOutRight", "lightSpeedOutLeft", "rotateIn", "rotateInDownLeft", "rotateInDownRight", "rotateInUpLeft", "rotateInUpRight", "rotateOut", "rotateOutDownLeft", "rotateOutDownRight", "rotateOutUpLeft", "rotateOutUpRight", "hinge", "jackInTheBox", "rollIn", "rollOut", "zoomIn", "zoomInDown", "zoomInLeft", "zoomInRight", "zoomInUp", "zoomOut", "zoomOutDown", "zoomOutLeft", "zoomOutRight", "zoomOutUp", "slideInDown", "slideInLeft", "slideInRight", "slideInUp", "slideOutDown", "slideOutLeft", "slideOutRight", "slideOutUp" )) js <- paste( " $this.animateCSS('%s', {", " delay: %d,", " duration: %d,", " callback: function(){", " %s", " }", " });", sep = "\n" ) sprintf(js, effect, delay, duration, ifelse(is.null(then), "", then)) } onShowJS <- function(animation, fadeDuration){ sprintf(paste( "$('#%%s > .animated-cond-panel').on('show', function(){", " var $this = $(this);", " $this.css('opacity', 0).animate({opacity: 1}, %d, function(){", animation, " });", "});", sep = "\n" ), fadeDuration) } onHideJS <- function(animation, fadeDuration){ sprintf(paste( "$('#%s > .animated-cond-panel').on('hide', function(){", " var $this = $(this);", " setTimeout(function(){", sub( "^(\\s.*?\\$this\\.animateCSS)", "$this.show().animateCSS", sub( "\\{\\n \\n \\}", sprintf("{$this.css('display', 'none');}", fadeDuration), animation ) ), " }, 0);", "});", sep = "\n" ), fadeDuration) } animatedConditionalPanel <- function(condition, ..., onShow = NULL, fadeIn = 0, onHide = NULL, fadeOut = 0){ id <- paste0("animateCSS-", stringi::stri_rand_strings(1, 15)) jsShow <- ifelse(!is.null(onShow), sprintf(onShowJS(onShow, fadeIn), id), "") jsHide <- ifelse(!is.null(onHide), sprintf(onHideJS(onHide, fadeOut), id), "") script <- tags$script(HTML(paste(jsShow,jsHide,sep="\n"))) # 给外层conditionalPanel添加专属类名,避免监听内部面板事件 condPanel <- conditionalPanel(condition, class = "animated-cond-panel", ...) tags$div(id=id, tagList(condPanel, script)) } ui <- dashboardPage( header = dashboardHeader(title = "test"), sidebar = dashboardSidebar(), shinydashboard::dashboardBody( fluidPage( useShinyjs(), tags$head( tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.0/animate.compat.min.css"), tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/animateCSS/1.2.2/jquery.animatecss.min.js") ), fluidRow( textOutput("test"), column(2, offset = 2, actionButton("prev_box", "Previous")), column( 4, animatedConditionalPanel( condition = "output.phase_box_ui == 0", onShow = animateCSS("backInRight", duration = 1000), onHide = animateCSS("backOutLeft", duration = 1000), fadeIn = 0, fadeOut = 0, uiOutput("box_move1") ), animatedConditionalPanel( condition = "output.phase_box_ui == 1", onShow = animateCSS("backInRight", duration = 1000), onHide = animateCSS("backOutLeft", duration = 1000), fadeIn = 0, fadeOut = 0, uiOutput("box_move2") ) ), column(2, actionButton("next_box", "Next")) ) ) ) ) server <- function(input, output,session) { #### TEST BOX MOVING phase_box = reactiveVal(0) observeEvent(phase_box(),{ if(phase_box() == 0){ shinyjs::hide("prev_box") }else{ shinyjs::show("prev_box") } }) observeEvent(input$prev_box,{ if(phase_box() > 0){ val = phase_box() - 1 phase_box(val) } }) observeEvent(input$next_box,{ if(phase_box() < 6){ val = phase_box() + 1 phase_box(val) } }) output$test = renderText({ phase_box() }) output$phase_box_ui = reactive({return(phase_box())}) outputOptions(output, "phase_box_ui", suspendWhenHidden = F) output$box_move1 = renderUI({ box(width = NULL, status = "primary", solidHeader = T, title = "First box", h4(strong("This is the first box")), selectInput("step2", "Choose a step:", choices = c("one", "two")), br(), conditionalPanel( condition = "input.step2 == 'two'", h4(strong("2. This is the second step")) ) ) }) output$box_move2 = renderUI({ box(width = NULL, status = "primary", solidHeader = T, title = "Second box", h4("This is the second box")) }) } shinyApp(ui, server)
关键修改说明
解决嵌套面板动画重复问题:
- 给
animatedConditionalPanel内部的conditionalPanel添加专属类名.animated-cond-panel - 修改
onShowJS和onHideJS的事件监听选择器,仅针对带有.animated-cond-panel类的元素,避免监听内部嵌套的conditionalPanel事件
- 给
解决返回步骤内容不显示问题:
- 将隐藏逻辑中的
$this.hide()改为$this.css('display', 'none'),避免jQuery的hide()方法添加额外样式导致后续显示异常 - 移除内部嵌套
conditionalPanel的style = "display: none",让Shiny原生逻辑控制其显示隐藏
- 将隐藏逻辑中的
内容的提问来源于stack exchange,提问作者Fabio DR
相关产品推荐
相关产品推荐

