在智能手机上使用,闪亮的互动情节不理解手指运动
used on a smartphone, shiny interactive plot doesn't understand finger movements
我有一个 R-Shiny 应用程序,它有一个实现交互操作的绘图:单击、悬停(悬停是将鼠标移到绘图上,可以通过 shiny 检测到)。为了给出一个想法,我 post 下面是一个简化的闪亮应用程序,它具有对我来说有问题的功能,即交互式绘图图。 (摘自我的旧回答here)
它实际上工作正常,但是我需要人们在他们的 smartphones 上使用它。问题:我们在 smartphone 中的手指移动被 phone 解释为页面上的 zooming 或 scrolling 在页面上,而不是作为鼠标选择或鼠标在绘图上移动(悬停)。
是否可以修改代码(java?CSS?),我可以在应用程序上将触摸事件转换为鼠标事件,或者 option/gesture smartphone 启用鼠标式移动?
非常感谢;代码:
library(shiny)
ui <- fluidPage(
h4("Click on plot to start drawing, click again to pause"),
sliderInput("mywidth", "width of the pencil", min=1, max=30, step=1, value=10),
actionButton("reset", "reset"),
plotOutput("plot", width = "500px", height = "500px",
hover=hoverOpts(id = "hover", delay = 100, delayType = "throttle", clip = TRUE, nullOutside = TRUE),
click="click"))
server <- function(input, output, session) {
vals = reactiveValues(x=NULL, y=NULL)
draw = reactiveVal(FALSE)
observeEvent(input$click, handlerExpr = {
temp <- draw(); draw(!temp)
if(!draw()) {
vals$x <- c(vals$x, NA)
vals$y <- c(vals$y, NA)
}})
observeEvent(input$reset, handlerExpr = {
vals$x <- NULL; vals$y <- NULL
})
observeEvent(input$hover, {
if (draw()) {
vals$x <- c(vals$x, input$hover$x)
vals$y <- c(vals$y, input$hover$y)
}})
output$plot= renderPlot({
plot(x=vals$x, y=vals$y, xlim=c(0, 28), ylim=c(0, 28), ylab="y", xlab="x", type="l", lwd=input$mywidth)
})}
shinyApp(ui, server)
虽然我不能完全解决这个问题,但也许一个肮脏的解决方法对你也有一些价值。或者其他人可以基于该答案。
我可以重现在移动设备上未捕获绘图点击的错误。但我注意到我可以使用 javascript/shinyjs 添加额外的点击事件。
一种方法是:
onevent(event = "click", id = "plot", function(e){
global$clickx = c(global$clickx, e$pageX - 88)
global$clicky = c(global$clicky, 540 - e$pageY)
})
它有一些缺点:
- 形状只是用线条绘制的,它不会捕获所有悬停在绘图上的东西
- 该位置非常不精确,因为您必须考虑边界和边距(非常脏但这里有潜力)
我 运行 几个小时后有点赶时间,肯定可以改进它,但无论如何它可能对你感兴趣。
在此处测试:(link 可能会在接下来的几周内发生变化)
http://ec2-3-121-215-255.eu-central-1.compute.amazonaws.com/shiny/rstudio/sample-apps/mobile/
可重现代码:(在智能手机上测试:Mi A2)
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
h4("Click on plot to start drawing, click again to pause"),
plotOutput(outputId = "plot", width = "500px", height = "500px")
)
server <- function(input, output, session) {
onevent(event = "click", id = "plot", function(e){
global$clickx = c(global$clickx, e$pageX - 88)
global$clicky = c(global$clicky, 540 - e$pageY)
})
global <- reactiveValues(clickx = NULL, clicky = NULL)
output$plot= renderPlot({
plot(x = NULL, y = NULL, xlim=c(0, 440), ylim=c(0, 440), ylab="y", xlab="x", type="l")
len <- length(global$clickx)
lines(x = global$clickx, y = global$clicky, type = "p")
if(len > 1){
for(nr in 2:len){
lines(x = global$clickx[(nr - 1):nr], y = global$clicky[(nr - 1):nr], type = "l")
}
}
})
}
shinyApp(ui, server)
您可以使用 touch-action
CSS 属性:
在图上禁用 panning/zoom 手势
#plot {
touch-action: none;
}
将触摸事件转换为鼠标事件有点棘手,但您可以监听 touchstart
、touchmove
、touchend
等触摸事件,并在 [=30] 中模拟等效的鼠标事件=].有关详细信息,请参阅 https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Using_Touch_Events and https://javascript.info/dispatch-events。
它并不完美,但我尝试了一下。我禁用了绘图上的触摸手势并添加了一个脚本,该脚本将 touchmove
转换为 mousemove
,并告诉服务器何时开始绘图(在 touchstart
上)和停止绘图(在 [=15= 上) ]).
library(shiny)
ui <- fluidPage(
h4("Click on plot to start drawing, click again to pause"),
sliderInput("mywidth", "width of the pencil", min=1, max=30, step=1, value=10),
actionButton("reset", "reset"),
plotOutput("plot", width = "400px", height = "400px",
hover=hoverOpts(id = "hover", delay = 100, delayType = "throttle", clip = TRUE, nullOutside = TRUE),
click="click"),
tags$head(
tags$script("
$(document).ready(function() {
var plot = document.getElementById('plot')
plot.addEventListener('touchmove', function (e) {
var touch = e.changedTouches[0];
var mouseEvent = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
screenX: touch.screenX,
screenY: touch.screenY,
clientX: touch.clientX,
clientY: touch.clientY
})
touch.target.dispatchEvent(mouseEvent);
e.preventDefault()
}, { passive: false });
plot.addEventListener('touchstart', function(e) {
Shiny.onInputChange('draw', true)
e.preventDefault()
}, { passive: false });
plot.addEventListener('touchend', function(e) {
Shiny.onInputChange('draw', false)
e.preventDefault()
}, { passive: false });
})
"),
tags$style("#plot { touch-action: none; }")
)
)
server <- function(input, output, session) {
vals = reactiveValues(x=NULL, y=NULL)
draw = reactiveVal(FALSE)
observeEvent(input$click, {
draw(!draw())
vals$x <- append(vals$x, NA)
vals$y <- append(vals$y, NA)
})
observeEvent(input$draw, {
draw(input$draw)
vals$x <- append(vals$x, NA)
vals$y <- append(vals$y, NA)
})
observeEvent(input$reset, handlerExpr = {
vals$x <- NULL; vals$y <- NULL
})
observeEvent(input$hover, {
if (draw()) {
vals$x <- c(vals$x, input$hover$x)
vals$y <- c(vals$y, input$hover$y)
}
})
output$plot= renderPlot({
plot(x=vals$x, y=vals$y, xlim=c(0, 28), ylim=c(0, 28), ylab="y", xlab="x", type="l", lwd=input$mywidth)
})
}
shinyApp(ui, server)
我有一个 R-Shiny 应用程序,它有一个实现交互操作的绘图:单击、悬停(悬停是将鼠标移到绘图上,可以通过 shiny 检测到)。为了给出一个想法,我 post 下面是一个简化的闪亮应用程序,它具有对我来说有问题的功能,即交互式绘图图。 (摘自我的旧回答here)
它实际上工作正常,但是我需要人们在他们的 smartphones 上使用它。问题:我们在 smartphone 中的手指移动被 phone 解释为页面上的 zooming 或 scrolling 在页面上,而不是作为鼠标选择或鼠标在绘图上移动(悬停)。
是否可以修改代码(java?CSS?),我可以在应用程序上将触摸事件转换为鼠标事件,或者 option/gesture smartphone 启用鼠标式移动?
非常感谢;代码:
library(shiny)
ui <- fluidPage(
h4("Click on plot to start drawing, click again to pause"),
sliderInput("mywidth", "width of the pencil", min=1, max=30, step=1, value=10),
actionButton("reset", "reset"),
plotOutput("plot", width = "500px", height = "500px",
hover=hoverOpts(id = "hover", delay = 100, delayType = "throttle", clip = TRUE, nullOutside = TRUE),
click="click"))
server <- function(input, output, session) {
vals = reactiveValues(x=NULL, y=NULL)
draw = reactiveVal(FALSE)
observeEvent(input$click, handlerExpr = {
temp <- draw(); draw(!temp)
if(!draw()) {
vals$x <- c(vals$x, NA)
vals$y <- c(vals$y, NA)
}})
observeEvent(input$reset, handlerExpr = {
vals$x <- NULL; vals$y <- NULL
})
observeEvent(input$hover, {
if (draw()) {
vals$x <- c(vals$x, input$hover$x)
vals$y <- c(vals$y, input$hover$y)
}})
output$plot= renderPlot({
plot(x=vals$x, y=vals$y, xlim=c(0, 28), ylim=c(0, 28), ylab="y", xlab="x", type="l", lwd=input$mywidth)
})}
shinyApp(ui, server)
虽然我不能完全解决这个问题,但也许一个肮脏的解决方法对你也有一些价值。或者其他人可以基于该答案。
我可以重现在移动设备上未捕获绘图点击的错误。但我注意到我可以使用 javascript/shinyjs 添加额外的点击事件。
一种方法是:
onevent(event = "click", id = "plot", function(e){
global$clickx = c(global$clickx, e$pageX - 88)
global$clicky = c(global$clicky, 540 - e$pageY)
})
它有一些缺点:
- 形状只是用线条绘制的,它不会捕获所有悬停在绘图上的东西
- 该位置非常不精确,因为您必须考虑边界和边距(非常脏但这里有潜力)
我 运行 几个小时后有点赶时间,肯定可以改进它,但无论如何它可能对你感兴趣。
在此处测试:(link 可能会在接下来的几周内发生变化)
http://ec2-3-121-215-255.eu-central-1.compute.amazonaws.com/shiny/rstudio/sample-apps/mobile/
可重现代码:(在智能手机上测试:Mi A2)
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
h4("Click on plot to start drawing, click again to pause"),
plotOutput(outputId = "plot", width = "500px", height = "500px")
)
server <- function(input, output, session) {
onevent(event = "click", id = "plot", function(e){
global$clickx = c(global$clickx, e$pageX - 88)
global$clicky = c(global$clicky, 540 - e$pageY)
})
global <- reactiveValues(clickx = NULL, clicky = NULL)
output$plot= renderPlot({
plot(x = NULL, y = NULL, xlim=c(0, 440), ylim=c(0, 440), ylab="y", xlab="x", type="l")
len <- length(global$clickx)
lines(x = global$clickx, y = global$clicky, type = "p")
if(len > 1){
for(nr in 2:len){
lines(x = global$clickx[(nr - 1):nr], y = global$clicky[(nr - 1):nr], type = "l")
}
}
})
}
shinyApp(ui, server)
您可以使用 touch-action
CSS 属性:
#plot {
touch-action: none;
}
将触摸事件转换为鼠标事件有点棘手,但您可以监听 touchstart
、touchmove
、touchend
等触摸事件,并在 [=30] 中模拟等效的鼠标事件=].有关详细信息,请参阅 https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Using_Touch_Events and https://javascript.info/dispatch-events。
它并不完美,但我尝试了一下。我禁用了绘图上的触摸手势并添加了一个脚本,该脚本将 touchmove
转换为 mousemove
,并告诉服务器何时开始绘图(在 touchstart
上)和停止绘图(在 [=15= 上) ]).
library(shiny)
ui <- fluidPage(
h4("Click on plot to start drawing, click again to pause"),
sliderInput("mywidth", "width of the pencil", min=1, max=30, step=1, value=10),
actionButton("reset", "reset"),
plotOutput("plot", width = "400px", height = "400px",
hover=hoverOpts(id = "hover", delay = 100, delayType = "throttle", clip = TRUE, nullOutside = TRUE),
click="click"),
tags$head(
tags$script("
$(document).ready(function() {
var plot = document.getElementById('plot')
plot.addEventListener('touchmove', function (e) {
var touch = e.changedTouches[0];
var mouseEvent = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
screenX: touch.screenX,
screenY: touch.screenY,
clientX: touch.clientX,
clientY: touch.clientY
})
touch.target.dispatchEvent(mouseEvent);
e.preventDefault()
}, { passive: false });
plot.addEventListener('touchstart', function(e) {
Shiny.onInputChange('draw', true)
e.preventDefault()
}, { passive: false });
plot.addEventListener('touchend', function(e) {
Shiny.onInputChange('draw', false)
e.preventDefault()
}, { passive: false });
})
"),
tags$style("#plot { touch-action: none; }")
)
)
server <- function(input, output, session) {
vals = reactiveValues(x=NULL, y=NULL)
draw = reactiveVal(FALSE)
observeEvent(input$click, {
draw(!draw())
vals$x <- append(vals$x, NA)
vals$y <- append(vals$y, NA)
})
observeEvent(input$draw, {
draw(input$draw)
vals$x <- append(vals$x, NA)
vals$y <- append(vals$y, NA)
})
observeEvent(input$reset, handlerExpr = {
vals$x <- NULL; vals$y <- NULL
})
observeEvent(input$hover, {
if (draw()) {
vals$x <- c(vals$x, input$hover$x)
vals$y <- c(vals$y, input$hover$y)
}
})
output$plot= renderPlot({
plot(x=vals$x, y=vals$y, xlim=c(0, 28), ylim=c(0, 28), ylab="y", xlab="x", type="l", lwd=input$mywidth)
})
}
shinyApp(ui, server)