R语言ShinyApp本地与部署后运行不一致问题求助
Shiny计时器部署至shinyapp.io后重复按键识别失效问题解决
问题概述
开发的实验室用Shiny计时器应用,本地运行时通过if (!e.repeat)实现了忽略重复按键的功能,但部署到shinyapp.io后该逻辑失效,长按按键会持续触发多次事件,导致数据重复添加。应用核心功能:
- 按键按下启动单次计时,首次按键启动实验总计时器
- 按键释放时保存计时数据至
DT::dataTableOutput - 支持导出CSV数据
问题原因
keypress事件在不同浏览器环境中对e.repeat属性的支持存在差异,shinyapp.io的运行环境未正确传递该属性- 代码中存在重复的事件监听逻辑,且部分脚本存在语法错误(如
keydown事件脚本中的多余字符b) - 事件绑定的逻辑冗余,同一按键的不同事件监听可能导致冲突
解决方案
- 将依赖
keypress的事件替换为keydown事件,keydown对e.repeat的支持更稳定 - 修复脚本中的语法错误
- 精简事件监听逻辑,避免重复绑定同一按键事件
修改后的完整代码
依赖库
# install.packages("shinythemes") # install.packages("shiny") # install.packages("DT") # install.packages("lubridate") # install.packages("keys") # install.packages("vtable") # install.packages('rsconnect') library(shinythemes) library(shiny) library(DT) library(lubridate) library(keys) library(vtable)
应用代码
my_options <- options(digits.secs = 3) # 设置计时器精度 ui <- fluidPage( title = NULL, lang = NULL, hr(), # 合并keydown事件监听,统一处理重复按键判断 tags$script(HTML('document.addEventListener("keydown", function(e) { if (!e.repeat) { // 回车键启动实验总计时器 if (e.keyCode === 13) { Shiny.setInputValue("t_exp_timer", TRUE, {priority: "event"}); } // 空格键启动单次计时 if (e.keyCode === 32) { Shiny.setInputValue("start", TRUE, {priority: "event"}); } // S键停止计时 if (e.keyCode === 83) { Shiny.setInputValue("stop", TRUE, {priority: "event"}); } } });')), # 空格键释放时的统一处理 tags$script(HTML('document.addEventListener("keyup", function(e) { if (e.keyCode === 32) { Shiny.setInputValue("lapsing_timer", TRUE, {priority: "event"}); Shiny.setInputValue("reset", TRUE, {priority: "event"}); } });')), titlePanel("Tail Flick Latency StopWatch"), sidebarPanel( textOutput('stopwatch') ), sidebarPanel( textOutput('exp_stopwatch') ), tags$hr(), mainPanel( DT::dataTableOutput("TailFlickTrials") ), downloadButton('download',"Download the data") ) # 初始化响应式数据框 v <- reactiveValues() v$df <- data.frame(Start_Time = numeric(), End_Time = numeric(), TimeLapsed = numeric(), stringsAsFactors = FALSE) server <- function(input, output, session) { exp_timer <- reactiveVal(0) exp_timer_active <- reactiveVal(FALSE) timer <- reactiveVal(0) active <- reactiveVal(FALSE) tmp_Start_Time <- numeric(0) tmp_End_Time <- numeric(0) observe({ invalidateLater(100, session) isolate({ if(active()) { timer(round(timer()+0.1,3)) } }) }) observe({ invalidateLater(100, session) isolate({ if(exp_timer_active()) { exp_timer(round(exp_timer()+0.1,2)) } }) }) # 单次计时启动(空格键按下) observeEvent(input$start,{ timer(0) tmp_End_Time <- numeric(0) tmp_Start_Time <- Sys.time() # 添加新行,仅填充开始时间 new_row <- head(v$df[NA,], 1) new_row[c('Start_Time')] <- list(Start_Time = tmp_Start_Time) v$df <- rbind(v$df, new_row) }) # 单次计时结束(空格键释放) observeEvent(input$reset,{ timer(0) tmp_Start_Time <- numeric(0) tmp_End_Time <- Sys.time() # 计算并填充耗时 tmp_TimeLapsed <- round(as.numeric(difftime(tmp_End_Time, v$df[nrow(v$df), 1], units ="secs")),3) v$df[nrow(v$df), 2] <- tmp_End_Time v$df[nrow(v$df), 3] <- tmp_TimeLapsed }) observeEvent(input$lapsing_timer, {active(TRUE)}) output$stopwatch <- renderText({ paste("Time Lapsed: ", seconds_to_period(timer())) }) observeEvent(input$t_exp_timer, exp_timer_active(TRUE)) output$exp_stopwatch <- renderText({ paste("Time Lapse Since first Pre-Test Trial: ", seconds_to_period(exp_timer())) }) output$TailFlickTrials <- DT::renderDataTable({ v$df }) output$download <- downloadHandler( filename = function () { paste("MyData.csv") }, content = function(file) { write.csv(v$df, file) } ) } # 运行应用 options(shiny.maxRequestSize=30*1024^2) options(rsconnect.max.bundle.files = 500000000) shinyApp(ui = ui, server = server)
关键修改点
- 将原有的多个
keypress事件监听合并为一个keydown事件监听,统一处理e.repeat判断 - 使用
e.keyCode替代e.key,避免字符编码差异导致的事件识别错误 - 修复了
keydown事件脚本中的多余字符b - 精简了
keyup事件逻辑,合并同一按键的多个事件触发
内容的提问来源于stack exchange,提问作者Jacob White
相关产品推荐
相关产品推荐

