You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Shiny嵌套animatedConditionalPanel动画异常问题求助

解决Shiny animatedConditionalPanel的两个问题

问题分析

  1. 嵌套面板触发外层重复动画:原代码中animatedConditionalPanel监听容器内所有div的show/hide事件,内部嵌套的conditionalPanel状态变化也会触发该事件,导致外层动画重复播放。
  2. 返回步骤后面板内容不显示:原隐藏逻辑使用$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)

关键修改说明

  1. 解决嵌套面板动画重复问题:

    • 给animatedConditionalPanel内部的conditionalPanel添加专属类名.animated-cond-panel
    • 修改onShowJS和onHideJS的事件监听选择器,仅针对带有.animated-cond-panel类的元素,避免监听内部嵌套的conditionalPanel事件
  2. 解决返回步骤内容不显示问题:

    • 将隐藏逻辑中的$this.hide()改为$this.css('display', 'none'),避免jQuery的hide()方法添加额外样式导致后续显示异常
    • 移除内部嵌套conditionalPanel的style = "display: none",让Shiny原生逻辑控制其显示隐藏

内容的提问来源于stack exchange,提问作者Fabio DR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 16:31:34