如何在R Shiny中根据用户输入切换外部CSS文件?

3

我想在我的应用程序中添加深色模式。我知道可以使用 bs_theme() 来实现,但是当我使用 bs_theme() 时,我的应用程序中的许多设置都无法从外部 css 文件中读取。

我希望有两个单独的 CSS 文件,一个用于浅色主题,另一个用于深色主题。根据用户输入,在我的 Rshiny 应用程序中加载相应的主题文件。你有什么建议吗?

2个回答

2
在这种情况下,我会创建两个不同的CSS类这里是如何将CSS文件(尽可能多)包含到闪亮应用程序中的方法。
要在类之间切换,我们可以使用library(shinyjs)中的addCssClass / removeCssClass(或toggleCssClass)。
library(shiny)
library(shinyjs)

if(!dir.exists("www")){
  dir.create("www")
}

writeLines(".dark {
  background-color: black;
  color: white; /* text color */
}", con = "www/dark_mode.css")

writeLines(".light {
  background-color: white;
  color: black; /* text color */
}", con = "www/light_mode.css")

ui <- fluidPage(
  useShinyjs(),
  tags$head(
    tags$link(rel = "stylesheet", type = "text/css", href = "dark_mode.css"),
    tags$link(rel = "stylesheet", type = "text/css", href = "light_mode.css")
  ),
  radioButtons("mode", "Select mode", choices = c("dark", "light"), selected  = "light")
)

server <- function(input, output, session) {
  observeEvent(input$mode, {
    if(input$mode == "dark"){
      addCssClass(class = "dark", selector = "body")
      removeCssClass(class = "light", selector = "body")
    } else {
      addCssClass(class = "light", selector = "body")
      removeCssClass(class = "dark", selector = "body")
    }
  })
}

shinyApp(ui, server)

result

可以通过使用Shiny.addCustomMessageHandler和一些自定义的JS代码,例如element.classList.add("myclass");(参见this),来实现相同的功能。
编辑:将addCssClass应用于不同类别的输入框:
library(shiny)
library(shinyjs)

if(!dir.exists("www")){
  dir.create("www")
}

writeLines(".dark {
  background-color: black !important;
  color: white; /* text color */
}", con = "www/dark_mode.css")

writeLines(".light {
  background-color: white !important;
  color: black; /* text color */
}", con = "www/light_mode.css")

ui <- fluidPage(
  useShinyjs(),
  tags$head(
    tags$link(rel = "stylesheet", type = "text/css", href = "dark_mode.css"),
    tags$link(rel = "stylesheet", type = "text/css", href = "light_mode.css")
  ),
  radioButtons("mode", "Select mode", choices = c("dark", "light"), selected  = "light"),
  selectizeInput("Test", "Test Input", choices = 1:10),
  actionButton("testButton", "Test Button")
)

server <- function(input, output, session) {
  observeEvent(input$mode, {
    applyTo <- list(".selectize-input", ".btn-default")
    if(input$mode == "dark"){
      lapply(applyTo, function(x){
        addCssClass(class = "dark", selector = x)
        removeCssClass(class = "light", selector = x)
      })
    } else {
      lapply(applyTo, function(x){
        addCssClass(class = "light", selector = x)
        removeCssClass(class = "dark", selector = x)
      })
    }
  })
}

shinyApp(ui, server)

result


编辑:使用 forEach:
library(shiny)
library(shinyjs)

if(!dir.exists("www")){
  dir.create("www")
}

writeLines(".dark {
  background-color: black !important;
  color: white; /* text color */
}", con = "www/dark_mode.css")

writeLines(".light {
  background-color: white !important;
  color: black; /* text color */
}", con = "www/light_mode.css")

ui <- fluidPage(
  useShinyjs(),
  tags$head(
    tags$link(rel = "stylesheet", type = "text/css", href = "dark_mode.css"),
    tags$link(rel = "stylesheet", type = "text/css", href = "light_mode.css")
  ),
  radioButtons("mode", "Select mode", choices = c("dark", "light"), selected  = "light"),
  selectizeInput("Test", "Test Input", choices = 1:10),
  actionButton("testButton", "Test Button")
)

server <- function(input, output, session) {
  
  dm_classes <- paste(c(".selectize-input", ".btn-default"), collapse = ", ")
  
  observeEvent(input$mode, {
    if(input$mode == "dark"){
      runjs(sprintf("document.querySelectorAll('%s').forEach(x=>x.classList.add('dark'));
                     document.querySelectorAll('%s').forEach(x=>x.classList.remove('light'));", dm_classes, dm_classes))
    } else {
      runjs(sprintf("document.querySelectorAll('%s').forEach(x=>x.classList.add('light'));
                     document.querySelectorAll('%s').forEach(x=>x.classList.remove('dark'));", dm_classes, dm_classes))
    }
  })
}

shinyApp(ui, server)

嗨@ismirsehregal,您的方法虽然有效,但我该如何自定义特定元素的样式?例如,下面的设置应仅适用于浅色模式: .light .selectize-input, .selectize-control.single .selectize-input.input-active { background-color:red; border:0px; } 但是,我在浅色和深色模式下都看到了这种变化。在深色模式CSS中,我将颜色保持为白色并附加了类.dark。 - shivam_data_scientist
这是暗模式CSS设置,以避免混淆: .dark .selectize-input,.selectize-control.single .selectize-input.input-active { 背景颜色:#738ca5; 边框:0px; } - shivam_data_scientist
@shivam_data_scientist,请查看我的编辑。 - ismirsehregal
嗨@ismirsehregal,感谢您提供的解决方案。按照您的方式它可以工作,但是我觉得随着我们向列表中添加越来越多的元素,这种方法会变得非常慢。您认为呢? - shivam_data_scientist
@shivam_data_scientist 我们可以使用JS代替 - 请查看我的编辑。 - ismirsehregal
太棒了!非常感谢@ismirsehregal!我已经接受了你的答案作为解决方案,因为它确实回答了原始问题。 但是我还想知道是否可以使用sass来实现相同的方法?请告诉我这是否是一个全新的问题。 - shivam_data_scientist

0
假设您的CSS文件名为darkmode.csslightmode.css,并且放置在www子文件夹中,您可以像这样使用renderUI

library(shiny)

ui <- fluidPage(
  tags$head(
    uiOutput("css")
  ),
  radioButtons("select", "Select mode", c("dark", "light"))
)

server <- function(input, output){
  
  output[["css"]] <- renderUI({
    cssfile <- paste0(input[["select"]], "mode.css")
    tags$link(rel = "stylesheet", type = "text/css", href = cssfile)
  })
  
}

shinyApp(ui, server)

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接